メインコンテンツへスキップ
ロジック関数は、Twenty プラットフォーム上で実行されるサーバーサイドの TypeScript 関数です。 HTTPリクエスト、cronスケジュール、またはデータベースイベントでトリガーでき、AIエージェント向けのツールとして公開することもできます。
各関数ファイルは、ハンドラーと任意のトリガーを含む設定を defineLogicFunction() でエクスポートします。
src/logic-functions/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async (params: RoutePayload) => {
  const client = new CoreApiClient();
  const body = (params.body ?? {}) as { name?: string };
  const name = body.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world';

  const result = await client.mutation({
    createPostCard: {
      __args: { data: { name } },
      id: true,
      name: true,
    },
  });
  return result;
};

export default defineLogicFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  name: 'create-new-post-card',
  timeoutSeconds: 2,
  handler,
  httpRouteTriggerSettings: {
    path: '/post-card/create',
    httpMethod: 'POST',
    isAuthRequired: true,
  },
  /*databaseEventTriggerSettings: {
    eventName: 'people.created',
  },*/
  /*cronTriggerSettings: {
    pattern: '0 0 1 1 *',
  },*/
});
利用可能なトリガーの種類:
  • httpRoute/s/ エンドポイント配下で、HTTP のパスとメソッドで関数を公開します:
例:path: '/post-card/create'https://your-twenty-server.com/s/post-card/create で呼び出せます
(ヘッドレスの)フロントコンポーネントからルートトリガー型ロジック関数を呼び出す方法については、ロジック関数を呼び出すを参照してください。
  • cron: CRON 式を使用してスケジュールで関数を実行します。
  • databaseEvent: ワークスペースのオブジェクトのライフサイクルイベントで実行されます。 イベント操作が updated の場合、監視する特定のフィールドを updatedFields 配列で指定できます。 未定義または空のままにすると、任意の更新でも関数がトリガーされます。
例:person.updated*.createdcompany.*
  • serverRoute: 1 つの登録スコープの HTTP ルートを公開します。 resolver 関数(serverRouteTriggerSettings で宣言)は、オーナーワークスペースで実行され、ディスパッチ先のターゲットワークスペースとターゲットロジック関数の両方を返します。その後、プラットフォームはその target 関数を実行し、そのレスポンスを返します。 サーバールートトリガー を参照してください。
CLI を使用して、関数を手動で実行することもできます:
yarn twenty dev:function:exec -n create-new-post-card -p '{"key": "value"}'
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
ログは次のコマンドで監視できます:
yarn twenty dev:function:logs

ルートトリガーのペイロード

ルートトリガーがロジック関数を呼び出すと、 AWS HTTP API v2 形式 に従う RoutePayload オブジェクトを受け取ります。 RoutePayload 型を twenty-sdk/logic-function からインポートします:
import type { RoutePayload } from 'twenty-sdk/logic-function';

const handler = async (event: RoutePayload) => {
  const { headers, queryStringParameters, pathParameters, body } = event;
  const { method, path } = event.requestContext.http;

  return { message: 'Success' };
};
RoutePayload 型は次の構造になっています:
プロパティタイプ説明
headersRecord\<string, string | undefined>HTTP ヘッダー (forwardedRequestHeaders に列挙されたもののみ)下記のセクションを参照
queryStringParametersRecord\<string, string | undefined>クエリ文字列パラメーター (複数の値はカンマで連結)/users?ids=1&ids=2&ids=3&name=Alice -> { ids: '1,2,3', name: 'Alice' }
pathParametersRecord\<string, string | undefined>ルートパターンから抽出されたパスパラメーター/users/:id, /users/123 -> { id: '123' }
bodyobject | null解析済みのリクエストボディ (JSON){ id: 1 } -> { id: 1 }
rawBodystring | undefinedJSON 解析前の元の UTF-8 リクエストボディ。 HMAC 方式の Webhook 署名を検証する際に役立ちます(例: GitHub の X-Hub-Signature-256、Stripe)。 ランタイムがそれを保持しなかった場合は undefined です。
isBase64Encodedbooleanbody が base64 エンコードされているかどうか
requestContext.http.methodstringHTTP メソッド (GET, POST, PUT, PATCH, DELETE)
requestContext.http.pathstring生のリクエストパス

forwardedRequestHeaders

