---
title: Приложения Twenty
description: Создавайте и управляйте настройками Twenty в виде кода.
---

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

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

Приложения позволяют создавать и управлять настройками Twenty **в виде кода**. Вместо настройки всего через интерфейс вы определяете модель данных и логические функции в коде — так быстрее создавать, поддерживать и развёртывать в нескольких рабочих пространствах.

**Что вы можете делать уже сегодня:**

* Определяйте пользовательские объекты и поля в виде кода (управляемая модель данных)
* Создавайте логические функции с пользовательскими триггерами
* Развёртывайте одно и то же приложение в нескольких рабочих пространствах

## Требования

* Node.js 24+ и Yarn 4
* Рабочее пространство Twenty и ключ API (создайте его на https://app.twenty.com/settings/api-webhooks)

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

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

```bash filename="Terminal"
# Создать каркас нового приложения
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
```

Отсюда вы можете:

```bash filename="Terminal"
# Добавить новую сущность в ваше приложение (с мастером)
yarn twenty entity:add

# Сгенерировать типизированный клиент Twenty и типы сущностей рабочего пространства
yarn twenty app:generate

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

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

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

# Показать справку по командам
yarn twenty help
```

Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).

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

Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор:

* Копирует минимальное базовое приложение в `my-twenty-app/`
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
* Генерирует конфигурацию приложения по умолчанию и роль функции по умолчанию

Свежесгенерированное приложение выглядит так:

```text filename="my-twenty-app/"
my-twenty-app/
  package.json
  yarn.lock
  .gitignore
  .nvmrc
  .yarnrc.yml
  .yarn/
    install-state.gz
  eslint.config.mjs
  tsconfig.json
  README.md
  public/                           # Папка общедоступных ресурсов (изображения, шрифты и т. п.)
  src/
  ├── application-config.ts           # Обязательный — основная конфигурация приложения
  ├── roles/
  │   └── default-role.ts               # Роль по умолчанию для логических функций
  ├── logic-functions/
  │   └── hello-world.ts                # Пример логической функции
  └── front-components/
      └── hello-world.tsx               # Пример фронтенд-компонента
```

В общих чертах:

