---
title: 安装钩子
description: 在安装之前或之后运行逻辑——预置数据、备份记录、验证升级。
icon: wrench
---

安装钩子是在安装或升级生命周期期间运行的特殊逻辑函数。 它们与常规的[逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions)共享相同的处理程序运行时，并接收 `InstallPayload`，但它们使用自己的定义函数声明——`definePostInstallLogicFunction()` 和 `definePreInstallLogicFunction()`——并且存在于普通触发模型（HTTP、cron、数据库事件）之外。

每个应用**最多只能定义一个安装前函数**和**最多一个安装后函数**。 如果检测到任一类型多于一个，清单构建将报错。

```
┌─────────────────────────────────────────────────────────────┐
│ install flow                                                │
│                                                             │
│   upload package → [pre-install] → metadata migration →     │
│   generate SDK → [post-install]                             │
│                                                             │
│                  old schema visible    new schema visible   │
└─────────────────────────────────────────────────────────────┘
```

<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="在应用工作区元数据迁移之后运行">

安装后函数会在你的应用完成安装到某个工作区后自动运行。 服务器会在应用的元数据已同步并已生成 SDK 客户端**之后**执行它，因此工作区已完全可用，且新架构已就绪。 常见用例包括预置默认数据、创建初始记录、配置工作区设置，或在第三方服务上预配资源。

```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';

const handler = async (payload: InstallPayload): Promise<void> => {
  console.log('Post install logic function executed successfully!', payload.previousVersion);
};

export default definePostInstallLogicFunction({
  universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
  name: 'post-install',
  description: 'Runs after installation to set up the application.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: false,
  shouldRunSynchronously: false,
  handler,
});
```

你也可以随时使用 CLI 手动执行安装后函数：

```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```

关键点：
* 安装后函数使用 `definePostInstallLogicFunction()` — 这是一个省略触发器设置（`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`toolTriggerSettings`、`workflowActionTriggerSettings`）的专用变体。
* 处理程序会接收一个 `InstallPayload`，其为 `{ previousVersion?: string; newVersion: string }` —— `newVersion` 是正在安装的版本，而 `previousVersion` 是先前已安装的版本（在全新安装时为 `undefined`）。 使用这些值来区分全新安装与升级，并运行特定版本的迁移逻辑。
* **钩子何时运行**：默认情况下，仅在全新安装时运行。 如果还希望在应用从旧版本升级时运行，请传入 `shouldRunOnVersionUpgrade: true`。 若省略，该标志默认为 `false`，升级将跳过该钩子。
* **执行模型 — 默认异步，可选择同步**：`shouldRunSynchronously` 标志控制安装后*如何*执行。
  * `shouldRunSynchronously: false` *(默认)* — 该钩子会**加入消息队列**，设置 `retryLimit: 3`，并在工作线程中异步运行。 作业一入列，安装响应即返回，因此缓慢或失败的处理程序不会阻塞调用方。 工作线程最多会重试三次。 **将其用于长时间运行的作业**——预填充大型数据集、调用缓慢的第三方 API、预配外部资源，以及任何可能超出合理 HTTP 响应窗口的任务。
  * `shouldRunSynchronously: true` — 该钩子会在安装流程中**内联执行**（与安装前使用相同的执行器）。 安装请求将阻塞直至处理程序完成；若抛出异常，安装调用方将收到 `POST_INSTALL_ERROR`。 不进行自动重试。 **用于需要在响应前完成的快速工作**——例如向用户返回验证错误，或进行安装调用返回后客户端将立即依赖的快速设置。 请注意，运行安装后时，元数据迁移已应用完成，因此同步模式下的失败**不会**回滚架构更改——它只会暴露错误。
* 确保你的处理程序是幂等的。 在异步模式下，队列最多可重试三次；在任一模式下，当 `shouldRunOnVersionUpgrade: true` 时，该钩子在升级时可能再次运行。
* 在处理程序内可使用环境变量 `APPLICATION_ID`、`APP_ACCESS_TOKEN` 和 `API_URL`（与其他逻辑函数相同），因此你可以使用作用域限定到你应用的应用访问令牌调用 Twenty API。
* 每个应用仅允许一个安装后函数。 如果检测到多个，清单构建将报错。
* 构建期间，函数的 `universalIdentifier`、`shouldRunOnVersionUpgrade` 和 `shouldRunSynchronously` 会自动附加到应用清单的 `postInstallLogicFunction` 字段下——你无需在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 中引用它们。
* 默认超时时间设置为 300 秒（5 分钟），以便支持更长的设置任务，如数据填充。
* **开发模式下不执行**：当应用在本地注册（通过 `yarn twenty dev`）时，服务器会完全跳过安装流程，并通过 CLI 监视器直接同步文件——因此无论 `shouldRunSynchronously` 如何，安装后在开发模式下都不会运行。 使用 `yarn twenty dev:function:exec --postInstall` 在运行中的工作区上手动触发它。

</Accordion>
<Accordion title="definePreInstallLogicFunction" description="在应用工作区元数据迁移之前运行">

安装前函数会在安装期间自动运行，**在应用工作区元数据迁移之前**。 它与安装后共享相同的负载结构（`InstallPayload`），但在安装流程中位置更早，因此可以准备即将到来的迁移所依赖的状态——典型用例如备份数据、验证与新架构的兼容性，或归档即将被重构或删除的记录。

```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';

const handler = async (payload: InstallPayload): Promise<void> => {
  console.log('Pre install logic function executed successfully!', payload.previousVersion);
};