デフォルトでは、セキュリティ上の理由から、受信リクエストの HTTP ヘッダーはロジック関数に渡されません。 特定のヘッダーにアクセスするには、forwardedRequestHeaders 配列に明示的に列挙してください:
export default defineLogicFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  name: 'webhook-handler',
  handler,
  httpRouteTriggerSettings: {
    path: '/webhook',
    httpMethod: 'POST',
    isAuthRequired: false,
    forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
  },
});
ハンドラー内では、転送されたヘッダーに次のようにアクセスします:
const handler = async (event: RoutePayload) => {
  const signature = event.headers['x-webhook-signature'];
  const contentType = event.headers['content-type'];

  // Validate webhook signature...
  return { received: true };
};
ヘッダー名は小文字に正規化されます。 小文字のキーを使用してアクセスしてください(例:event.headers['content-type'])。

カスタム HTTP レスポンス

デフォルトでは、ハンドラーからプレーンな値を返すと、それが 200 レスポンスとして送信されます(オブジェクトの場合は JSON、文字列の場合は text/plain)。 ステータスコードとレスポンスヘッダーを制御するには、twenty-sdk/logic-function から Response を返します。
import { Response } from 'twenty-sdk/logic-function';

const handler = async (event: RoutePayload) => {
  return new Response('<h1>Hello</h1>', {
    status: 201,
    headers: { 'content-type': 'text/html' },
  });
};
セキュリティ上の理由から、レスポンスヘッダーは許可リストに限定されています。 リストに含まれていないヘッダー(例: Set-CookieAccess-Control-Allow-Origin などの CORS ヘッダー、カスタム X-* ヘッダー)は、レスポンスが送信される前に暗黙的に削除されます。 許可されているレスポンスヘッダーは次のとおりです。
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
ステータスコードは、有効な HTTP ステータスコード(100 から 599 の間)でなければなりません。 レスポンスヘッダー名は、大文字と小文字を区別せずに照合されます。

サーバールートトリガー

httpRouteTriggerSettings/s/ 配下に関数を公開し、リクエストホストからワークスペースを特定します。これは、各ワークスペースが独自ドメインを持つ場合に機能します。 しかし、サードパーティプロバイダーは、すべてのテナントのイベントを 1 つの URL に配信します。 その場合は、serverRouteTriggerSettings を使用します。トリガーには 2 つの構成要素があります:
  1. resolver ロジック関数 — serverRouteTriggerSettings で宣言される — は、オーナーワークスペース(アプリケーション登録を所有するワークスペース)で実行されます。 受信リクエストを検査し、{ workspaceId, targetLogicFunctionUniversalIdentifier, payload? } を返し、ターゲットのワークスペースと関数の 両方 を選択します。 resolver は単一の認可ポイントであり、URL には resolver の識別子のみが含まれます。 ここがリクエスト署名を検証するための推奨箇所です。resolver は副作用が発生する前に実行され、元の rawBody と転送されたヘッダーにアクセスでき、ターゲットに一切触れずにリクエストを拒否できます。
  2. target ロジック関数 — 通常のワークスペース単位のロジック関数 — は、その後、resolver によって解決されたワークスペースで、resolver が返したペイロード(resolver が変換しなかった場合は元のリクエストペイロード)を使って実行されます。 その戻り値が HTTP レスポンスになります。
src/logic-functions/resolve-server-route.logic-function.ts
import { createHmac, timingSafeEqual } from 'crypto';
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';

// Runs in the owner workspace. Verifies the request signature, picks
// which target function should handle the event, and returns the
// workspace + target the platform should dispatch to.
const handler = async (event: RoutePayload) => {
  // Fail closed if the secret isn't configured — never fall back to an
  // empty key, which would let any caller forge a matching signature.
  const secret = process.env.GITHUB_WEBHOOK_SECRET;

  if (!secret) {
    throw new Error('GITHUB_WEBHOOK_SECRET is not configured');
  }

  const signature = event.headers['x-hub-signature-256'] ?? '';
  const expected =
    'sha256=' +
    createHmac('sha256', secret).update(event.rawBody ?? '').digest('hex');

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);

  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    throw new Error('invalid signature');
  }

  const body = (event.body ?? {}) as {
    metadata?: { twentyWorkspaceId?: string };
    type?: string;
  };

  return {
    workspaceId: body.metadata?.twentyWorkspaceId ?? '',
    // Route different event types to different target functions.
    targetLogicFunctionUniversalIdentifier:
      body.type === 'invoice.paid'
        ? 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10' // handle-invoice-paid
        : 'd5f3b0c2-8e5f-5d0b-a0c3-3f2e7b5d9f21', // handle-other-event
  };
};

