---
title: 설치 훅
description: 설치 전에나 후에 로직을 실행하여 시드 데이터를 추가하고, 레코드를 백업하고, 업그레이드를 검증하세요.
icon: wrench
---

설치 훅은 설치 또는 업그레이드 라이프사이클 동안 실행되는 특수한 로직 함수입니다. 이들은 일반 [로직 함수](/l/ko/developers/extend/apps/logic/logic-functions)와 동일한 핸들러 런타임을 공유하고 `InstallPayload`를 받지만, 자체 정의 함수인 `definePostInstallLogicFunction()` 및 `definePreInstallLogicFunction()`으로 선언되며, 일반 트리거 모델(HTTP, cron, 데이터베이스 이벤트) 외부에서 동작합니다.

각 앱은 **최대 하나의 pre-install** 함수와 **최대 하나의 post-install** 함수만 정의할 수 있습니다. 둘 이상이 감지되면 매니페스트 빌드에서 오류가 발생합니다.

```
┌─────────────────────────────────────────────────────────────┐
│ 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`)을 생략한 특수 변형입니다.
* 핸들러는 `{ previousVersion?: string; newVersion: string }` 형태의 `InstallPayload`를 받습니다 — `newVersion`은 현재 설치 중인 버전이고, `previousVersion`은 이전에 설치되었던 버전입니다(처음 설치인 경우에는 `undefined`). 이 값들을 사용하여 신규 설치와 업그레이드를 구분하고, 버전별 마이그레이션 로직을 실행하세요.
* **훅이 실행되는 시점**: 기본적으로 신규 설치에서만 실행됩니다. 이전 버전에서 앱이 업그레이드될 때도 실행되게 하려면 `shouldRunOnVersionUpgrade: true`를 전달하세요. 생략하면 플래그는 기본값 `false`가 되며, 업그레이드 시 훅을 건너뜁니다.
* **실행 모델 — 기본은 비동기, 동기는 선택적**: `shouldRunSynchronously` 플래그는 설치 후 작업이 *어떤 방식으로* 실행되는지 제어합니다.
  * `shouldRunSynchronously: false` *(기본값)* — 훅은 `retryLimit: 3`와 함께 **메시지 큐에 등록**되며 워커에서 비동기적으로 실행됩니다. 작업이 큐에 등록되는 즉시 설치 응답이 반환되므로, 처리 속도가 느리거나 실패하는 핸들러가 호출자를 차단하지 않습니다. 워커는 최대 세 번까지 재시도합니다. **장시간 실행되는 작업에 사용하세요** — 대규모 데이터셋 시딩, 느린 서드파티 API 호출, 외부 리소스 프로비저닝 등 합리적인 HTTP 응답 시간 창을 초과할 수 있는 모든 작업.
  * `shouldRunSynchronously: true` — 훅이 **설치 플로우 중에 인라인으로** 실행됩니다(설치 전과 동일한 실행기). 핸들러가 완료될 때까지 설치 요청이 블록되고, 예외가 발생하면 설치 호출자는 `POST_INSTALL_ERROR`를 받습니다. 자동 재시도 없음. **응답 전에 반드시 완료되어야 하는 빠른 작업에 사용하세요** — 예: 사용자에게 검증 오류를 표시하거나, 설치 호출이 반환된 직후 클라이언트가 즉시 의존하는 빠른 설정. post-install이 실행될 시점에는 메타데이터 마이그레이션이 이미 적용되었음을 유의하세요. 따라서 동기 모드에서 실패하더라도 스키마 변경이 **롤백되지 않으며**, 오류만 노출됩니다.
* 핸들러가 멱등적임을 보장하세요. 비동기 모드에서는 큐가 최대 세 번까지 재시도할 수 있습니다. 어떤 모드이든 `shouldRunOnVersionUpgrade: true`인 경우 업그레이드 시 훅이 다시 실행될 수 있습니다.
* 환경 변수 `APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`은 핸들러 내부에서 사용할 수 있습니다(다른 로직 함수와 동일). 따라서 앱에 범위가 지정된 애플리케이션 액세스 토큰으로 Twenty API를 호출할 수 있습니다.
* 애플리케이션당 설치 후 함수는 하나만 허용됩니다. 둘 이상이 감지되면 매니페스트 빌드에서 오류가 발생합니다.
* 함수의 `universalIdentifier`, `shouldRunOnVersionUpgrade`, `shouldRunSynchronously`는 빌드 중에 애플리케이션 매니페스트의 `postInstallLogicFunction` 필드에 자동으로 첨부됩니다 — 따라서 [`defineApplication()`](/l/ko/developers/extend/apps/config/application)에서 이들을 참조할 필요가 없습니다.
* 기본 시간 제한은 데이터 시딩과 같은 더 긴 설정 작업을 허용하기 위해 300초(5분)로 설정되어 있습니다.
* **개발 모드에서 실행되지 않음**: 앱이 로컬로 등록된 경우(`yarn twenty dev`), 서버는 설치 플로우를 완전히 건너뛰고 CLI 워처를 통해 파일을 직접 동기화합니다 — 따라서 `shouldRunSynchronously` 여부와 관계없이 개발 모드에서는 post-install이 절대 실행되지 않습니다. 실행 중인 워크스페이스에 대해 수동으로 트리거하려면 `yarn twenty dev:function:exec --postInstall`을 사용하세요.

</Accordion>
<Accordion title="definePreInstallLogicFunction" description="워크스페이스 메타데이터 마이그레이션이 적용되기 전에 실행됩니다.">

