snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
---
title: アプリケーション設定
description: "`defineApplication` を使用して、アプリの識別情報、デフォルトのロール、変数、およびマーケットプレイスのメタデータを宣言します。"
icon: rocket
---
すべてのアプリは、`defineApplication` の呼び出しを厳密に 1 つ持つ必要があります。 ここでは次の内容を宣言します。
* **Identity** — ユニバーサル識別子、表示名、説明。
* **Permissions** — ロジック関数およびフロントコンポーネントがどのロールで実行されるか。
* **Variables** *(optional)* — コードから環境変数として利用できるキーと値のペア。
* **プレインストール / ポストインストール / アンインストール フック** *(任意)* — [Logic Functions](/l/ja/developers/extend/apps/logic/logic-functions) を参照してください。
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
displayName: 'My Twenty App',
description: 'My first Twenty app',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
});
```
注記:
* `universalIdentifier` フィールドは、あなたが所有する安定した ID です。 一度生成し、その後の同期でも安定して維持してください。
* `applicationVariables` は関数やフロントコンポーネントの環境変数になります。 ロジック関数(サーバーサイド)では、`process.env.VARIABLE_NAME` として利用できます。 フロントコンポーネントでは、`twenty-sdk/front-component` の `getApplicationVariable('VARIABLE_NAME')` を使用します。 `isSecret: true` が指定された変数は、ロジック関数にのみインジェクトされます。 フロントコンポーネントには、秘密ではない変数のみが渡されます。
* デフォルトのロールは、[`defineApplicationRole()`](/l/ja/developers/extend/apps/config/roles) でマークされたロールファイルから自動的に検出されます。`defineApplication()` から参照する必要はありません。
* プレインストール関数、ポストインストール関数、およびアンインストール関数は、マニフェストのビルド中に自動的に検出されるため、`defineApplication()` で参照する必要はありません。
* 後方互換性のために `defaultRoleUniversalIdentifier` を明示的に渡すことも依然としてサポートされていますが、`defineApplicationRole()` が推奨されるため、非推奨となっています。
* `serverVariables` はインスタンス単位の構成およびシークレット(例: API キー)です。 `applicationVariables` と異なり、マニフェスト内で値は宣言されません。ワークスペースのオペレーターがアプリの設定からそれらを入力し、設定された時点でのみロジック関数に注入されます。
* どちらの種類の変数も `isDeprecated: true` を受け付けます。 変数を削除する代わりに廃止するためにこれを使用します。キーの宣言を残しておくことで保存されている値が保持されます(削除するとオペレーターが入力した値は失われます)。また、その変数は依然としてインジェクトされるため、コードは `process.env.NEW_API_KEY ?? process.env.API_KEY` にフォールバックできます。 非推奨の変数は値がなくなるとアプリの設定から消え、アプリの構成チェックにも一切カウントされません。そのため、`isDeprecated` は `isRequired` より優先されます。
* アプリの **Settings** タブ内で(デフォルトの変数設定セクションの代わりに)カスタム設定 UI をレンダーするには、専用のファイル内で [`defineSettingsFrontComponent()`](/l/ja/developers/extend/apps/layout/front-components#custom-settings-component) を使用してフロントコンポーネントを宣言します。 アプリごとに 1 つのみ許可されています。 システム管理のセクション(自動アップグレード、App URL、接続)は常に表示されます。
## 変数の型
`applicationVariables` と `serverVariables` の両方は、オプションの `type`(および `SELECT` / `MULTI_SELECT` の場合は `options` リスト)を受け取ります。 サポートされている型: `TEXT` (既定), `BOOLEAN`, `NUMBER`, `NUMERIC`, `DATE`, `DATE_TIME`, `SELECT`, `MULTI_SELECT`, `ARRAY`, `RAW_JSON`, `RICH_TEXT`。
```ts src/application-config.ts
import { defineApplication, FieldType } from 'twenty-sdk/define';
export default defineApplication({
// ...identity, role...
applicationVariables: {
MAX_POSTCARDS: {
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
description: 'Maximum postcards per batch',
type: FieldType.NUMBER,
value: 10,
},
DEFAULT_REGION: {
universalIdentifier: '76c5c321-b6b6-46eb-b4fc-f9f04bb04227',
description: 'Default shipping region',
type: FieldType.SELECT,
options: [
{ label: 'Europe', value: 'eu' },
{ label: 'United States', value: 'us' },
],
value: 'eu',
},
},
});
```
`type` は **表示とバリデーション** のみに影響します。つまり、ワークスペース設定 UI で対応する入力(トグル、数値フィールド、ドロップダウン、日付ピッカー、JSON エディタ など)を選択します。 また、ビルド時に設定を検証できるようにします(たとえば、`SELECT` / `MULTI_SELECT` では空でない `options` を宣言する必要があります)。 これは、値がコードに届く方法を**変更しません**。
値は **常に文字列として注入されます**。これは環境変数の性質によるものです(`process.env.*` は文字列のみです)。 ロジック関数が実行されるとき、エグゼキュータは宣言された `type` に従って各値をシリアライズしながら `process.env` を構築します。そのため、値がどのように設定されたか(マニフェストのデフォルト、設定 UI、あるいは以前のバージョン)に関係なく、文字列形式は一貫したものになります。
| タイプ | `process.env` の文字列 |
| ------------------------------------- | --------------------------------- |
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | 生の値(`"eu"`, `"2026-01-01"`) |
| `BOOLEAN` | `"true"` / `"false"` |
| `NUMBER`, `NUMERIC` | 10進数の文字列(`"10"`, `"2.5"`) |
| `MULTI_SELECT`, `ARRAY` | JSON 配列(`'["email","postcard"]'`) |
| `RAW_JSON`, `RICH_TEXT` | JSON オブジェクト(`'{"retries":3}'`) |
文字列を、想定している型に再度パースしてください:
```ts
const maxCards = Number(process.env.MAX_POSTCARDS); // "10" -> 10
const enabled = process.env.ENABLE_TRACKING === 'true'; // "true" -> true
const channels = JSON.parse(process.env.ENABLED_CHANNELS ?? '[]'); // '["email"]' -> ["email"]
const config = JSON.parse(process.env.PROVIDER_CONFIG ?? '{}'); // '{"retries":3}' -> { retries: 3 }
```
同じことが、`getApplicationVariable('VARIABLE_NAME')` で値を読み取るフロントコンポーネントにも当てはまります。返される値は文字列なので、必要に応じてパースしてください。
## デフォルトの関数ロール
[`defineApplicationRole()`](/l/ja/developers/extend/apps/config/roles) で宣言されたロールは、アプリのロジック関数とフロントコンポーネントがアクセスできる内容を制御します。
* ロジック関数に注入される実行時トークンは、このロールに基づいて生成されます。 人物として動作する呼び出しは、その人物が実行できる範囲によってさらに制限されるため、どちらか一方の権限を超えることはありません。 [呼び出しで使用されるアクセス権](/l/ja/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses)を参照してください。
* 型付き API クライアントは、そのロールに付与された権限に制限されます。
* 最小権限の原則に従い、関数に必要な権限のみを宣言してください。
新しいアプリをスキャフォルドすると、CLI は `src/roles/default-role.ts` にスターターロールファイルを作成します。 詳細は [Roles & Permissions](/l/ja/developers/extend/apps/config/roles) を参照してください。
## マーケットプレイスのメタデータ
アプリを[公開](/l/ja/developers/extend/apps/operations/publishing)する予定がある場合、これらの任意フィールドでマーケットプレイスでの表示方法を制御できます:
| フィールド | 説明 |
| ------------------ | -------------------------------------------------------------------------------- |
| `author` | 作成者または会社名 |
| `category` | マーケットプレイスのフィルタリング用のアプリカテゴリ |
| `logo` | `public/`にバンドルされているアプリのロゴのパス(例:`public/logo.png`) |
| `galleryImages` | `public/`にバンドルされているギャラリー画像パスの配列(例:`public/screenshot-1.png`) |
| `aboutDescription` | 「About」タブ向けの詳細な Markdown 説明。 省略した場合、マーケットプレイスは npm のパッケージにある `README.md` を使用します。 |
| `websiteUrl` | 自社ウェブサイトへのリンク |
| `termsUrl` | 利用規約へのリンク |
| `emailSupport` | サポート用メールアドレス |
| `issueReportUrl` | 課題トラッカーへのリンク |
<Note>
`logoUrl`と`screenshots`は、`logo`と`galleryImages`の別名は非推奨です。 外部絶対URL (`http://`または`https://`) はこれらのフィールドでサポートされていません: ビルド時に警告が表示されます。 アプリの `public/` フォルダに画像を束ねます。
</Note>