export default defineLogicFunction({
  universalIdentifier: 'b3c2f0a1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
  name: 'resolve-server-route',
  handler,
  serverRouteTriggerSettings: {
    forwardedRequestHeaders: ['x-hub-signature-256'],
  },
});
src/logic-functions/handle-invoice-paid.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';

// Runs in the resolved workspace. The resolver has already authenticated
// the request, so this handler can focus on the actual work.
const handler = async (event: RoutePayload) => {
  // ...handle the verified event
  return { received: true };
};

export default defineLogicFunction({
  universalIdentifier: 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
  name: 'handle-invoice-paid',
  handler,
});
エンドポイントには次の URL でアクセスできます:
POST https://your-twenty-server.com/webhooks/server/:resolverLogicFunctionUniversalIdentifier
識別子は、マニフェストに記載されている resolver の universalIdentifier です。 その URL をプロバイダーに登録します。
アプリケーションは所有者ワークスペースでクレームされ、インストールされている必要があります。 リゾルバーは 所有者ワークスペース(アプリケーション登録を所有しているワークスペース)上で実行されるため、サーバールートトリガーが動作するのは、アプリケーションがクレームされている、つまり所有者ワークスペースを持っていること かつ そのアプリケーションが 所有者ワークスペースにインストールされている 場合のみです。 この2つの条件がどちらも満たされるまでは、リゾルバーを実行する場所が存在しないため、ルートをディスパッチできません。 したがって、serverRouteTriggerSettings ロジック関数を公開するアプリケーションは、所有者ワークスペースでクレームされインストールされるまで、マーケットプレイスに掲載することはできません。
Resolver の契約。SDK の LogicFunctionConfig 型は、コンパイル時にこれを強制します。serverRouteTriggerSettings を設定するとすぐに、ハンドラーは { workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object }(またはその Promise)を返すように制約されます。 workspaceId は、ターゲット関数がインストールされているワークスペースである必要があります。そうでない場合、リクエストは 404 で拒否されます。
フィールドタイプノート
workspaceIdstringターゲットが実行されるワークスペースの UUID。
targetLogicFunctionUniversalIdentifierstringそのワークスペースで呼び出すロジック関数の universalIdentifier
payloadobject(オプション)設定された場合、ターゲットに送信されるリクエストボディを置き換えます。
署名の検証はあなたの責任です — resolver 内で検証してください。 プラットフォームはリクエスト署名を検証しません。 resolver はそれを行う推奨箇所です。最初に実行され、event.rawBody と、forwardedRequestHeaders に指定したヘッダーにアクセスできます。また、エラーをスローする(または一致しない workspaceId を返す)ことで、ターゲットが呼び出される前にディスパッチを停止できます。 代わりに検証処理を target 側に押し下げる場合、target は rawBody とヘッダーを失わないよう注意する必要があります。つまり、resolver は payload を返してはいけません。 副作用を伴う処理の前に必ず検証を行い、コンスタントタイム比較を使用してください。
リクエスト署名については、ほとんどのプロバイダーが HMAC-SHA256 で署名します。異なるのはヘッダー名、ダイジェストのエンコーディング、および署名対象となるペイロード文字列です。 いくつかの例を示します。
プロバイダー転送するヘッダー署名対象文字列ダイジェスト
Svix (Recall, Resend, Clerk)webhook-id, webhook-timestamp, webhook-signature{id}.{timestamp}.{rawBody}base64(シークレットは whsec_ を取り除いた後の文字列を base64 として扱います)
Stripestripe-signature{timestamp}.{rawBody}hex
GitHubx-hub-signature-256{rawBody}hex(先頭に sha256= が付与されます)
Shopifyx-shopify-hmac-sha256{rawBody}base64
Slackx-slack-signature, x-slack-request-timestampv0:{timestamp}:{rawBody}hex(先頭に v0= が付与されます)
上記の resolver の例では、すでに GitHub の HMAC-SHA256 フローを示しています。連携するプロバイダーに合わせて、ヘッダー名、ダイジェストのエンコーディング、および署名対象となるペイロード文字列を調整してください。
target は同期的に実行され、その戻り値が HTTP レスポンスになるため、呼び出し元はあなたのステータスコードを確認し、2xx 以外の場合にリトライできます。 両方のハンドラーを高速に保ってください。Slack など一部のプロバイダーは数秒でタイムアウトします。 resolver はパブリックエンドポイントとして到達可能であるため、エッジでレート制限をかけて保護してください。

