---
title: Установочные хуки
description: Выполняйте логику во время жизненного цикла установки, обновления или удаления — выполняйте начальное заполнение данными, создавайте резервные копии записей, проверяйте корректность обновления, очищайте внешние ресурсы.
icon: wrench
---
Установочные хуки — это специальные логические функции, которые выполняются во время жизненного цикла установки, обновления или удаления. Они используют то же окружение выполнения обработчика, что и обычные [logic functions](/l/ru/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. Обращайтесь к pre-install только тогда, когда сама миграция разрушительна и вам нужно перехватить предыдущее состояние, прежде чем оно исчезнет.
| Вы хотите... | Использовать |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Инициализировать данные, настроить рабочее пространство, зарегистрировать внешние ресурсы | `post-install` |
| Длительные операции, которые не должны блокировать ответ установки | `post-install` (асинхронный режим по умолчанию, с повторными попытками воркера) |
| Быстрая настройка, на которую вызывающая сторона полагается сразу после завершения установки | `post-install` с `shouldRunSynchronously: true` |
| Прочитать или сохранить данные, которые предстоящая миграция может потерять | `pre-install` |
| Отклонить обновление, которое повредит существующие данные | `pre-install` (бросьте исключение из обработчика) |
| Согласование при каждом обновлении | Любой хук с `shouldRunOnVersionUpgrade: true` |
## Общее поведение для обоих хуков
* Конфиг — это конфиг `defineLogicFunction` без настроек триггера, но с `shouldRunOnVersionUpgrade`.
* **Когда запускается**: по умолчанию только при чистой установке. Установите `shouldRunOnVersionUpgrade: true`, чтобы также запускать его при обновлениях. Используйте `previousVersion` / `newVersion`, чтобы разветвлять логику в зависимости от пути обновления.
* **Идемпотентность имеет значение**: асинхронный post-install может быть повторно выполнен, и любой хук будет запускаться повторно при обновлениях, если включён `shouldRunOnVersionUpgrade`.
* Обычное окружение logic-функций (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) инъецируется, поэтому вы можете вызывать Twenty API с токеном своего приложения.
* Хук автоматически прикрепляется к манифесту приложения на этапе сборки (`preInstallLogicFunction` / `postInstallLogicFunction`) — ничего не нужно указывать в [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
* Значение `timeoutSeconds` по умолчанию — 300, чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных.
* **Не выполняется в dev-режиме**: `yarn twenty dev` пропускает процесс установки и синхронизирует файлы напрямую, поэтому хуки там никогда не запускаются. Вместо этого запускайте их вручную:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
Запускается после завершения установки приложения: метаданные синхронизированы, SDK-клиент сгенерирован, новая схема доступна для запросов. Пример — инициализировать запись по умолчанию при новой установке:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise => {
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` (без повторных попыток). **Используйте для быстрых операций, которые обязательно должны завершиться до ответа.** На этом этапе миграция уже применена, поэтому сбой не откатывает изменения схемы — он только сообщает об ошибке.
Запускается до миграции метаданных, в контексте **предыдущей** схемы — подходящее место, чтобы создать резервную копию данных, которые были бы потеряны при миграции, или отклонить рискованное обновление. Перед выполнением сервер запускает чисто добавочную «урезанную синхронизацию», которая регистрирует только pre-install функцию новой версии; всё остальное — объекты, поля и данные предыдущей версии — остаётся нетронутым во время выполнения вашего обработчика.
Pre-install всегда **синхронный** и блокирует установку. Если обработчик генерирует исключение, установка прерывается до применения каких-либо изменений схемы — рабочее пространство остаётся на предыдущей версии в согласованном состоянии. Это сделано намеренно: pre-install — ваш последний шанс отказать в рискованном обновлении.
Пример — скопировать значения устаревшего поля перед тем, как миграция его удалит:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => {
// 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,
});
```
## Хук удаления
`defineUninstallLogicFunction` объявляет хук, который выполняется, когда пользователь удаляет ваше приложение. Он выполняется **до того**, как метаданные, данные и код приложения будут удалены — после запуска миграции удаления уже нечему будет выполняться — поэтому ваш обработчик все еще может запрашивать объекты и записи приложения. Используйте его для очистки внешних ресурсов: отмены выделения ресурсов API, удаления оставшихся ботов, отзыва вебхуков.
Заметки:
* Хук работает по принципу «по возможности»: он выполняется синхронно, но сбой только логируется и **никогда не блокирует удаление** — очистка не должна делать удаление приложения невозможным.
* Он получает `UninstallPayload` (`{ version?: string }` — версия, которая удаляется).
* Он **не** выполняется при откате неуспешной новой установки — приложение так и не было полностью установлено.
* Хук не может выполниться после того, как приложение удалено, поэтому внешняя очистка, которая зависит от данных приложения (например, ID ботов, сохраненные в записях), должна выполняться здесь, а не во внешнем запланированном задании.
* Как и установочные хуки, он **не выполняется в режиме разработки** — вместо этого запустите его вручную:
```bash filename="Terminal"
yarn twenty dev:function:exec --uninstall
```
```ts src/logic-functions/uninstall.ts
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (_payload: UninstallPayload): Promise => {
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,
});
```