Перейти к основному содержанию
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.

Что такое приложения?

Приложения позволяют создавать и управлять настройками Twenty в виде кода. Вместо настройки всего через интерфейс вы определяете модель данных и логические функции в коде — так быстрее создавать, поддерживать и развёртывать в нескольких рабочих пространствах. Что вы можете делать уже сегодня:
  • Определяйте пользовательские объекты и поля в виде кода (управляемая модель данных)
  • Создавайте логические функции с пользовательскими триггерами
  • Определите навыки для ИИ-агентов
  • Развёртывайте одно и то же приложение в нескольких рабочих пространствах

Требования

Начало работы

Создайте новое приложение с помощью официального генератора, затем выполните аутентификацию и начните разработку:
# Создать каркас нового приложения (по умолчанию включает все примеры)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app

# Если вы не используете yarn@4
corepack enable
yarn install

# Аутентифицироваться с помощью вашего API-ключа (вам будет предложено)
yarn twenty auth:login

# Запустить режим разработки: автоматически синхронизирует локальные изменения с вашим рабочим пространством
yarn twenty app:dev
Генератор каркаса поддерживает три режима для управления тем, какие примерные файлы включаются:
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app

# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal

# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
Отсюда вы можете:
# Добавить новую сущность в ваше приложение (с мастером)
yarn twenty entity:add

# Просматривать логи функций вашего приложения
yarn twenty function:logs

# Выполнить функцию по имени
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'

# Выполнить послеустановочную функцию
yarn twenty function:execute --postInstall

# Удалить приложение из текущего рабочего пространства
yarn twenty app:uninstall

# Показать справку по командам
yarn twenty help
Смотрите также: страницы справки CLI для create-twenty-app и twenty-sdk CLI.

Структура проекта (сгенерированного)

Когда вы запускаете npx create-twenty-app@latest my-twenty-app, генератор:
  • Копирует минимальное базовое приложение в my-twenty-app/
  • Добавляет локальную зависимость twenty-sdk и конфигурацию Yarn 4
  • Создаёт файлы конфигурации и скрипты, подключённые к CLI twenty
  • Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, постустановочную функцию), а также примерные файлы в зависимости от выбранного режима создания каркаса
Сгенерированное с помощью каркаса приложение с режимом по умолчанию --exhaustive выглядит так:
my-twenty-app/
  package.json
  yarn.lock
  .gitignore
  .nvmrc
  .yarnrc.yml
  .yarn/
    install-state.gz
  eslint.config.mjs
  tsconfig.json
  README.md
  public/                           # Public assets folder (images, fonts, etc.)
  src/
  ├── application-config.ts           # Required - main application configuration
  ├── roles/
  │   └── default-role.ts               # Default role for logic functions
  ├── objects/
  │   └── example-object.ts             # Example custom object definition
  ├── fields/
  │   └── example-field.ts              # Example standalone field definition
  ├── logic-functions/
  │   ├── hello-world.ts                # Example logic function
  │   └── post-install.ts               # Post-install logic function
  ├── front-components/
  │   └── hello-world.tsx               # Example front component
  ├── views/
  │   └── example-view.ts                # Example saved view definition
  ├── navigation-menu-items/
  │   └── example-navigation-menu-item.ts # Example sidebar navigation link
  └── skills/
      └── example-skill.ts                # Example AI agent skill definition
С --minimal создаются только основные файлы (application-config.ts, roles/default-role.ts и logic-functions/post-install.ts). С --interactive вы выбираете, какие примерные файлы включить. В общих чертах:
  • package.json: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет twenty-sdk, а также скрипт twenty, который делегирует выполнение локальному CLI twenty. Выполните yarn twenty help, чтобы вывести список всех доступных команд.
  • .gitignore: Игнорирует распространённые артефакты, такие как node_modules, .yarn, generated/ (типизированный клиент), dist/, build/, каталоги coverage, файлы журналов и файлы .env*.
  • yarn.lock, .yarnrc.yml, .yarn/: Фиксируют и настраивают используемый в проекте инструментарий Yarn 4.
  • .nvmrc: Фиксирует версию Node.js, ожидаемую проектом.
  • eslint.config.mjs и tsconfig.json: Обеспечивают линтинг и конфигурацию TypeScript для исходников вашего приложения на TypeScript.
  • README.md: Короткий README в корне приложения с базовыми инструкциями.
  • public/: Папка для хранения общедоступных ресурсов (изображений, шрифтов, статических файлов), которые будут отдаваться вашим приложением. Файлы, размещённые здесь, загружаются во время синхронизации и доступны во время выполнения.
  • src/: Основное место, где вы определяете приложение как код