データベースイベントトリガーのペイロード

データベースイベントトリガーがロジック関数を呼び出すと、変更されたレコードごとに 1 つの DatabaseEventPayload を受け取ります。 このペイロードは、ソースワークスペースとオブジェクトに関するメタデータを、レコードレベルのイベントと組み合わせたものです。
import type {
  DatabaseEventPayload,
  ObjectRecordCreateEvent,
  ObjectRecordDestroyEvent,
  ObjectRecordUpdateEvent,
} from 'twenty-sdk/logic-function';

type Person = {
  id: string;
  emails?: { primaryEmail?: string };
};
ペイロードには次のものが含まれます:
プロパティ説明
nameperson.updated などのイベント名。
workspaceIdイベントが発生したワークスペース。
objectMetadata変更されたオブジェクトのメタデータ。
recordId変更されたレコードの ID。
userId, userWorkspaceId, workspaceMemberIdイベントがワークスペースユーザーによって引き起こされた場合のアクターに関するフィールド。
propertiesイベントのレコードデータ。操作に応じて beforeafterdiffupdatedFields が含まれます。
イベントレコードデータ
person.createdevent.properties.after
person.updatedevent.properties.before, event.properties.after, event.properties.diff, event.properties.updatedFields
person.destroyedevent.properties.before
ソフトデリートの場合、レコードの deletedAt フィールドが変化するため、.deleted はアップデート時と同じ形式になります。 完全な削除の場合は .destroyed を使用します。
databaseEventTriggerSettings.updatedFields は、どの更新イベントで関数をトリガーするかをフィルタリングします。 event.properties.updatedFields は、現在のイベントで実際にどのフィールドが変更されたかを示します。
作成イベントの例:
type PersonCreatedEvent = DatabaseEventPayload<
  ObjectRecordCreateEvent<Person>
>;

const handler = async (event: PersonCreatedEvent) => {
  const person = event.properties.after;

  return {
    personId: event.recordId,
    email: person.emails?.primaryEmail,
  };
};
更新イベントの例:
type PersonUpdatedEvent = DatabaseEventPayload<
  ObjectRecordUpdateEvent<Person>
>;

const handler = async (event: PersonUpdatedEvent) => {
  const { before, after, diff, updatedFields } = event.properties;

  return {
    personId: event.recordId,
    updatedFields,
    previousEmail: before.emails?.primaryEmail,
    currentEmail: after.emails?.primaryEmail,
    emailDiff: diff.emails,
  };
};
メール更新時のみトリガーする例:
export default defineLogicFunction({
  ...,
  databaseEventTriggerSettings: {
    eventName: 'person.updated',
    updatedFields: ['emails'],
  },
});
削除イベントの例:
type PersonDestroyedEvent = DatabaseEventPayload<
  ObjectRecordDestroyEvent<Person>
>;

const handler = async (event: PersonDestroyedEvent) => {
  const personBeforeDestroy = event.properties.before;

  return {
    personId: event.recordId,
    email: personBeforeDestroy.emails?.primaryEmail,
  };
};

関数を AI ツールまたはワークフロー アクションとして公開する

ロジック関数は 2 つのサーフェス上で公開でき、それぞれに固有のトリガーがあります:
  • toolTriggerSettings — Twenty の AI 機能(チャット、MCP、関数呼び出し)からその関数を見つけられるようにします。 標準的な JSON Schema を使用します。これは LLM がネイティブに理解する形式です。
  • workflowActionTriggerSettings — ビジュアル ワークフロー ビルダー内のステップとして関数を表示します。 ビルダーが適切なフィールドエディタ、変数ピッカー、ラベルをレンダリングできるよう、Twenty の充実した InputSchema を使用します。
