메인 콘텐츠로 건너뛰기
설치 훅은 설치 또는 업그레이드 라이프사이클 동안 실행되는 특수한 로직 함수입니다. 이들은 일반 로직 함수와 동일한 핸들러 런타임을 공유하고 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   │
└─────────────────────────────────────────────────────────────┘
설치 후 함수는 워크스페이스에 앱 설치가 완료된 뒤 자동으로 실행되는 로직 함수입니다. 서버는 앱의 메타데이터가 동기화되고 SDK 클라이언트가 생성된 이후 이를 실행하므로, 워크스페이스는 완전히 사용할 준비가 되었고 새 스키마가 적용된 상태입니다. 일반적인 사용 사례로는 기본 데이터를 시드하는 것, 초기 레코드를 생성하는 것, 워크스페이스 설정을 구성하는 것, 서드파티 서비스에서 리소스를 프로비저닝하는 것 등이 있습니다.
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를 사용하여 언제든지 설치 후 함수를 수동으로 실행할 수도 있습니다:
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()에서 이들을 참조할 필요가 없습니다.
  • 기본 시간 제한은 데이터 시딩과 같은 더 긴 설정 작업을 허용하기 위해 300초(5분)로 설정되어 있습니다.
  • 개발 모드에서 실행되지 않음: 앱이 로컬로 등록된 경우(yarn twenty dev), 서버는 설치 플로우를 완전히 건너뛰고 CLI 워처를 통해 파일을 직접 동기화합니다 — 따라서 shouldRunSynchronously 여부와 관계없이 개발 모드에서는 post-install이 절대 실행되지 않습니다. 실행 중인 워크스페이스에 대해 수동으로 트리거하려면 yarn twenty dev:function:exec --postInstall을 사용하세요.
pre-install 함수는 설치 중에 자동으로 실행되는 로직 함수로, 워크스페이스 메타데이터 마이그레이션이 적용되기 전에 실행됩니다. post-install과 동일한 페이로드 형태(InstallPayload)를 사용하지만, 설치 플로우에서 더 이른 단계에 위치하여 곧 진행될 마이그레이션이 의존하는 상태를 준비할 수 있습니다 — 일반적인 사용 사례로는 데이터 백업, 새 스키마와의 호환성 검증, 재구조화되거나 삭제될 레코드의 보관 등이 있습니다.
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를 사용하여 언제든지 설치 전 함수를 수동으로 실행할 수도 있습니다:
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를 사용하여 수동으로 트리거하세요.
두 훅 모두 동일한 설치 플로우의 일부이며 같은 InstallPayload를 받습니다. 차이점은 워크스페이스 메타데이터 마이그레이션과의 상대적인 실행 시점이며, 이에 따라 안전하게 다룰 수 있는 데이터가 달라집니다.pre-install은 항상 동기식입니다(설치를 차단하고 중단할 수 있음). post-install은 기본적으로 비동기식입니다 — 워커에 큐잉되고 자동 재시도가 수행됩니다 — 하지만 shouldRunSynchronously: true로 동기 실행을 선택할 수 있습니다. 각 모드를 언제 사용할지에 대해서는 위의 definePostInstallLogicFunction 아코디언을 참고하세요.새로운 스키마의 존재가 필요한 작업에는 post-install을 사용하세요. 일반적인 경우입니다:
  • 새로 추가된 객체와 필드를 대상으로 기본 데이터를 시딩(초기 레코드, 기본 보기, 데모 콘텐츠 생성)하는 작업.
  • 앱에 자격 증명이 생겼으므로 서드파티 서비스에 웹훅을 등록하는 작업.
  • 동기화된 메타데이터에 의존하는 설정을 완료하기 위해 자체 API를 호출하는 작업.
  • 모든 업그레이드마다 상태를 조정해야 하는 멱등적인 “존재함을 보장(ensure this exists)” 로직 — shouldRunOnVersionUpgrade: true와 함께 사용하세요.
예시 — 설치 후 기본 PostCard 레코드를 시딩하기:
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 값을 가진 행을 먼저 삭제하거나 수정해야 하는 경우.
  • 호환성을 검증하고, 현재 데이터를 깔끔하게 마이그레이션할 수 없는 경우 업그레이드를 거부 — 핸들러에서 예외를 던지면 아무 변경도 적용되지 않은 채 설치가 중단됩니다. 이는 마이그레이션 도중에 비호환성을 발견하는 것보다 더 안전합니다.
  • 연관이 끊어질 수 있는 스키마 변경에 앞서 데이터 이름 변경 또는 키 재지정.
예시 — 파괴적인 마이그레이션 전에 레코드 보관하기:
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
확신이 서지 않는다면 기본적으로 post-install을 사용하세요. 마이그레이션 자체가 파괴적이며 이전 상태가 사라지기 전에 이를 가로채야 할 때에만 pre-install을 사용하세요.