Install hooks are special logic functions that run during the install or upgrade lifecycle. They share the same handler runtime as regular logic functions and receive an InstallPayload ({ previousVersion?: string; newVersion: string }previousVersion is undefined on a fresh install), but they’re declared with their own define functions and live outside the normal trigger model (HTTP, cron, database events). Each app may define at most one pre-install and at most one post-install function. The manifest build errors if more than one of either is detected.

At a glance

Rule of thumb: default to post-install. Only reach for pre-install when the migration itself is destructive and you need to intercept the previous state before it is gone.

Behavior shared by both hooks

  • The config is a defineLogicFunction config minus the trigger settings, plus shouldRunOnVersionUpgrade.
  • When it runs: fresh installs only, by default. Set shouldRunOnVersionUpgrade: true to also run on upgrades. Use previousVersion / newVersion to branch on the upgrade path.
  • Idempotency matters: async post-install may be retried, and either hook re-runs on upgrades when shouldRunOnVersionUpgrade is on.
  • The usual logic-function environment (APPLICATION_ID, APP_ACCESS_TOKEN, API_URL) is injected, so you can call the Zambrite CRM API with your app’s token.
  • The hook is attached to the application manifest automatically at build time (preInstallLogicFunction / postInstallLogicFunction) — nothing to reference in defineApplication().
  • The default timeoutSeconds is 300 to allow longer setup tasks like data seeding.
  • Not executed in dev mode: yarn twenty dev skips the install flow and syncs files directly, so hooks never run there. Trigger them manually instead:
Runs once your app has finished installing: metadata synchronized, SDK client generated, new schema queryable. Example — seed a default record on fresh installs:
src/logic-functions/post-install.ts
The shouldRunSynchronously flag controls the execution model:
  • false (default) — enqueued on the message queue (retryLimit: 3) and run by a worker. The install response returns as soon as the job is enqueued. Use for long-running work — seeding large datasets, slow third-party APIs.
  • true — executed inline during the install flow. The install request blocks until the handler finishes; a thrown error surfaces as POST_INSTALL_ERROR to the caller (no retries). Use for fast, must-complete-before-response work. The migration has already been applied at this point, so a failure does not roll back schema changes — it only surfaces the error.
Runs before the metadata migration, against the previous schema — the right place to back up data a migration would lose, or to refuse a risky upgrade. Before executing, the server runs a purely additive “pared-down sync” that registers only the new version’s pre-install function; everything else — the previous version’s objects, fields, and data — is untouched when your handler runs.Pre-install is always synchronous and blocks the install. If the handler throws, the install is aborted before any schema change — the workspace stays on the previous version in a consistent state. This is intentional: pre-install is your last chance to refuse a risky upgrade.Example — copy a legacy field’s values before the migration drops it:
src/logic-functions/pre-install.ts