View File

@@ -0,0 +1,188 @@
---
title: インストールフック
description: インストール、アップグレード、アンインストールのライフサイクル中にロジックを実行し、データのシード、レコードのバックアップ、アップグレードの検証、外部リソースのクリーンアップを行います。
icon: wrench
---
インストールフックは、インストール、アップグレード、またはアンインストールのライフサイクル中に実行される特別なロジック関数です。 これらは通常の[ロジック関数](/l/ja/developers/extend/apps/logic/logic-functions)と同じハンドラーランタイムを共有しますが、独自の define 関数で宣言され、通常のトリガーモデル (HTTP、cron、データベースイベント) の外側で動作します。 インストールフックは `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — 新規インストールでは `previousVersion` は `undefined`) を受け取り、アンインストールフックは `UninstallPayload` (`{ version?: string }` — 削除されるバージョン) を受け取ります。
各アプリは、各フック (プレインストール、ポストインストール、アンインストール) を **最大 1 つまで** 定義できます。 いずれかが複数検出された場合、マニフェストのビルドはエラーになります。
```
┌─────────────────────────────────────────────────────────────┐
│ 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` が有効な場合はいずれのフックもアップグレード時に再実行されます。
* 通常のロジック関数の環境(`APPLICATION_ID`、`APP_ACCESS_TOKEN`、`API_URL`)が注入されるため、アプリのトークンを使って Twenty API を呼び出せます。
* フックはビルド時に自動的にアプリケーションマニフェストにアタッチされます(`preInstallLogicFunction` / `postInstallLogicFunction`)。[`defineApplication()`](/l/ja/developers/extend/apps/config/application) 内で参照する必要はありません。
* デフォルトの `timeoutSeconds` は 300 に設定されており、データシーディングのような長めのセットアップ作業を許容します。
* **開発モードでは実行されません**: `yarn twenty dev` はインストールフローをスキップしてファイルを直接同期するため、フックはそこで一切実行されません。 代わりに手動でトリガーしてください:
```bash filename="Terminal"
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
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="ワークスペースのメタデータマイグレーションが適用される前に実行されます">
メタデータマイグレーションの前、**以前の**スキーマに対して実行されます — マイグレーションで失われるデータをバックアップしたり、リスクの高いアップグレードを拒否したりするのに適した場所です。 実行前に、サーバーは純粋に追加のみの「簡易同期」を実行し、新しいバージョンのプレインストール関数だけを登録します。あなたのハンドラーが実行される際には、それ以外 — 以前のバージョンのオブジェクト、フィールド、データ — には一切手を触れません。
プレインストールは常に**同期的**であり、インストールをブロックします。 ハンドラーがスローした場合、スキーマ変更が行われる前にインストールは中止され、ワークスペースは一貫した状態のまま前のバージョンに留まります。 これは意図的な設計です。プレインストールは、リスクの高いアップグレードを拒否できる最後の機会です。
例 — マイグレーションでレガシーフィールドが削除される前に、その値をコピーする:
```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<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 リソースのプロビジョニング解除、残っているボットの削除、webhook の失効などです。
注記:
* このフックはベストエフォートで動作します。同期的に実行されますが、失敗してもログに記録されるだけであり、**アンインストールをブロックすることは決してありません**。クリーンアップ処理が原因でアプリを削除できなくなってはなりません。
* これは `UninstallPayload` (`{ version?: string }` — 削除されるバージョン) を受け取ります。
* 新規インストールの失敗によりロールバックされた場合には実行されません。アプリのインストールが最後まで完了していないためです。
* このフックは、アプリが削除された後には実行できないため、アプリデータ (レコードに保存されたボット ID など) に依存する外部クリーンアップ処理は、外部のスケジュールジョブではなく、ここに実装する必要があります。
* インストールフックと同様に、これは **dev モードでは実行されません**。代わりに手動でトリガーしてください:
```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<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,
});
```

View File

@@ -0,0 +1,56 @@
---
title: 概要
description: アプリ自体の設定 — アプリのアイデンティティ、デフォルト権限、およびインストール時に実行される内容を構成します。
icon: screwdriver-wrench
---
Twenty アプリの **config layer** は、アプリのアイデンティティ、保持する権限、インストールやアップグレード時に実行されるコードなど、アプリを *プラットフォームに対して* 説明するものです。 これらの宣言は新しいデータ構造や実行時の動作を追加するものではなく、アプリが *何者であるか*、そして *どのようにセットアップするか* を Twenty に伝える役割を持ちます。
```text
┌────────────────────────────────────────────────────────┐
│ Application — identity, default role, variables, │
│ marketplace metadata │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Role — what the app's logic functions can read │ │
│ │ and write (referenced by Application) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ (at install / upgrade time)
┌──────────────────────────────────┐
│ Pre-install hook │ before metadata migration
└──────────────────────────────────┘
┌──────────────────────────────────┐
│ Post-install hook │ after metadata migration
└──────────────────────────────────┘
│
▼ (at uninstall time)
┌──────────────────────────────────┐
│ Uninstall hook │ before app removal
└──────────────────────────────────┘
```
## このセクションについて
<CardGroup cols={2}>
<Card title="アプリケーション設定" icon="rocket" href="/l/ja/developers/extend/apps/config/application">
`defineApplication` — アイデンティティ、デフォルトロール、変数、マーケットプレイスのメタデータ。
</Card>
<Card title="ロールと権限" icon="shield-halved" href="/l/ja/developers/extend/apps/config/roles">
`defineRole` — アプリのロジック関数が読み書きできる内容を宣言します。
</Card>
<Card title="インストールフック" icon="wrench" href="/l/ja/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction`、`definePostInstallLogicFunction`、および `defineUninstallLogicFunction` — データのバックアップ、デフォルト値の投入、アップグレードの検証、アンインストール時のクリーンアップを行います。
</Card>
</CardGroup>
## 各要素同士の関係
* **アプリケーション** がエントリポイントです。 すべてのアプリには `defineApplication()` 呼び出しが 1 つだけ存在し、その呼び出しは 1 つの **ロール** をデフォルトとして指します。
* **ロール** は、アプリのロジック関数とフロントコンポーネントが読み書きできる内容を制御します。 最小権限の原則に従い、コードが実際に必要とする権限だけを付与してください。
* **インストールフック** はインストールまたはアップグレード時に実行されます。メタデータのマイグレーション前にプレインストールが実行されるため(リスクの高いアップグレードを拒否できます)、マイグレーション後にポストインストールが実行され(新しいスキーマに対してデフォルトデータを投入できます)。 アンインストールフックはアプリが削除される直前に実行されるため、アプリのデータがまだ読み取り可能なうちに外部リソースをクリーンアップできます。
<Note>
インストールフックは [logic function](/l/ja/developers/extend/apps/logic/logic-functions) ランタイムを共有します。つまり、同じハンドラーシグネチャ、同じ環境変数、同じ型付き API クライアントを使用します。ただし、独自の define 関数で宣言され、通常のトリガーモデル(HTTP、cron、データベースイベント)とは別に存在します。
</Note>