関数は一方、もう一方、またはその両方を選択できます。 これらは cronTriggerSettingsdatabaseEventTriggerSettingshttpRouteTriggerSettings と並列に存在します — 同じパターン、同じ形です。
src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async (params: { companyName: string; domain?: string }) => {
  const client = new CoreApiClient();

  const result = await client.mutation({
    createTask: {
      __args: {
        data: {
          title: `Enrich data for ${params.companyName}`,
          body: `Domain: ${params.domain ?? 'unknown'}`,
        },
      },
      id: true,
    },
  });

  return { taskId: result.createTask.id };
};

export default defineLogicFunction({
  universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
  name: 'enrich-company',
  description: 'Enrich a company record with external data',
  timeoutSeconds: 10,
  handler,
  toolTriggerSettings: {},
});
主なポイント:
  • 関数はサーフェスを混在させることができます — toolTriggerSettingsworkflowActionTriggerSettings の両方を宣言して、チャットおよびワークフロー ビルダーの両方に公開します。
  • toolTriggerSettings.inputSchemaworkflowActionTriggerSettings.inputSchema はいずれも任意です。 省略された場合、マニフェストビルダーはハンドラーのソースコードからそれらを推論します(AI ツールには JSON Schema、ワークフロー アクションには Twenty の InputSchema)。 より豊富な型付けが必要な場合は、明示的に指定してください — たとえば、ワークフロー ビルダー向けに CURRENCYRELATION といった FieldMetadataType に対応したフィールド、または AI エージェントが読み取れる description フィールドを使用する場合など:
export default defineLogicFunction({
  ...,
  toolTriggerSettings: {
    inputSchema: {
      type: 'object',
      properties: {
        companyName: {
          type: 'string',
          description: 'The name of the company to enrich',
        },
        domain: {
          type: 'string',
          description: 'The company website domain (optional)',
        },
      },
      required: ['companyName'],
    },
  },
});
パラメーターを一度だけ宣言して両方のサーフェスで利用できるようにするには、単一の JSON Schema(InputJsonSchema)を定義し、twenty-sdk/logic-functionjsonSchemaToInputSchema を使ってワークフローアクション用に変換します。 toolTriggerSettings.inputSchema は JSON Schema を直接受け取りますが、workflowActionTriggerSettings.inputSchema には Twenty の InputSchema が必要です。
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function';

const inputSchema: InputJsonSchema = {
  type: 'object',
  properties: {
    companyName: { type: 'string', label: 'Company name' },
    domain: { type: 'string', label: 'Domain' },
  },
  required: ['companyName'],
};

export default defineLogicFunction({
  ...,
  toolTriggerSettings: { inputSchema },
  workflowActionTriggerSettings: {
    label: 'Enrich Company',
    icon: 'IconBuilding',
    inputSchema: jsonSchemaToInputSchema(inputSchema),
  },
});
良い description を記述してください。 AI エージェントは、ツールをいつ使用するかを判断するために関数の description フィールドに依存します。 ツールが何を行い、いつ呼び出すべきかを具体的に記述してください。
ランタイムヘルパー。 twenty-sdk/utils は、小さなランタイムヘルパーを再エクスポートすることで、ハンドラーが直接 twenty-shared からインポートする必要がないようにします。 たとえば、isDefined(value)nullundefined の両方に対して false を返します。これを使うと、オプショナルなハンドラー入力を安全に絞り込めます。こうした入力は、型としては T | undefined であっても、実行時には null として渡される場合があります。
import { isDefined } from 'twenty-sdk/utils';

const handler = async (params: { parentMessageId?: string }) => {
  if (isDefined(params.parentMessageId)) {
    // params.parentMessageId is narrowed to string here
  }
};
インストールフック — pre-install と post-install のハンドラー — はこのランタイムを共有しますが、それぞれ独自の define 関数で宣言され、トリガー設定は受け取りません。 definePreInstallLogicFunctiondefinePostInstallLogicFunction については、インストールフック を参照してください。