* **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()`          | Расширения полей для существующих объектов |

<Note>
  **Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define<Entity>({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование.
</Note>

Пример обнаруженной сущности:

```typescript
// 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:generate` создаст папку `generated/` (типизированный клиент Twenty + типы рабочего пространства).
* `yarn twenty entity:add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов или ролей.

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

При первом запуске `yarn twenty auth:login` вам будет предложено указать:

* URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства)
* Ключ API

Ваши учётные данные хранятся для каждого пользователя в `~/.twenty/config.json`. Вы можете хранить несколько профилей и переключаться между ними.

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

```bash filename="Terminal"
# Войти в интерактивном режиме (рекомендуется)
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 предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в [Обнаружение сущностей](#entity-detection), вы должны использовать `export default define<Entity>({...})`, чтобы ваши сущности были обнаружены:

| Функция                  | Назначение                                                             |
| ------------------------ | ---------------------------------------------------------------------- |
| `defineApplication()`    | Настройка метаданных приложения (обязательно, по одному на приложение) |
| `defineObject()`         | Определяет пользовательские объекты с полями                           |
| `defineLogicFunction()`  | Определение логических функций с обработчиками                         |
| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса            |
| `defineRole()`           | Настраивает права роли и доступ к объектам                             |
| `defineField()`          | Расширение существующих объектов дополнительными полями                |

Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов.

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

Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте `defineObject()` для определения объектов со встроенной валидацией:

```typescript
// 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`, который проведёт вас через настройку имени, полей и связей.

<Note>
  **Базовые поля создаются автоматически.** Когда вы определяете пользовательский объект, Twenty автоматически добавляет стандартные поля, такие как `name`, `createdAt`, `updatedAt`, `createdBy`, `position` и `deletedAt`. Вам не нужно определять их в массиве `fields` — добавляйте только свои пользовательские поля.
</Note>

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

У каждого приложения есть единственный файл `application-config.ts`, который описывает:

* **Что это за приложение**: идентификаторы, отображаемое имя и описание.
* **Как запускаются его функции**: какую роль они используют для прав доступа.
* **(Необязательно) переменные**: пары ключ-значение, предоставляемые вашим функциям как переменные окружения.

Используйте `defineApplication()` для определения конфигурации вашего приложения:

```typescript
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';

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,
});
```

Заметки:

* `universalIdentifier` — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями.
* `applicationVariables` становятся переменными окружения для ваших функций (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` должен соответствовать файлу роли (см. ниже).

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

Приложения могут определять роли, инкапсулирующие права на объекты и действия в вашем рабочем пространстве. Поле `defaultRoleUniversalIdentifier` в `application-config.ts` обозначает роль по умолчанию, используемую логическими функциями вашего приложения.

* Ключ API во время выполнения, подставляемый как `TWENTY_API_KEY`, получается из этой роли функции по умолчанию.
* Типизированный клиент будет ограничен правами, предоставленными этой ролью.
* Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям, и укажите её универсальный идентификатор.

##### Роль функции по умолчанию (\*.role.ts)

Когда вы генерируете новое приложение, CLI также создаёт файл роли по умолчанию. Используйте `defineRole()` для определения ролей со встроенной валидацией:

```typescript
// 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`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).

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

Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами.

```typescript
// 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` необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями.
* Вы можете сочетать несколько типов триггеров в одной функции.

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

<Warning>
  **Нарушающее совместимость изменение (v1.16, январь 2026):** Формат полезной нагрузки триггера маршрута изменился. До v1.16 параметры запроса, параметры пути и тело передавались напрямую в качестве полезной нагрузки. Начиная с v1.16 они вложены в структурированный объект `RoutePayload`.

  **До v1.16:**

  ```typescript
  const handler = async (params) => {
    const { param1, param2 } = params; // Direct access
  };
  ```

  **После v1.16:**

  ```typescript
  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.
</Warning>

Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, соответствующий формату AWS HTTP API v2. Импортируйте тип из `twenty-sdk`:

```typescript
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` имеет следующую структуру:

| Свойство                     | Тип                                   | Описание                                                                                   |
| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ |
| `headers`                    | `Record<string, string \| undefined>` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`)                          |
| `queryStringParameters`      | `Record<string, string \| undefined>` | Параметры строки запроса (несколько значений объединяются запятыми)                        |
| `pathParameters`             | `Record<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`:

```typescript
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'],
    },
  ],
});
```

В обработчике вы сможете получить доступ к этим заголовкам:

```typescript
const handler = async (event: RoutePayload) => {
  const signature = event.headers['x-webhook-signature'];
  const contentType = event.headers['content-type'];

  // Validate webhook signature...
  return { received: true };
};
```

<Note>
  Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`).
</Note>

Вы можете создать новые функции двумя способами:

* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления новой функции логики. Это создаёт стартовый файл с обработчиком и конфигурацией.
* **Вручную**: Создайте новый файл `*.logic-function.ts` и используйте `defineLogicFunction()`, следуя тому же шаблону.

### Marking a logic function as a tool

Logic functions can be exposed as **tools** for AI agents and workflows. When a function is marked as a tool, it becomes discoverable by Twenty's AI features and can be selected as a step in workflow automations.

To mark a logic function as a tool, set `isTool: true` and provide a `toolInputSchema` describing the expected input parameters using [JSON Schema](https://json-schema.org/):

```typescript
// 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`, default: `false`): When set to `true`, the function is registered as a tool and becomes available to AI agents and workflow automations.
* **`toolInputSchema`** (`object`, optional): A JSON Schema object that describes the parameters your function accepts. AI agents use this schema to understand what inputs the tool expects and to validate calls. If omitted, the schema defaults to `{ type: 'object', properties: {} }` (no parameters).
* Functions with `isTool: false` (or unset) are **not** exposed as tools. They can still be executed directly or called by other functions, but will not appear in tool discovery.
* **Tool naming**: When exposed as a tool, the function name is automatically normalized to `logic_function_<name>` (lowercased, non-alphanumeric characters replaced with underscores). For example, `enrich-company` becomes `logic_function_enrich_company`.
* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events (cron, database events, routes) at the same time.

<Note>
  **Write a good `description`.** AI agents rely on the function's `description` field to decide when to use the tool. Be specific about what the tool does and when it should be called.
</Note>

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

Фронт-компоненты позволяют создавать пользовательские компоненты React, которые рендерятся внутри интерфейса Twenty. Используйте `defineFrontComponent()` для определения компонентов со встроенной валидацией:

```typescript
// 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()`.

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

Запустите `yarn twenty app:generate`, чтобы создать локальный типизированный клиент в `generated/` на основе схемы вашего рабочего пространства. Используйте его в своих функциях:

```typescript
import Twenty from '~/generated';

const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```

Клиент повторно генерируется командой `yarn twenty app:generate`. Запускайте повторно после изменения ваших объектов или при подключении к новому рабочему пространству.

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

Когда ваша функция запускается на 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

Ознакомьтесь с минимальным сквозным примером, демонстрирующим объекты, логические функции, фронт-компоненты и несколько триггеров, [здесь](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):

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

Хотя мы рекомендуем использовать `create-twenty-app` для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте `twenty-sdk` как локальную зависимость и настройте один скрипт в вашем package.json:

```bash filename="Terminal"
yarn add -D twenty-sdk
```

Затем добавьте скрипт `twenty`:

```json filename="package.json"
{
  "scripts": {
    "twenty": "twenty"
  }
}
```

Теперь вы можете запускать все команды через `yarn twenty <command>`, например, `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help` и т. д.

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

* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права.
* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty.
* Типы или клиент отсутствуют/устарели: выполните `yarn twenty app:generate`.
* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty app:dev`, и что ваша среда не игнорирует изменения.

Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