View File

@@ -0,0 +1,67 @@
---
title: 公開アセット
description: 静的ファイル(画像、アイコン、フォントなど)を、public/ フォルダーを通じてアプリと一緒に配信します。
icon: folder-open
---
アプリのルートにある `public/` フォルダーには、実行時に必要な画像、アイコン、フォントなどの静的ファイルが格納されます。 これらのファイルはビルドに自動的に含まれ、開発モード中に同期され、サーバーにアップロードされます。
`public/` に配置されたファイルは次のとおりです:
* **公開アクセス可能** — サーバーに同期されると、アセットはパブリック URL で配信されます。 アクセスには認証は不要です。
* **フロントコンポーネントで利用可能** — アセットの URL を使用して、React コンポーネント内に画像、アイコン、その他のメディアを表示します。
* **ロジック関数で利用可能** — メール、API レスポンス、その他のサーバーサイドロジックでアセットの URL を参照します。
* **マーケットプレイスのメタデータに使用** — `logo`と`galleryImages`フィールドの`defineApplication()`参照ファイルのこのフォルダ(例:`public/logo.png`)。 アプリを公開すると、これらがマーケットプレイスに表示されます。 外部の絶対URLはこれらのフィールドでは無視されます — 代わりに`public/`で画像をバンドルします。 各画像のサイズは10 MBを超えてはいけません。
* **開発モードで自動同期** — `public/` のファイルを追加、更新、削除すると、自動的にサーバーへ同期されます。 再起動は不要です。
* **ビルドに含まれる** — `yarn twenty dev:build` はすべての公開アセットを配布物にバンドルします。
## `getPublicAssetUrl` で公開アセットにアクセスする
`twenty-sdk` の `getPublicAssetUrl` ヘルパーを使用して、`public/` ディレクトリ内のファイルのフル URL を取得します。 これはロジック関数とフロントコンポーネントの両方で機能します。
**ロジック関数内:**
```ts src/logic-functions/send-invoice.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const handler = async (): Promise<any> => {
const logoUrl = getPublicAssetUrl('logo.png');
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
// Fetch the file content (no auth required — public endpoint)
const response = await fetch(invoiceUrl);
const buffer = await response.arrayBuffer();
return { logoUrl, size: buffer.byteLength };
};
export default defineLogicFunction({
universalIdentifier: 'a1b2c3d4-...',
name: 'send-invoice',
description: 'Sends an invoice with the app logo',
timeoutSeconds: 10,
handler,
});
```
**フロントコンポーネント内:**
```tsx src/front-components/company-card.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const CompanyCard = () => {
const logoUrl = getPublicAssetUrl('logo.png');
return <img src={logoUrl} alt="App logo" />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'company-card',
component: CompanyCard,
});
```
`path` 引数はアプリの `public/` フォルダーからの相対パスです。 `getPublicAssetUrl('logo.png')` と `getPublicAssetUrl('public/logo.png')` のどちらも同じ URL に解決されます—`public/` プレフィックスが付いている場合は自動的に取り除かれます。

