> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 安装钩子

> 在安装、升级或卸载生命周期中运行逻辑——预置数据、备份记录、验证升级、清理外部资源。

安装钩子是在安装、升级或卸载生命周期期间运行的特殊逻辑函数。 它们与常规的[逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions)共享相同的处理程序运行时，但使用自己的定义函数声明，并且存在于普通触发模型（HTTP、cron、数据库事件）之外。 安装钩子接收一个 `InstallPayload`（`{ previousVersion?: string; newVersion: string }`——在全新安装时 `previousVersion` 为 `undefined`）；卸载钩子接收一个 `UninstallPayload`（`{ version?: string }`——要被移除的版本）。

每个应用每种钩子（预安装、后安装、卸载）最多只能定义一个。 如果检测到任一类型多于一个，清单构建将报错。

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

## 一览

|      | `definePreInstallLogicFunction` | `definePostInstallLogicFunction`                          |
| ---- | ------------------------------- | --------------------------------------------------------- |
| 运行   | 元数据迁移之前——**先前**的模式和数据仍然完好无损     | 迁移和 SDK 生成之后——**新的**模式已就位                                 |
| 执行   | 始终为同步；会阻塞安装                     | 默认异步（排队，重试 3 次）；可通过 `shouldRunSynchronously: true` 选择同步   |
| 失败时  | 安装在任何模式更改之前被**中止**              | 异步：最多重试 3 次。 同步：调用方会收到 `POST_INSTALL_ERROR`（模式更改**不会**回滚） |
| 典型用途 | 备份或修复迁移会丢失的数据；通过抛出异常拒绝存在风险的升级   | 预填充默认数据、配置工作区、注册外部资源                                      |

**经验法则：** 默认使用 post-install。 仅当迁移本身具有破坏性，且你需要在其丢失之前拦截先前状态时，才使用安装前。

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

## 两个钩子共享的行为

* 该配置等同于 `defineLogicFunction` 的配置减去触发器设置，再加上 `shouldRunOnVersionUpgrade`。
* **运行时机**：默认情况下，仅在全新安装时运行。 将 `shouldRunOnVersionUpgrade: true` 设为 true 以便在升级时也运行。 使用 `previousVersion` / `newVersion` 按升级路径分支处理。
* **幂等性很重要**：异步 post-install 可能会被重试，而且当开启 `shouldRunOnVersionUpgrade` 时，任一钩子都会在升级时重新运行。
* 会注入常规的逻辑函数环境（`APPLICATION_ID`、`APP_ACCESS_TOKEN`、`API_URL`），因此你可以使用应用的令牌调用 Twenty API。
* 该钩子会在构建时自动附加到应用清单上（`preInstallLogicFunction` / `postInstallLogicFunction`）——在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 中无需额外引用。
* 默认的 `timeoutSeconds` 为 300，以便支持更长的设置任务，例如数据填充。
* **在开发模式下不会执行**：`yarn twenty dev` 会跳过安装流程并直接同步文件，因此钩子在其中不会运行。 改为手动触发它们：

```bash filename="Terminal" theme={null}
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```

