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
defineLogicFunctionconfig minus the trigger settings, plusshouldRunOnVersionUpgrade. - When it runs: fresh installs only, by default. Set
shouldRunOnVersionUpgrade: trueto also run on upgrades. UsepreviousVersion/newVersionto branch on the upgrade path. - Idempotency matters: async post-install may be retried, and either hook re-runs on upgrades when
shouldRunOnVersionUpgradeis 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 indefineApplication(). - The default
timeoutSecondsis 300 to allow longer setup tasks like data seeding. - Not executed in dev mode:
yarn twenty devskips the install flow and syncs files directly, so hooks never run there. Trigger them manually instead:
definePostInstallLogicFunction
Runs after the workspace metadata migration is applied
definePostInstallLogicFunction
Runs after the workspace metadata migration is applied
Runs once your app has finished installing: metadata synchronized, SDK client generated, new schema queryable. Example — seed a default record on fresh installs:The
src/logic-functions/post-install.ts
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 asPOST_INSTALL_ERRORto 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.
definePreInstallLogicFunction
Runs before the workspace metadata migration is applied
definePreInstallLogicFunction
Runs before the workspace metadata migration is applied
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