Обнаружение сущностей

SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов export default define<Entity>({...}). Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из twenty-sdk:
Вспомогательная функцияТип сущности
defineObject()Определения пользовательских объектов
defineLogicFunction()Определения логических функций
defineFrontComponent()Определения компонентов фронтенда
defineRole()Определения ролей
defineField()Расширения полей для существующих объектов
defineView()Определения сохранённых представлений
defineNavigationMenuItem()Определения пунктов меню навигации
defineSkill()AI agent skill definitions
Имена файлов заданы гибко. Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона export default define<Entity>({...}). Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, logic-functions/, roles/) — это лишь соглашение для организации кода, а не требование.
Пример обнаруженной сущности:
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';

export default defineObject({
  universalIdentifier: '...',
  nameSingular: 'postCard',
  // ... rest of config
});
Позднее команды добавят больше файлов и папок:
  • yarn twenty app:dev автоматически сгенерирует типизированный клиент API в node_modules/twenty-sdk/generated (типизированный клиент Twenty + типы рабочего пространства).
  • yarn twenty entity:add will add entity definition files under src/ for your custom objects, functions, front components, roles, skills, and more.

Аутентификация

При первом запуске yarn twenty auth:login вам будет предложено указать:
  • URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства)
  • Ключ API
Ваши учётные данные хранятся для каждого пользователя в ~/.twenty/config.json. Вы можете хранить несколько профилей и переключаться между ними.

Управление рабочими пространствами

# Войти в интерактивном режиме (рекомендуется)
yarn twenty auth:login

# Войти в профиль конкретного рабочего пространства
yarn twenty auth:login --workspace my-custom-workspace

# Показать список всех настроенных рабочих пространств
yarn twenty auth:list

# Переключить рабочее пространство по умолчанию (в интерактивном режиме)
yarn twenty auth:switch

# Переключиться на определённое рабочее пространство
yarn twenty auth:switch production

# Проверить текущий статус аутентификации
yarn twenty auth:status
После переключения рабочего пространства с помощью yarn twenty auth:switch все последующие команды по умолчанию будут использовать это рабочее пространство. Вы по-прежнему можете временно переопределить это с помощью --workspace <name>.

Используйте ресурсы SDK (типы и конфигурация)

Пакет twenty-sdk предоставляет типизированные строительные блоки и вспомогательные функции, которые вы используете внутри своего приложения. Ниже — ключевые части, с которыми вы будете работать чаще всего.

Вспомогательные функции

SDK предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в Обнаружение сущностей, вы должны использовать export default define<Entity>({...}), чтобы ваши сущности были обнаружены:
ФункцияНазначение
defineApplication()Настройка метаданных приложения (обязательно, по одному на приложение)
defineObject()Определяет пользовательские объекты с полями
defineLogicFunction()Определение логических функций с обработчиками
defineFrontComponent()Определение фронт-компонентов для настраиваемого интерфейса
defineRole()Настраивает права роли и доступ к объектам
defineField()Расширение существующих объектов дополнительными полями
defineView()Определяйте сохранённые представления для объектов
defineNavigationMenuItem()Определяйте ссылки боковой панели навигации
defineSkill()Define AI agent skills
Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов.

Определение объектов

Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте defineObject() для определения объектов со встроенной валидацией:
// src/app/postCard.object.ts
import { defineObject, FieldType } from 'twenty-sdk';

enum PostCardStatus {
  DRAFT = 'DRAFT',
  SENT = 'SENT',
  DELIVERED = 'DELIVERED',
  RETURNED = 'RETURNED',
}