<AccordionGroup>
  <Accordion title="definePostInstallLogicFunction" description="在应用工作区元数据迁移之后运行">
    在应用完成安装后运行：元数据已同步、SDK 客户端已生成、新模式可被查询。 示例——在全新安装时预填充一个默认记录：

    ```ts src/logic-functions/post-install.ts theme={null}
    import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
    import { CoreApiClient } from 'twenty-client-sdk/core';

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

      const client = new CoreApiClient();
      await client.mutation({
        createPostCard: {
          __args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
          id: true,
        },
      });
    };

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

    `shouldRunSynchronously` 标志控制执行模型：

    * `false` *（默认）*——放入消息队列（`retryLimit: 3`）并由工作线程运行。 安装响应会在任务被放入队列后立即返回。 **用于长时间运行的任务**——例如预填充大型数据集、调用缓慢的第三方 API。
    * `true`——在安装流程中内联执行。 安装请求会阻塞直至处理程序完成；抛出的错误会以 `POST_INSTALL_ERROR` 的形式暴露给调用方（不重试）。 **用于必须在返回响应前完成的快速任务。** 此时迁移已应用，因此失败不会回滚模式更改——只会将错误暴露出来。
  </Accordion>

  <Accordion title="definePreInstallLogicFunction" description="在应用工作区元数据迁移之前运行">
    在元数据迁移之前、针对**先前**模式运行——适合在迁移会删除数据前对其进行备份，或拒绝存在风险的升级。 在执行之前，服务器会运行一次纯增量的“精简同步”，仅注册新版本的 pre-install 函数；当你的处理程序运行时，其他一切——上一版本的对象、字段和数据——都不会被触及。

    安装前始终为**同步**，并会阻塞安装。 如果处理程序抛出异常，安装会在任何模式更改之前被中止——工作区将保持在上一版本且处于一致状态。 这是有意为之：安装前是你拒绝高风险升级的最后机会。

    示例——在迁移删除旧字段之前复制该旧字段的值：

    ```ts src/logic-functions/pre-install.ts theme={null}
    import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
    import { CoreApiClient } from 'twenty-client-sdk/core';

    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 = new CoreApiClient();
      const { postCards } = await client.query({
        postCards: {
          __args: { filter: { notes: { isNot: null } } },
          edges: { node: { id: true, notes: true } },
        },
      });

      // Copy legacy `notes` into `description` before the migration drops the
      // column. If this fails, the upgrade aborts and the workspace stays on v1.
      for (const { node } of postCards.edges) {
        await client.mutation({
          updatePostCard: {
            __args: { id: node.id, data: { description: node.notes } },
            id: true,
          },
        });
      }
    };

    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,
    });
    ```
  </Accordion>
</AccordionGroup>

## 卸载钩子

`defineUninstallLogicFunction` 声明一个在用户卸载你的应用时运行的钩子。 它在应用的元数据、数据和代码被移除之前执行——一旦删除迁移运行完成，就不再有任何内容可供执行——因此你的处理程序仍然可以查询应用的对象和记录。 将其用于清理外部资源：取消预置 API 资源、删除剩余的机器人、撤销网络钩子。

备注：

* 该钩子是尽力执行：它同步运行，但失败会被记录，并且**绝不会阻止卸载**——清理操作绝不能导致应用无法被移除。
* 它接收 `UninstallPayload`（`{ version?: string }`——要被移除的版本）。
* 在回滚失败的全新安装时，它**不会**运行——该应用从未完成安装。
* 钩子无法在应用被删除后运行，因此依赖应用数据的外部清理（例如存储在记录中的机器人 ID）应放在这里，而不是放在外部计划任务中。
* 与安装钩子类似，它在开发模式下**不会执行**——请改为手动触发：

```bash filename="Terminal" theme={null}
yarn twenty dev:function:exec --uninstall
```

```ts src/logic-functions/uninstall.ts theme={null}
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async (_payload: UninstallPayload): Promise<void> => {
  const client = new CoreApiClient();
  const { meetingBots } = await client.query({
    meetingBots: { edges: { node: { id: true, externalBotId: true } } },
  });

  // Delete the provider-side bots so nothing keeps recording after uninstall.
  for (const { node } of meetingBots.edges) {
    await fetch(`https://api.recorder.example/bots/${node.externalBotId}`, {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${process.env.RECORDER_API_KEY}` },
    });
  }
};

export default defineUninstallLogicFunction({
  universalIdentifier: 'b2c3d4e5-6789-01bc-def0-234567890abc',
  name: 'uninstall',
  description: 'Deletes remaining recorder bots when the app is uninstalled.',
  timeoutSeconds: 300,
  handler,
});
```