View File

@@ -0,0 +1,203 @@
---
title: 役割と権限
description: アプリのロジック関数とフロントコンポーネントが読み取りおよび書き込みできるオブジェクトとフィールドを宣言します。
icon: shield-halved
---
**ロール**とは権限セットのことであり、アプリが読み取りまたは書き込みできるオブジェクト、参照できるフィールド、および使用できるプラットフォームレベルの機能を定義します。 すべてのアプリのロジック関数とフロントコンポーネントは、`defineApplicationRole()` でマークされたロールの権限を継承します(下記の[The default function role](#the-default-function-role)を参照してください)。
```ts src/roles/restricted-company-role.ts
import {
defineRole,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
SystemPermissionFlag,
} from 'twenty-sdk/define';
export default defineRole({
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
label: 'My new role',
description: 'A role that can be used in your workspace',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
fieldPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS],
});
```
## 行レベルセキュリティ
オブジェクトおよびフィールド権限は、ロールが操作できる *オブジェクトとフィールド* を決定します。 **行レベル
権限述語** はさらに進んで、ロールが閲覧および操作できる *レコード* を決定します。たとえば、各外部ユーザーが自分自身のレコードだけを閲覧できるセルフサービスロールのようなケースです。
ロールに対して `rowLevelPermissionPredicates` を使って述語を宣言します。 マニフェストの他の要素と同様に、各述語はそれ自身の `universalIdentifier` を持ち、オブジェクトおよびフィールドをそれぞれの `universalIdentifier`、`operand`、そして(オプションで)クエリ時に値が注入される workspaceMember フィールドによって参照します。これにより、「レコードの所有者リレーションが現在のワークスペースメンバー **である**」ことを表現できます。
```ts src/roles/partner-role.ts
import {
defineRole,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/account-owner.field';
export default defineRole({
universalIdentifier: 'c3c1dc2e-1a08-4de5-abb7-2139b3d99343',
label: 'Partner',
description: 'External partner — sees only its own records',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd0f0c1a2-3b4c-4d5e-8f60-111111111111',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier: ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier,
},
],
});
```
述語はマニフェスト内に含まれて出荷されるため、インストールやアップグレードのたびに、ロールと一緒にまとめて作成・更新・削除されます。同期を保つための、別個のポストインストール手順は不要です。
### 述語とグループの組み合わせ
デフォルトでは、ロールの述語は `AND` で組み合わされます。 それらの一部を `OR` で組み合わせたり(あるいはロジックをネストしたり)するには、`rowLevelPermissionPredicateGroups` エントリを宣言し、各述語から
`predicateGroupUniversalIdentifier` を通じてそのエントリを参照させます。 このロールでは、パートナーが、自分が**所有している**か、あるいは**連絡窓口**となっている Opportunity を閲覧できます:
```ts src/roles/partner-opportunities-role.ts
import {
defineRole,
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
const OPPORTUNITY = STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity;
const CURRENT_MEMBER =
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier;
export default defineRole({
universalIdentifier: 'b2a1c0d9-8e7f-4a6b-9c5d-222222222222',
label: 'Partner (opportunities)',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
canReadObjectRecords: true,
},
],
rowLevelPermissionPredicateGroups: [
{
universalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd4c3b2a1-0e9f-4c8b-9e7d-444444444444',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier: OPPORTUNITY.fields.owner.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
{
universalIdentifier: 'e5d4c3b2-1f0e-4d9c-8f8e-555555555555',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier:
OPPORTUNITY.fields.pointOfContact.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
],
});
```
注記:
* すべての述語とグループに安定した `universalIdentifier`(任意の uuid)を付与してください。これはアップグレードにまたがってエンティティを特定するキーとなり、述語はこの識別子でグループを参照します。
* 述語は、あなたのアプリが所有するオブジェクトやフィールド、または Twenty の標準オブジェクトを参照できます。
* 行レベルセキュリティは、それを含むプランのワークスペースに対して適用されます。その他のプランでも述語は引き続き同期されますが、単に適用されないだけです。
## デフォルトの関数ロール
新しいアプリをスキャフォルドすると、CLI は `defineApplicationRole()` で宣言されたデフォルトのロールファイルを作成します。
```ts src/roles/default-role.ts
import { defineApplicationRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagUniversalIdentifiers: [],
});
```
`defineApplicationRole()` は `defineRole()` の軽量なラッパーであり、インストール時にアプリケーションのデフォルトとして使用されるロールをフラグ付けします。 検証は `defineRole` と同一ですが、ビルドパイプラインがその `universalIdentifier` をアプリケーションマニフェストの `defaultRoleUniversalIdentifier` に自動的に関連付けるため、[`defineApplication`](/l/ja/developers/extend/apps/config/application) から参照する必要はありません。
注記:
* 各アプリにつき `defineApplicationRole(...)` は **1 つだけ** 許可されます。2 つ以上見つかった場合、マニフェストのビルドは失敗します。
* アプリが提供する**追加**ロールには、`defineApplicationRole()` ではなく `defineRole()` を使用してください。
* `defineApplication()` で `defaultRoleUniversalIdentifier` を明示的に設定することは後方互換性のため引き続きサポートされていますが、非推奨であり、代わりに `defineApplicationRole()` の使用が推奨されます。
## ベストプラクティス
* スキャフォールドされたロールから開始し、徐々に制限を強めてください。デフォルトでは幅広い読み取りアクセスが付与されていますが、本番環境でこれをそのまま使いたいケースはほとんどありません。
* `objectPermissions` と `fieldPermissions` を、関数が実際に必要とするオブジェクトとフィールドだけに置き換えてください。
* `permissionFlagUniversalIdentifiers` はプラットフォームレベルの機能へのアクセスを制御します。 できるだけ最小限に保ってください。
* 動作例を参照してください。[`examples/hello-world/src/roles/default-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/hello-world/src/roles/default-role.ts)。