export default defineObject({
  universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
  nameSingular: 'postCard',
  namePlural: 'postCards',
  labelSingular: 'Post Card',
  labelPlural: 'Post Cards',
  description: 'A post card object',
  icon: 'IconMail',
  fields: [
    {
      universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
      name: 'content',
      type: FieldType.TEXT,
      label: 'Content',
      description: "Postcard's content",
      icon: 'IconAbc',
    },
    {
      universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
      name: 'recipientName',
      type: FieldType.FULL_NAME,
      label: 'Recipient name',
      icon: 'IconUser',
    },
    {
      universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
      name: 'recipientAddress',
      type: FieldType.ADDRESS,
      label: 'Recipient address',
      icon: 'IconHome',
    },
    {
      universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
      name: 'status',
      type: FieldType.SELECT,
      label: 'Status',
      icon: 'IconSend',
      defaultValue: `'${PostCardStatus.DRAFT}'`,
      options: [
        { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
        { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
        { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
        { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
      ],
    },
    {
      universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
      name: 'deliveredAt',
      type: FieldType.DATE_TIME,
      label: 'Delivered at',
      icon: 'IconCheck',
      isNullable: true,
      defaultValue: null,
    },
  ],
});
Основные моменты:
  • Используйте defineObject() для встроенной валидации и лучшей поддержки в IDE.
  • universalIdentifier должен быть уникальным и стабильным между развёртываниями.
  • Каждому полю требуются name, type, label и собственный стабильный universalIdentifier.
  • Массив fields необязателен — вы можете определять объекты без пользовательских полей.
  • Вы можете сгенерировать новые объекты с помощью yarn twenty entity:add, который проведёт вас через настройку имени, полей и связей.
Базовые поля создаются автоматически. Когда вы определяете пользовательский объект, Twenty автоматически добавляет стандартные поля, такие как id, name, createdAt, updatedAt, createdBy, updatedBy и deletedAt. Вам не нужно определять их в массиве fields — добавляйте только свои пользовательские поля. Вы можете переопределить поля по умолчанию, определив поле с тем же именем в массиве fields, но это не рекомендуется.

Конфигурация приложения (application-config.ts)

У каждого приложения есть единственный файл application-config.ts, который описывает:
  • Что это за приложение: идентификаторы, отображаемое имя и описание.
  • Как запускаются его функции: какую роль они используют для прав доступа.
  • (Необязательно) переменные: пары ключ-значение, предоставляемые вашим функциям как переменные окружения.
  • (Необязательно) послеустановочная функция: функция логики, которая запускается после установки приложения.
Используйте defineApplication() для определения конфигурации вашего приложения:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';

export default defineApplication({
  universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
  displayName: 'My Twenty App',
  description: 'My first Twenty app',
  icon: 'IconWorld',
  applicationVariables: {
    DEFAULT_RECIPIENT_NAME: {
      universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
      description: 'Default recipient name for postcards',
      value: 'Jane Doe',
      isSecret: false,
    },
  },
  defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
Заметки:
  • universalIdentifier — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями.
  • applicationVariables становятся переменными окружения для ваших функций (например, DEFAULT_RECIPIENT_NAME доступна как process.env.DEFAULT_RECIPIENT_NAME).
  • defaultRoleUniversalIdentifier должен соответствовать файлу роли (см. ниже).
  • postInstallLogicFunctionUniversalIdentifier (необязательно) указывает на логическую функцию, которая автоматически выполняется после установки приложения. См. Послеустановочные функции.

Роли и разрешения

Приложения могут определять роли, инкапсулирующие права на объекты и действия в вашем рабочем пространстве. Поле defaultRoleUniversalIdentifier в application-config.ts обозначает роль по умолчанию, используемую логическими функциями вашего приложения.
  • Ключ API во время выполнения, подставляемый как TWENTY_API_KEY, получается из этой роли функции по умолчанию.
  • Типизированный клиент будет ограничен правами, предоставленными этой ролью.
  • Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям, и укажите её универсальный идентификатор.
Роль функции по умолчанию (*.role.ts)
Когда вы генерируете новое приложение, CLI также создаёт файл роли по умолчанию. Используйте defineRole() для определения ролей со встроенной валидацией:
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';

export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
  'b648f87b-1d26-4961-b974-0908fd991061';

export default defineRole({
  universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  label: 'Default function role',
  description: 'Default role for function Twenty client',
  canReadAllObjectRecords: false,
  canUpdateAllObjectRecords: false,
  canSoftDeleteAllObjectRecords: false,
  canDestroyAllObjectRecords: false,
  canUpdateAllSettings: false,
  canBeAssignedToAgents: false,
  canBeAssignedToUsers: false,
  canBeAssignedToApiKeys: false,
  objectPermissions: [
    {
      objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
      canReadObjectRecords: true,
      canUpdateObjectRecords: true,
      canSoftDeleteObjectRecords: false,
      canDestroyObjectRecords: false,
    },
  ],
  fieldPermissions: [
    {
      objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
      fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
      canReadFieldValue: false,
      canUpdateFieldValue: false,
    },
  ],
  permissionFlags: [PermissionFlag.APPLICATIONS],
});
Значение universalIdentifier этой роли затем указывается в application-config.ts как defaultRoleUniversalIdentifier. Иными словами:
  • *.role.ts определяет, что может делать роль функции по умолчанию.
  • application-config.ts указывает на эту роль, чтобы ваши функции наследовали её права.
Заметки:
  • Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий.
  • Замените objectPermissions и fieldPermissions на объекты/поля, которые нужны вашим функциям.
  • permissionFlags управляют доступом к возможностям на уровне платформы. Держите их минимальными; добавляйте только то, что нужно.
  • См. рабочий пример в приложении Hello World: packages/twenty-apps/hello-world/src/roles/function-role.ts.

Конфигурация логической функции и точка входа

Каждый файл функции использует defineLogicFunction() для экспорта конфигурации с обработчиком и необязательными триггерами.
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';

const handler = async (params: RoutePayload) => {
  const client = new Twenty(); // generated typed client
  const name = 'name' in params.queryStringParameters
    ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
    : '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,
  triggers: [
    // Public HTTP route trigger '/s/post-card/create'
    {
      universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
      type: 'route',
      path: '/post-card/create',
      httpMethod: 'GET',
      isAuthRequired: false,
    },
    // Cron trigger (CRON pattern)
    // {
    //   universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
    //   type: 'cron',
    //   pattern: '0 0 1 1 *',
    // },
    // Database event trigger
    // {
    //   universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
    //   type: 'databaseEvent',
    //   eventName: 'person.updated',
    //   updatedFields: ['name'],
    // },
  ],
});
Распространённые типы триггеров:
  • route: Публикует вашу функцию по HTTP-пути и методу под конечной точкой /s/:
например, path: '/post-card/create', -> вызов по адресу <APP_URL>/s/post-card/create
  • cron: Запускает вашу функцию по расписанию с использованием выражения CRON.
  • databaseEvent: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — updated, можно указать конкретные поля для отслеживания в массиве updatedFields. Если оставить не заданным или пустым, любое обновление будет вызывать функцию.
например, person.updated
Заметки:
  • Массив triggers необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями.
  • Вы можете сочетать несколько типов триггеров в одной функции.

Послеустановочные функции

Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства. Когда вы создаёте каркас нового приложения с помощью create-twenty-app, для вас генерируется постустановочная функция по пути src/logic-functions/post-install.ts:
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';

export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';

const handler = async (): Promise<void> => {
  console.log('Post install logic function executed successfully!');
};

export default defineLogicFunction({
  universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
  name: 'post-install',
  description: 'Runs after installation to set up the application.',
  timeoutSeconds: 300,
  handler,
});
Функция подключается к вашему приложению посредством ссылки на её универсальный идентификатор в application-config.ts:
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';

export default defineApplication({
  // ...
  postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI:
yarn twenty function:execute --postInstall
Основные моменты:
  • Постустановочные функции — это стандартные логические функции: они используют defineLogicFunction() как и любые другие функции.
  • Поле postInstallLogicFunctionUniversalIdentifier в defineApplication() является необязательным. Если его опустить, после установки никакая функция выполняться не будет.
  • Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных.
  • Постустановочным функциям не нужны триггеры — платформа вызывает их во время установки или вручную через function:execute --postInstall.

Полезная нагрузка триггера маршрута

Нарушающее совместимость изменение (v1.16, январь 2026): Формат полезной нагрузки триггера маршрута изменился. До v1.16 параметры запроса, параметры пути и тело передавались напрямую в качестве полезной нагрузки. Начиная с v1.16 они вложены в структурированный объект RoutePayload.До v1.16:
const handler = async (params) => {
  const { param1, param2 } = params; // Direct access
};
После v1.16:
const handler = async (event: RoutePayload) => {
  const { param1, param2 } = event.body; // Access via .body
  const { queryParam } = event.queryStringParameters;
  const { id } = event.pathParameters;
};
Чтобы мигрировать существующие функции: Обновите обработчик, чтобы деструктурировать из event.body, event.queryStringParameters или event.pathParameters вместо прямого доступа к объекту params.
Когда триггер маршрута вызывает вашу логическую функцию, она получает объект RoutePayload, соответствующий формату AWS HTTP API v2. Импортируйте тип из twenty-sdk:
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';

const handler = async (event: RoutePayload) => {
  // Access request data
  const { headers, queryStringParameters, pathParameters, body } = event;

  // HTTP method and path are available in requestContext
  const { method, path } = event.requestContext.http;

  return { message: 'Success' };
};
Тип RoutePayload имеет следующую структуру:
СвойствоТипОписание
headersRecord<string, string | undefined>HTTP-заголовки (только перечисленные в forwardedRequestHeaders)
queryStringParametersRecord<string, string | undefined>Параметры строки запроса (несколько значений объединяются запятыми)
pathParametersRecord<string, string | undefined>Параметры пути, извлечённые из шаблона маршрута (например, /users/:id{ id: '123' })
текстobject | nullРазобранное тело запроса (JSON)
isBase64Encodedлогический типЯвляется ли тело закодированным в base64
requestContext.http.methodстрокаМетод HTTP (GET, POST, PUT, PATCH, DELETE)
requestContext.http.pathстрокаНеобработанный путь запроса

Проброс HTTP-заголовков

По умолчанию HTTP-заголовки из входящих запросов не передаются в вашу логическую функцию по соображениям безопасности. Чтобы получить доступ к определённым заголовкам, явно перечислите их в массиве forwardedRequestHeaders:
export default defineLogicFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  name: 'webhook-handler',
  handler,
  triggers: [
    {
      universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
      type: 'route',
      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']).
Вы можете создать новые функции двумя способами:
  • Сгенерировано: Запустите yarn twenty entity:add и выберите опцию добавления новой функции логики. Это создаёт стартовый файл с обработчиком и конфигурацией.
  • Вручную: Создайте новый файл *.logic-function.ts и используйте defineLogicFunction(), следуя тому же шаблону.

Пометка логической функции как инструмента

Логические функции можно предоставлять как инструменты для ИИ-агентов и рабочих процессов. Когда функция помечена как инструмент, она становится доступной для ИИ Twenty и может быть выбрана в качестве шага в автоматизациях рабочих процессов. Чтобы пометить логическую функцию как инструмент, установите isTool: true и укажите toolInputSchema для описания ожидаемых входных параметров с помощью схемы JSON:
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import Twenty from '~/generated';

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

  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,
  isTool: true,
  toolInputSchema: {
    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'],
  },
});
Основные моменты:
  • isTool (boolean, по умолчанию: false): Если значение равно true, функция регистрируется как инструмент и становится доступной агентам ИИ и автоматизациям рабочих процессов.
  • toolInputSchema (object, необязательно): Объект JSON Schema, который описывает параметры, которые принимает ваша функция. Агенты ИИ используют эту схему, чтобы понять, какие входные данные ожидает инструмент, и проверять корректность вызовов. Если опущено, по умолчанию используется схема { type: 'object', properties: {} } (без параметров).
  • Функции с isTool: false (или без указания) не выставляются как инструменты. Их по-прежнему можно выполнять напрямую или вызывать из других функций, но они не будут отображаться при обнаружении инструментов.
  • Именование инструмента: При публикации как инструмента имя функции автоматически нормализуется до logic_function_<name> (в нижнем регистре, небуквенно-цифровые символы заменяются на подчёркивания). Например, enrich-company становится logic_function_enrich_company.
  • Вы можете комбинировать isTool с триггерами — функция может одновременно быть инструментом (вызываемым агентами ИИ) и запускаться событиями (cron, события базы данных, маршруты).
Напишите хорошее описание в поле description. Агенты ИИ опираются на поле description функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать.

Фронт-компоненты

Фронт-компоненты позволяют создавать пользовательские компоненты React, которые рендерятся внутри интерфейса Twenty. Используйте defineFrontComponent() для определения компонентов со встроенной валидацией:
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';

const MyWidget = () => {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>My Custom Widget</h1>
      <p>This is a custom front component for Twenty.</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  name: 'my-widget',
  description: 'A custom widget component',
  component: MyWidget,
});
Основные моменты:
  • Фронт-компоненты — это компоненты React, которые рендерятся в изолированных контекстах внутри Twenty.
  • Используйте суффикс файла *.front-component.tsx для автоматического обнаружения.
  • Поле component ссылается на ваш компонент React.
  • Компоненты автоматически собираются и синхронизируются во время yarn twenty app:dev.
Вы можете создать новые фронт-компоненты двумя способами:
  • Сгенерировано: Запустите yarn twenty entity:add и выберите опцию добавления нового фронтенд-компонента.
  • Вручную: Создайте новый файл *.front-component.tsx и используйте defineFrontComponent().

Навыки

Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use defineSkill() to define skills with built-in validation:
// src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk';

export default defineSkill({
  universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  name: 'sales-outreach',
  label: 'Sales Outreach',
  description: 'Guides the AI agent through a structured sales outreach process',
  icon: 'IconBrain',
  content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
Основные моменты:
  • name is a unique identifier string for the skill (kebab-case recommended).
  • label is the human-readable display name shown in the UI.
  • content contains the skill instructions — this is the text the AI agent uses.
  • icon (optional) sets the icon displayed in the UI.
  • description (optional) provides additional context about the skill’s purpose.
You can create new skills in two ways:
  • Scaffolded: Run yarn twenty entity:add and choose the option to add a new skill.
  • Manual: Create a new file and use defineSkill(), following the same pattern.

Сгенерированный типизированный клиент

Типизированный клиент автоматически генерируется с помощью yarn twenty app:dev и сохраняется в node_modules/twenty-sdk/generated на основе схемы вашего рабочего пространства. Используйте его в своих функциях:
import Twenty from '~/generated';

const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
Клиент автоматически перегенерируется с помощью yarn twenty app:dev при изменении ваших объектов или полей.

Учётные данные времени выполнения в логических функциях

Когда ваша функция запускается на Twenty, платформа подставляет учётные данные как переменные окружения перед выполнением вашего кода:
  • TWENTY_API_URL: Базовый URL API Twenty, на который нацелено ваше приложение.
  • TWENTY_API_KEY: Краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения.
Заметки:
  • Вам не нужно передавать URL или ключ API сгенерированному клиенту. Он читает TWENTY_API_URL и TWENTY_API_KEY из process.env во время выполнения.
  • Права ключа API определяются ролью, на которую ссылается ваш application-config.ts через defaultRoleUniversalIdentifier. Это роль по умолчанию, используемая логическими функциями вашего приложения.
  • Приложения могут определять роли, чтобы следовать принципу наименьших привилегий. Предоставляйте только те права, которые нужны вашим функциям, затем укажите в defaultRoleUniversalIdentifier универсальный идентификатор этой роли.

Пример Hello World

Ознакомьтесь с минимальным сквозным примером, демонстрирующим объекты, логические функции, фронт-компоненты и несколько триггеров, здесь:

Ручная настройка (без генератора)

Хотя мы рекомендуем использовать create-twenty-app для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте twenty-sdk как локальную зависимость и настройте один скрипт в вашем package.json:
yarn add -D twenty-sdk
Затем добавьте скрипт twenty:
{
  "scripts": {
    "twenty": "twenty"
  }
}
Теперь вы можете запускать все команды через yarn twenty <command>, например, yarn twenty app:dev, yarn twenty help и т. д.

Устранение неполадок

  • Ошибки аутентификации: выполните yarn twenty auth:login и убедитесь, что у вашего ключа API есть необходимые права.
  • Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty.
  • Types or client missing/outdated: restart yarn twenty app:dev — it auto-generates the typed client.
  • Режим разработки не синхронизируется: убедитесь, что запущен yarn twenty app:dev, и что ваша среда не игнорирует изменения.
Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322