pre-install 함수는 설치 중에 자동으로 실행되는 로직 함수로, **워크스페이스 메타데이터 마이그레이션이 적용되기 전에** 실행됩니다. post-install과 동일한 페이로드 형태(`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
```

핵심 요점:
* pre-install 함수는 `definePreInstallLogicFunction()`을 사용합니다 — post-install과 동일한 특수화된 구성을 사용하되, 서로 다른 라이프사이클 슬롯에 연결됩니다.
* pre-install과 post-install 핸들러는 동일한 `InstallPayload` 타입을 받습니다: `{ previousVersion?: string; newVersion: string }`. 한 번만 임포트하여 두 훅에서 재사용하세요.
* **훅이 실행되는 시점**: 워크스페이스 메타데이터 마이그레이션(`synchronizeFromManifest`) 직전. 실행에 앞서, 서버는 워크스페이스 메타데이터에 **새로운** 버전의 pre-install 함수를 등록하는 순수 추가식의 "간소화된 동기화"를 수행합니다 — 그 외에는 아무것도 변경하지 않습니다 — 그리고 나서 이를 실행합니다. 이 동기화는 추가 전용이므로, 핸들러가 실행될 때 이전 버전의 객체, 필드, 데이터는 그대로 유지됩니다. 따라서 마이그레이션 이전 상태를 안전하게 읽고 백업할 수 있습니다.
* **실행 모델**: pre-install은 **동기적으로** 실행되며 **설치를 차단**합니다. 핸들러에서 예외를 던지면, 어떤 스키마 변경도 적용되기 전에 설치가 중단되며 — 워크스페이스는 일관된 상태로 이전 버전에 머무릅니다. 이는 의도된 동작입니다: pre-install은 위험한 업그레이드를 거부할 수 있는 마지막 기회입니다.
* post-install과 마찬가지로, 애플리케이션당 pre-install 함수는 하나만 허용됩니다. 빌드 중에 애플리케이션 매니페스트의 `preInstallLogicFunction` 아래에 자동으로 연결됩니다.
* **개발 모드에서 실행되지 않음**: post-install과 동일하게 — 로컬로 등록된 앱은 설치 플로우가 완전히 건너뛰어지므로 `yarn twenty dev` 환경에서 pre-install은 실행되지 않습니다. `yarn twenty dev:function:exec --preInstall`를 사용하여 수동으로 트리거하세요.

</Accordion>
<Accordion title="pre-install vs post-install: 언제 어떤 것을 사용할지" description="올바른 설치 훅 선택하기">

두 훅 모두 동일한 설치 플로우의 일부이며 같은 `InstallPayload`를 받습니다. 차이점은 워크스페이스 메타데이터 마이그레이션과의 상대적인 실행 **시점**이며, 이에 따라 안전하게 다룰 수 있는 데이터가 달라집니다.

pre-install은 항상 **동기식**입니다(설치를 차단하고 중단할 수 있음). post-install은 **기본적으로 비동기식**입니다 — 워커에 큐잉되고 자동 재시도가 수행됩니다 — 하지만 `shouldRunSynchronously: true`로 동기 실행을 선택할 수 있습니다. 각 모드를 언제 사용할지에 대해서는 위의 `definePostInstallLogicFunction` 아코디언을 참고하세요.

**새로운 스키마의 존재가 필요한 작업에는 `post-install`을 사용하세요.** 일반적인 경우입니다:

* 새로 추가된 객체와 필드를 대상으로 기본 데이터를 시딩(초기 레코드, 기본 보기, 데모 콘텐츠 생성)하는 작업.
* 앱에 자격 증명이 생겼으므로 서드파티 서비스에 웹훅을 등록하는 작업.
* 동기화된 메타데이터에 의존하는 설정을 완료하기 위해 자체 API를 호출하는 작업.
* 모든 업그레이드마다 상태를 조정해야 하는 멱등적인 "존재함을 보장(ensure this exists)" 로직 — `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`을 사용하세요.** pre-install은 *이전* 스키마에 대해 실행되고 실패 시 업그레이드를 롤백하므로, 위험한 작업에 적합합니다:

* **곧 삭제되거나 재구조화될 데이터를 백업** — 예: v2에서 필드를 제거하므로, 마이그레이션이 실행되기 전에 해당 값을 다른 필드로 복사하거나 스토리지로 내보내야 하는 경우.
* **새로운 제약으로 인해 무효화될 레코드를 보관** — 예: 어떤 필드가 `NOT NULL`로 바뀌어 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`, 워커 재시도 포함) |
| 설치 호출이 반환된 직후 호출자가 즉시 의존하는 빠른 설정 실행    | `shouldRunSynchronously: true`를 사용하는 `post-install`              |
| 곧 진행될 마이그레이션으로 손실될 데이터를 읽거나 백업         | `pre-install`                                                    |
| 기존 데이터를 손상시킬 업그레이드를 거부                 | `pre-install` (핸들러에서 예외를 던짐)                                     |
| 모든 업그레이드 시 상태 조정 실행                    | `shouldRunOnVersionUpgrade: true`를 사용하는 `post-install`           |
| 최초 설치에서만 1회성 설정 수행                     | `shouldRunOnVersionUpgrade: false`(기본값)을 사용하는 `post-install`     |

<Note>
확신이 서지 않는다면 기본적으로 **post-install**을 사용하세요. 마이그레이션 자체가 파괴적이며 이전 상태가 사라지기 전에 이를 가로채야 할 때에만 pre-install을 사용하세요.
</Note>

</Accordion>
</AccordionGroup>