export default definePreInstallLogicFunction({
  universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
  name: 'pre-install',
  description: 'Runs before installation to prepare the application.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: true,
  handler,
});
```

你也可以随时使用 CLI 手动执行安装前函数：

```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```

关键点：
* 安装前函数使用 `definePreInstallLogicFunction()`——与安装后相同的专用配置，只是附加到不同的生命周期阶段。
* 安装前和安装后处理程序接收相同的 `InstallPayload` 类型：`{ previousVersion?: string; newVersion: string }`。 导入一次，可在两个钩子中复用。
* **钩子何时运行**：位于工作区元数据迁移（`synchronizeFromManifest`）之前。 在执行之前，服务器会运行一次纯增量的“精简同步”，将**新**版本的安装前函数注册到工作区元数据中——不会触及其他任何内容——然后再执行它。 由于此次同步仅为增量操作，当你的处理程序运行时，上一版本的对象、字段和数据仍完好无损：你可以安全地读取并备份迁移前的状态。
* **执行模型**：安装前以**同步**方式执行，并且**会阻塞安装**。 如果处理程序抛出异常，安装会在任何架构更改应用之前被中止——工作区将保持在上一版本且处于一致状态。 这是有意为之：安装前是你拒绝高风险升级的最后机会。
* 与安装后相同，每个应用仅允许一个安装前函数。 在构建期间，它会自动附加到应用清单的 `preInstallLogicFunction` 下。
* **开发模式下不执行**：与安装后相同——对于本地注册的应用将完全跳过安装流程，因此在 `yarn twenty dev` 下不会运行安装前。 使用 `yarn twenty dev:function:exec --preInstall` 手动触发它。

</Accordion>
<Accordion title="安装前 vs 安装后：何时使用哪一个" description="选择合适的安装钩子">

两个钩子都属于同一安装流程，并接收相同的 `InstallPayload`。 区别在于它们相对于工作区元数据迁移**何时**运行，这会影响它们可以安全访问的数据范围。

安装前始终为**同步**（会阻塞安装并可中止它）。 安装后**默认异步**——在工作线程中入列并自动重试——但可通过 `shouldRunSynchronously: true` 选择同步执行。 关于各模式的使用场景，请参见上方的 `definePostInstallLogicFunction` 折叠面板。

**对于需要新架构已存在的任何事项，请使用 `post-install`。** 这是最常见的情况：

* 针对新添加的对象和字段预填充默认数据（创建初始记录、默认视图、演示内容）。
* 在应用已有凭据的前提下，向第三方服务注册 Webhook。
* 调用你自己的 API 完成依赖已同步元数据的设置。
* 用于在每次升级时对状态进行对账的幂等“确保其存在”逻辑——结合 `shouldRunOnVersionUpgrade: true` 使用。

示例——在安装后预填充一个默认的 `PostCard` 记录：

```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';

const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
  if (previousVersion) return; // fresh installs only

  const client = createClient();
  await client.postCard.create({
    data: { title: 'Welcome to Postcard', content: 'Your first card!' },
  });
};

export default definePostInstallLogicFunction({
  universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
  name: 'post-install',
  description: 'Seeds a welcome post card after install.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: false,
  handler,
});
```

**当迁移可能破坏或损坏现有数据时，请使用 `pre-install`。** 由于安装前在*先前*架构上运行，且其失败会回滚升级，因此凡是有风险的操作都应放在这里：

* **备份即将被删除或重构的数据**——例如，你在 v2 中移除某个字段，需要在迁移运行前将其值复制到另一个字段或导出到存储中。
* **归档会被新约束判为无效的记录**——例如某个字段将变为 `NOT NULL`，你需要先删除或修正具有空值的行。
* **验证兼容性；若当前数据无法干净迁移则拒绝升级**——从处理程序中抛出异常，安装将中止且不会应用任何更改。 这比在迁移中途才发现不兼容要更安全。
* **在会导致关联丢失的架构更改之前**对数据进行重命名或重新设置键。

示例——在破坏性迁移之前归档记录：

```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';

const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
  // Only the 1.x → 2.x upgrade drops the legacy `notes` field.
  if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
    return;
  }

  const client = createClient();
  const legacyRecords = await client.postCard.findMany({
    where: { notes: { isNotNull: true } },
  });

  if (legacyRecords.length === 0) return;

  // Copy legacy `notes` into the new `description` field before the migration
  // drops the `notes` column. If this fails, the upgrade is aborted and the
  // workspace stays on v1 with all data intact.
  await Promise.all(
    legacyRecords.map((record) =>
      client.postCard.update({
        where: { id: record.id },
        data: { description: record.notes },
      }),
    ),
  );
};

export default definePreInstallLogicFunction({
  universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
  name: 'pre-install',
  description: 'Backs up legacy notes into description before the v2 migration.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: true,
  handler,
});
```

**经验法则：**

| 你想要...                  | 使用                                                            |
| ----------------------- | ------------------------------------------------------------- |
| 预填充默认数据、配置工作区、注册外部资源    | `post-install`                                                |
| 运行不应阻塞安装响应的长时间预填充或第三方调用 | `post-install` (默认 — `shouldRunSynchronously: false`，由工作线程重试) |
| 运行安装调用返回后调用方将立即依赖的快速设置  | `post-install`，配合 `shouldRunSynchronously: true`              |
| 读取或备份即将被迁移丢失的数据         | `pre-install`                                                 |
| 拒绝会损坏现有数据的升级            | `pre-install`（从处理程序中抛出异常）                                     |
| 在每次升级时执行对账              | `post-install` 配合 `shouldRunOnVersionUpgrade: true`           |
| 仅在首次安装时执行一次性设置          | `post-install` 配合 `shouldRunOnVersionUpgrade: false`（默认）      |

<Note>
如有不确定，默认选择**安装后（post-install）**。 仅当迁移本身具有破坏性，且你需要在其丢失之前拦截先前状态时，才使用安装前。
</Note>

</Accordion>
</AccordionGroup>