型付き API クライアント(twenty-client-sdk

twenty-client-sdk パッケージは、ロジック関数やフロントコンポーネントから Twenty API とやり取りするための、型付き GraphQL クライアントを 2 つ提供します。
クライアントインポートエンドポイント自動生成?
CoreApiClienttwenty-client-sdk/core/graphql — ワークスペースデータ(レコード、オブジェクト)はい、開発/ビルド時
MetadataApiClienttwenty-client-sdk/metadata/metadata — ワークスペース設定、ファイルアップロードいいえ、あらかじめビルド済みで提供
CoreApiClient は、ワークスペースデータのクエリと変更のための主要なクライアントです。 yarn twenty dev または yarn twenty dev:build の実行時にワークスペースのスキーマから生成されるため、オブジェクトやフィールドに一致する完全な型付けが行われます。
import { CoreApiClient } from 'twenty-client-sdk/core';

const client = new CoreApiClient();

// Query records
const { companies } = await client.query({
  companies: {
    edges: {
      node: {
        id: true,
        name: true,
        domainName: {
          primaryLinkLabel: true,
          primaryLinkUrl: true,
        },
      },
    },
  },
});

// Create a record
const { createCompany } = await client.mutation({
  createCompany: {
    __args: {
      data: {
        name: 'Acme Corp',
      },
    },
    id: true,
    name: true,
  },
});
このクライアントは selection-set 構文を使用します。フィールドを含めるには true を渡し、引数には __args を使い、リレーションにはオブジェクトをネストします。 ワークスペースのスキーマに基づく完全な補完と型チェックが得られます。
CoreApiClient は開発/ビルド時に生成されます。 先に yarn twenty dev または yarn twenty dev:build を実行せずに使用すると、エラーが発生します。 生成は自動で行われます—CLI がワークスペースの GraphQL スキーマをイントロスペクトし、@genql/cli を使用して型付きクライアントを生成します。

CoreSchema を型注釈に使用する

CoreSchema は、ワークスペースのオブジェクトに対応する TypeScript の型を提供し、コンポーネントの状態や関数パラメーターの型付けに役立ちます:
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
import { useState } from 'react';

const [company, setCompany] = useState<
  Pick<CoreSchema.Company, 'id' | 'name'> | undefined
>(undefined);

const client = new CoreApiClient();
const result = await client.query({
  company: {
    __args: { filter: { position: { eq: 1 } } },
    id: true,
    name: true,
  },
});
setCompany(result.company);
MetadataApiClient は SDK に同梱されており、あらかじめビルド済みです(生成は不要)。 ワークスペースの設定、アプリケーション、ファイルアップロードのために /metadata エンドポイントに対してクエリを実行します。
import { MetadataApiClient } from 'twenty-client-sdk/metadata';

const metadataClient = new MetadataApiClient();

// List first 10 objects in the workspace
const { objects } = await metadataClient.query({
  objects: {
    edges: {
      node: {
        id: true,
        nameSingular: true,
        namePlural: true,
        labelSingular: true,
        isCustom: true,
      },
    },
    __args: {
      filter: {},
      paging: { first: 10 },
    },
  },
});

ファイルのアップロード

MetadataApiClient には、ファイル型フィールドにファイルを添付するための uploadFile メソッドが含まれています:
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import * as fs from 'fs';

const metadataClient = new MetadataApiClient();

const fileBuffer = fs.readFileSync('./invoice.pdf');

const uploadedFile = await metadataClient.uploadFile(
  fileBuffer,                                         // file contents as a Buffer
  'invoice.pdf',                                      // filename
  'application/pdf',                                  // MIME type
  '58a0a314-d7ea-4865-9850-7fb84e72f30b',            // field universalIdentifier
);

console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
パラメータータイプ説明
fileBufferBufferファイルの生データ
filenamestring保存および表示に使用されるファイル名
contentTypestringMIME タイプ(省略時は application/octet-stream がデフォルト)
fieldMetadataUniversalIdentifierstringオブジェクト上のファイルタイプのフィールドの universalIdentifier
主なポイント:
  • フィールドの universalIdentifier(ワークスペース固有の ID ではありません)を使用するため、アップロードコードはアプリがインストールされている任意のワークスペースで動作します。
  • 返される url は、アップロード済みファイルにアクセスするために使用できる署名付き URL です。
コードが Twenty 上で実行される際(ロジック関数やフロントコンポーネント)、プラットフォームは認証情報を環境変数として注入します:
  • TWENTY_API_URL — Twenty API のベース URL
  • TWENTY_APP_ACCESS_TOKEN — アプリケーションのデフォルト関数ロールにスコープされた短命のキー
これらをクライアントに渡す必要はありません — 自動的に process.env から読み取ります。 API キーの権限は、defineApplicationRole() で宣言されたロール(または application-config.tsdefaultRoleUniversalIdentifier で参照されるロール)によって決まります。