i18n - docs translations (#20366)

Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
github-actions[bot]
2026-05-07 18:53:27 +02:00
committed by GitHub
parent 24e64350ee
commit 95bc8aea28
175 changed files with 5164 additions and 5007 deletions
@@ -1,15 +1,15 @@
---
title: Application Config
description: Declare your app's identity, default role, variables, and marketplace metadata with defineApplication.
title: Конфигурация приложения
description: Объявите идентификацию вашего приложения, роль по умолчанию, переменные и метаданные маркетплейса с помощью defineApplication.
icon: rocket
---
Every app must have exactly one `defineApplication` call. It declares:
В каждом приложении должен быть ровно один вызов `defineApplication`. Он объявляет:
* **Identity** — universal identifier, display name, description.
* **Permissions** — which role its logic functions and front components run under.
* **Variables** *(optional)* — keyvalue pairs exposed to your code as environment variables.
* **Pre-install / post-install hooks** *(optional)* — see [Logic Functions](/l/ru/developers/extend/apps/logic/logic-functions).
* **Идентификация** — универсальный идентификатор, отображаемое имя, описание.
* **Разрешения** — под какой ролью выполняются его логические функции и фронтенд-компоненты.
* **Переменные** *(необязательно)* — пары ключ–значение, доступные вашему коду как переменные окружения.
* **Хуки предустановки / постустановки** *(необязательно)* — см. [Логические функции](/l/ru/developers/extend/apps/logic/logic-functions).
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
@@ -31,35 +31,35 @@ export default defineApplication({
});
```
Notes:
Заметки:
* `universalIdentifier` fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
* `applicationVariables` become environment variables for your functions and front components (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` must reference a role defined with [`defineRole()`](/l/ru/developers/extend/apps/config/roles).
* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
* Поля `universalIdentifier` — это детерминированные идентификаторы, которые принадлежат вам. Сгенерируйте их один раз и сохраняйте неизменными между синхронизациями.
* `applicationVariables` становятся переменными окружения для ваших функций и фронтенд-компонентов (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` должен ссылаться на роль, определённую с помощью [`defineRole()`](/l/ru/developers/extend/apps/config/roles).
* Предустановочные и постустановочные функции обнаруживаются автоматически во время сборки манифеста — вам не нужно указывать их в `defineApplication()`.
## Default function role
## Роль функции по умолчанию
The `defaultRoleUniversalIdentifier` controls what the app's logic functions and front components can access:
`defaultRoleUniversalIdentifier` определяет, к чему могут получать доступ логические функции и фронтенд-компоненты приложения:
* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role.
* The typed API client is restricted to the permissions granted to that role.
* Follow least-privilege: declare only the permissions your functions need.
* Токен времени выполнения, подставляемый как `TWENTY_APP_ACCESS_TOKEN`, формируется из этой роли.
* Типизированный клиент API ограничен правами, предоставленными этой роли.
* Следуйте принципу наименьших привилегий: объявляйте только те разрешения, которые действительно нужны вашим функциям.
When you scaffold a new app, the CLI creates a starter role file at `src/roles/default-role.ts`. See [Roles & Permissions](/l/ru/developers/extend/apps/config/roles) for the full reference.
Когда вы создаёте новое приложение с помощью шаблона, CLI создаёт стартовый файл роли по адресу `src/roles/default-role.ts`. Полную справочную информацию см. в разделе [Роли и разрешения](/l/ru/developers/extend/apps/config/roles).
## Marketplace metadata
## Метаданные маркетплейса
If you plan to [publish your app](/l/ru/developers/extend/apps/operations/publishing), these optional fields control how it appears in the marketplace:
Если вы планируете [опубликовать приложение](/l/ru/developers/extend/apps/operations/publishing), эти необязательные поля определяют, как оно отображается в маркетплейсе:
| Field | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `author` | Author or company name |
| `category` | App category for marketplace filtering |
| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) |
| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) |
| `aboutDescription` | Longer markdown description for the "About" tab. If omitted, the marketplace uses the package's `README.md` from npm |
| `websiteUrl` | Link to your website |
| `termsUrl` | Link to terms of service |
| `emailSupport` | Support email address |
| `issueReportUrl` | Link to issue tracker |
| Поле | Описание |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `author` | Имя автора или название компании |
| `category` | Категория приложения для фильтрации в маркетплейсе |
| `logoUrl` | Путь к логотипу вашего приложения (например, `public/logo.png`) |
| `screenshots` | Массив путей к скриншотам (например, `public/screenshot-1.png`) |
| `aboutDescription` | Расширенное описание в Markdown для вкладки "About". Если опущено, маркетплейс использует `README.md` пакета из npm |
| `websiteUrl` | Ссылка на ваш сайт |
| `termsUrl` | Ссылка на условия предоставления услуг |
| `emailSupport` | Адрес электронной почты поддержки |
| `issueReportUrl` | Ссылка на систему отслеживания проблем |
@@ -1,12 +1,12 @@
---
title: Install Hooks
description: Run logic before or after the install — seed data, back up records, validate the upgrade.
title: Установочные хуки
description: Запускайте логику до или после установки — заполняйте исходные данные, создавайте резервные копии записей, проверяйте корректность обновления.
icon: wrench
---
Install hooks are special logic functions that run during the install or upgrade lifecycle. They share the same handler runtime as regular [logic functions](/l/ru/developers/extend/apps/logic/logic-functions) and receive an `InstallPayload`, but they're declared with their own define functions — `definePostInstallLogicFunction()` and `definePreInstallLogicFunction()` — and live outside the normal trigger model (HTTP, cron, database events).
Установочные хуки — это специальные логические функции, которые выполняются во время установки или обновления. Они используют то же окружение выполнения обработчика, что и обычные [logic functions](/l/ru/developers/extend/apps/logic/logic-functions) и получают `InstallPayload`, но объявляются с помощью собственных функций определения — `definePostInstallLogicFunction()` и `definePreInstallLogicFunction()` — и существуют вне обычной модели триггеров (HTTP, cron, события базы данных).
Each app may define **at most one pre-install** and **at most one post-install** function. The manifest build will error if more than one of either is detected.
Каждое приложение может определить **не более одной pre-install** и **не более одной post-install** функции. Сборка манифеста завершится ошибкой, если будет обнаружено более одной функции любого из этих типов.
```
┌─────────────────────────────────────────────────────────────┐
@@ -20,9 +20,9 @@ Each app may define **at most one pre-install** and **at most one post-install**
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Runs after the workspace metadata migration is applied">
<Accordion title="definePostInstallLogicFunction" description="Выполняется после применения миграции метаданных рабочего пространства">
A post-install function runs automatically once your app has finished installing on a workspace. The server executes it **after** the app's metadata has been synchronized and the SDK client has been generated, so the workspace is fully ready to use and the new schema is in place. Typical use cases include seeding default data, creating initial records, configuring workspace settings, or provisioning resources on third-party services.
Послеустановочная функция автоматически запускается после того, как установка вашего приложения в рабочем пространстве завершена. Сервер выполняет её **после** того, как метаданные приложения синхронизированы и клиент SDK сгенерирован, так что рабочее пространство полностью готово к использованию, а новая схема уже применена. Типичные сценарии использования включают предзаполнение данных по умолчанию, создание начальных записей, настройку параметров рабочего пространства или выделение ресурсов в сторонних сервисах.
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -42,30 +42,30 @@ export default definePostInstallLogicFunction({
});
```
You can also manually execute the post-install function at any time using the CLI:
Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI:
```bash filename="Terminal"
yarn twenty exec --postInstall
```
Key points:
* Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* The handler receives an `InstallPayload` with `{ previousVersion?: string; newVersion: string }` — `newVersion` is the version being installed, and `previousVersion` is the version that was previously installed (or `undefined` on a fresh install). Use these values to distinguish fresh installs from upgrades and to run version-specific migration logic.
* **When the hook runs**: on fresh installs only, by default. Pass `shouldRunOnVersionUpgrade: true` if you also want it to run when the app is upgraded from a previous version. When omitted, the flag defaults to `false` and upgrades skip the hook.
* **Execution model — async by default, sync opt-in**: the `shouldRunSynchronously` flag controls *how* post-install is executed.
* `shouldRunSynchronously: false` *(default)* — the hook is **enqueued on the message queue** with `retryLimit: 3` and runs asynchronously in a worker. The install response returns as soon as the job is enqueued, so a slow or failing handler does not block the caller. The worker will retry up to three times. **Use this for long-running jobs** — seeding large datasets, calling slow third-party APIs, provisioning external resources, anything that might exceed a reasonable HTTP response window.
* `shouldRunSynchronously: true` — the hook is executed **inline during the install flow** (same executor as pre-install). The install request blocks until the handler finishes, and if it throws, the install caller receives a `POST_INSTALL_ERROR`. No automatic retries. **Use this for fast, must-complete-before-response work** — for example, emitting a validation error to the user, or quick setup that the client will rely on immediately after the install call returns. Keep in mind the metadata migration has already been applied by the time post-install runs, so a sync-mode failure does **not** roll back the schema changes — it only surfaces the error.
* Make sure your handler is idempotent. In async mode the queue may retry up to three times; in either mode the hook may run again on upgrades when `shouldRunOnVersionUpgrade: true`.
* The environment variables `APPLICATION_ID`, `APP_ACCESS_TOKEN`, and `API_URL` are available inside the handler (same as any other logic function), so you can call the Twenty API with an application access token scoped to your app.
* Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
* The function's `universalIdentifier`, `shouldRunOnVersionUpgrade`, and `shouldRunSynchronously` are automatically attached to the application manifest under the `postInstallLogicFunction` field during the build — you do not need to reference them in [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* **Not executed in dev mode**: when an app is registered locally (via `yarn twenty dev`), the server skips the install flow entirely and syncs files directly through the CLI watcher — so post-install never runs in dev mode, regardless of `shouldRunSynchronously`. Use `yarn twenty exec --postInstall` to trigger it manually against a running workspace.
Основные моменты:
* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* Обработчик получает `InstallPayload` с `{ previousVersion?: string; newVersion: string }` — `newVersion` — это устанавливаемая версия, а `previousVersion` — версия, установленная ранее (или `undefined` при чистой установке). Используйте эти значения, чтобы отличать чистые установки от обновлений и запускать логику миграции, зависящую от версии.
* **Когда запускается хук**: по умолчанию только при чистой установке. Передайте `shouldRunOnVersionUpgrade: true`, если хотите, чтобы он также выполнялся при обновлении приложения с предыдущей версии. Если флаг опущен, по умолчанию он равен `false`, и при обновлении хук пропускается.
* **Модель выполнения — по умолчанию асинхронно, синхронный режим по выбору**: флаг `shouldRunSynchronously` определяет, *как* выполняется post-install.
* `shouldRunSynchronously: false` *(по умолчанию)* — хук **помещается в очередь сообщений** с `retryLimit: 3` и выполняется асинхронно в воркере. Ответ на установку возвращается сразу после постановки задания в очередь, поэтому медленный или дающий сбой обработчик не блокирует вызывающую сторону. Воркер выполнит до трёх повторных попыток. **Используйте это для длительных задач** — наполнение большими наборами данных, вызовы медленных сторонних API, подготовка внешних ресурсов — всего, что может выйти за разумное окно ответа HTTP.
* `shouldRunSynchronously: true` — хук выполняется **непосредственно в процессе установки** (тот же исполнитель, что и для pre-install). Запрос установки блокируется, пока обработчик не завершится, и если он генерирует исключение, вызывающая сторона установки получает `POST_INSTALL_ERROR`. Автоматических повторов нет. **Используйте это для быстрых задач, которые должны завершиться до отправки ответа** — например, выдача ошибки валидации пользователю или быстрая настройка, на которую клиент будет полагаться сразу после возврата вызова установки. Имейте в виду, что к моменту запуска post-install миграция метаданных уже применена, поэтому сбой в синхронном режиме **не** откатывает изменения схемы — он лишь выявляет ошибку.
* Убедитесь, что ваш обработчик идемпотентен. В асинхронном режиме очередь может выполнить до трёх повторных попыток; в любом режиме хук может запускаться снова при обновлениях, когда `shouldRunOnVersionUpgrade: true`.
* Переменные окружения `APPLICATION_ID`, `APP_ACCESS_TOKEN` и `API_URL` доступны внутри обработчика (как и в любой другой логической функции), поэтому вы можете вызывать API Twenty с токеном доступа приложения, ограниченным вашим приложением.
* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции.
* Параметры функции `universalIdentifier`, `shouldRunOnVersionUpgrade` и `shouldRunSynchronously` автоматически добавляются в манифест приложения в поле `postInstallLogicFunction` во время сборки — вам не нужно указывать их в [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных.
* **Не выполняется в режиме разработки**: когда приложение зарегистрировано локально (через `yarn twenty dev`), сервер полностью пропускает процесс установки и синхронизирует файлы напрямую через наблюдатель CLI — поэтому post-install никогда не запускается в режиме разработки, независимо от `shouldRunSynchronously`. Используйте `yarn twenty exec --postInstall`, чтобы запустить это вручную для запущенного рабочего пространства.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Runs before the workspace metadata migration is applied">
<Accordion title="definePreInstallLogicFunction" description="Выполняется до применения миграции метаданных рабочего пространства">
A pre-install function runs automatically during installation, **before the workspace metadata migration is applied**. It shares the same payload shape as post-install (`InstallPayload`), but it is positioned earlier in the install flow so it can prepare state that the upcoming migration depends on — typical uses include backing up data, validating compatibility with the new schema, or archiving records that are about to be restructured or dropped.
Функция pre-install автоматически выполняется во время установки, **до применения миграции метаданных рабочего пространства**. Она использует ту же структуру полезной нагрузки, что и post-install (`InstallPayload`), но находится раньше в процессе установки, чтобы подготовить состояние, от которого зависит предстоящая миграция, — типичные сценарии включают резервное копирование данных, проверку совместимости с новой схемой или архивирование записей, которые будут реструктурированы или удалены.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -84,35 +84,35 @@ export default definePreInstallLogicFunction({
});
```
You can also manually execute the pre-install function at any time using the CLI:
Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI:
```bash filename="Terminal"
yarn twenty exec --preInstall
```
Key points:
* Pre-install functions use `definePreInstallLogicFunction()` — same specialized config as post-install, just attached to a different lifecycle slot.
* Both pre- and post-install handlers receive the same `InstallPayload` type: `{ previousVersion?: string; newVersion: string }`. Import it once and reuse it for both hooks.
* **When the hook runs**: positioned just before the workspace metadata migration (`synchronizeFromManifest`). Before executing, the server runs a purely additive "pared-down sync" that registers the **new** version's pre-install function in the workspace metadata — nothing else is touched — and then executes it. Because this sync is additive-only, the previous version's objects, fields, and data are still intact when your handler runs: you can safely read and back up pre-migration state.
* **Execution model**: pre-install is executed **synchronously** and **blocks the install**. If the handler throws, the install is aborted before any schema changes are applied — the workspace stays on the previous version in a consistent state. This is intentional: pre-install is your last chance to refuse a risky upgrade.
* As with post-install, only one pre-install function is allowed per application. It is attached to the application manifest under `preInstallLogicFunction` automatically during the build.
* **Not executed in dev mode**: same as post-install — the install flow is skipped entirely for locally-registered apps, so pre-install never runs under `yarn twenty dev`. Use `yarn twenty exec --preInstall` to trigger it manually.
Основные моменты:
* Функции pre-install используют `definePreInstallLogicFunction()` — та же специализированная конфигурация, что и у post-install, только привязанная к другому этапу жизненного цикла.
* И обработчики pre-, и post-install получают один и тот же тип `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Импортируйте его один раз и используйте повторно в обоих хуках.
* **Когда запускается хук**: выполняется непосредственно перед миграцией метаданных рабочего пространства (`synchronizeFromManifest`). Перед выполнением сервер запускает чисто добавочную «урезанную синхронизацию», которая регистрирует в метаданных рабочего пространства pre-install функцию **новой** версии — ничего больше не затрагивается — а затем выполняет её. Поскольку эта синхронизация только добавляет, объекты, поля и данные предыдущей версии остаются нетронутыми к моменту запуска вашего обработчика: вы можете безопасно читать и сохранять состояние до миграции.
* **Модель выполнения**: pre-install выполняется **синхронно** и **блокирует установку**. Если обработчик генерирует исключение, установка прерывается до применения каких-либо изменений схемы — рабочее пространство остаётся на предыдущей версии в согласованном состоянии. Это сделано намеренно: pre-install — ваш последний шанс отказать в рискованном обновлении.
* Как и в случае с post-install, для каждого приложения допускается только одна предустановочная функция. Она автоматически добавляется в манифест приложения в поле `preInstallLogicFunction` во время сборки.
* **Не выполняется в режиме разработки**: как и post-install, процесс установки полностью пропускается для локально зарегистрированных приложений, поэтому pre-install никогда не запускается при `yarn twenty dev`. Используйте `yarn twenty exec --preInstall`, чтобы запустить это вручную.
</Accordion>
<Accordion title="Pre-install vs post-install: when to use which" description="Choosing the right install hook">
<Accordion title="Pre-install и post-install: когда что использовать" description="Выбор подходящего хука установки">
Both hooks are part of the same install flow and receive the same `InstallPayload`. The difference is **when** they run relative to the workspace metadata migration, and that changes what data they can safely touch.
Оба хука являются частью одного и того же процесса установки и получают один и тот же `InstallPayload`. Разница в том, **когда** они запускаются относительно миграции метаданных рабочего пространства, и это определяет, к каким данным можно безопасно обращаться.
Pre-install is always **synchronous** (it blocks the install and can abort it). Post-install is **asynchronous by default** — enqueued on a worker with automatic retries — but can opt into synchronous execution with `shouldRunSynchronously: true`. See the `definePostInstallLogicFunction` accordion above for when to use each mode.
Pre-install всегда **синхронный** (он блокирует установку и может её прервать). Post-install **по умолчанию асинхронный** — ставится в очередь воркера с автоматическими повторами — но может перейти к синхронному выполнению с `shouldRunSynchronously: true`. См. аккордеон `definePostInstallLogicFunction` выше о том, когда использовать каждый режим.
**Use `post-install` for anything that needs the new schema to exist.** This is the common case:
**Используйте `post-install` для всего, что требует наличия новой схемы.** Это распространённый случай:
* Seeding default data (creating initial records, default views, demo content) against newly-added objects and fields.
* Registering webhooks with third-party services now that the app has its credentials.
* Calling your own API to finish setup that depends on the synchronized metadata.
* Idempotent "ensure this exists" logic that should reconcile state on every upgrade — combine with `shouldRunOnVersionUpgrade: true`.
* Наполнение данными по умолчанию (создание начальных записей, стандартных представлений, демонстрационного контента) для недавно добавленных объектов и полей.
* Регистрация вебхуков в сторонних сервисах теперь, когда у приложения уже есть учётные данные.
* Вызов вашего собственного API для завершения настройки, зависящей от синхронизированных метаданных.
* Идемпотентная логика «убедиться, что это существует», которая должна приводить состояние в соответствие при каждом обновлении — совместите с `shouldRunOnVersionUpgrade: true`.
Example — seed a default `PostCard` record after install:
Пример — создать запись `PostCard` по умолчанию после установки:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -137,14 +137,14 @@ export default definePostInstallLogicFunction({
});
```
**Use `pre-install` when a migration would otherwise destroy or corrupt existing data.** Because pre-install runs against the *previous* schema and its failure rolls back the upgrade, it is the right place for anything risky:
**Используйте `pre-install`, когда миграция в противном случае уничтожит или повредит существующие данные.** Поскольку pre-install работает с *предыдущей* схемой и при сбое откатывает обновление, это правильное место для всего рискованного:
* **Backing up data that is about to be dropped or restructured** — e.g. you are removing a field in v2 and need to copy its values into another field or export them to storage before the migration runs.
* **Archiving records that a new constraint would invalidate** — e.g. a field is becoming `NOT NULL` and you need to delete or fix rows with null values first.
* **Validating compatibility and refusing the upgrade if the current data cannot be migrated cleanly** — throw from the handler and the install aborts with no changes applied. This is safer than discovering the incompatibility mid-migration.
* **Renaming or rekeying data** ahead of a schema change that would lose the association.
* **Резервное копирование данных, которые будут удалены или реструктурированы** — например, вы удаляете поле в v2 и вам нужно скопировать его значения в другое поле или экспортировать их в хранилище до запуска миграции.
* **Архивирование записей, которые новое ограничение сделает недопустимыми** — например, поле становится `NOT NULL`, и вам сначала нужно удалить или исправить строки со значениями null.
* **Проверка совместимости и отказ от обновления, если текущие данные нельзя корректно мигрировать** — выбросьте исключение из обработчика, и установка прервётся без внесения изменений. Это безопаснее, чем обнаружить несовместимость в середине миграции.
* **Переименование или изменение ключей данных** перед изменением схемы, которое привело бы к потере связи.
Example — archive records before a destructive migration:
Пример — архивировать записи перед разрушительной миграцией:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -186,20 +186,20 @@ export default definePreInstallLogicFunction({
});
```
**Rule of thumb:**
**Общее правило:**
| You want to... | Use |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Seed default data, configure the workspace, register external resources | `post-install` |
| Run long-running seeding or third-party calls that shouldn't block the install response | `post-install` (default — `shouldRunSynchronously: false`, with worker retries) |
| Run fast setup that the caller will rely on immediately after the install call returns | `post-install` with `shouldRunSynchronously: true` |
| Read or back up data that the upcoming migration would lose | `pre-install` |
| Reject an upgrade that would corrupt existing data | `pre-install` (throw from the handler) |
| Run reconciliation on every upgrade | `post-install` with `shouldRunOnVersionUpgrade: true` |
| Do one-off setup on the first install only | `post-install` with `shouldRunOnVersionUpgrade: false` (default) |
| Вы хотите... | Использовать |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Наполнить данными по умолчанию, настроить рабочее пространство, зарегистрировать внешние ресурсы | `post-install` |
| Выполнить длительное наполнение или сторонние вызовы, которые не должны блокировать ответ установки | `post-install` (по умолчанию — `shouldRunSynchronously: false`, с повторами воркера) |
| Выполнить быструю настройку, на которую вызывающая сторона будет полагаться сразу после возврата вызова установки | `post-install` с `shouldRunSynchronously: true` |
| Прочитать или сохранить данные, которые предстоящая миграция может потерять | `pre-install` |
| Отклонить обновление, которое повредит существующие данные | `pre-install` (бросьте исключение из обработчика) |
| Выполнять согласование при каждом обновлении | `post-install` с `shouldRunOnVersionUpgrade: true` |
| Сделать одноразовую настройку только при первой установке | `post-install` с `shouldRunOnVersionUpgrade: false` (по умолчанию) |
<Note>
If in doubt, default to **post-install**. Only reach for pre-install when the migration itself is destructive and you need to intercept the previous state before it is gone.
Если сомневаетесь, выбирайте по умолчанию **post-install**. Обращайтесь к pre-install только тогда, когда сама миграция разрушительна и вам нужно перехватить предыдущее состояние, прежде чем оно исчезнет.
</Note>
</Accordion>
@@ -1,10 +1,10 @@
---
title: Overview
description: Configure the app itself — its identity, default permissions, and what runs at install time.
title: Обзор
description: Настройте само приложение — его идентичность, разрешения по умолчанию и то, что выполняется во время установки.
icon: screwdriver-wrench
---
A Twenty app's **config layer** is what describes the app *to the platform* — its identity, the permissions it holds, and the code that runs during install or upgrade. These declarations don't add new data shapes or runtime behavior; they tell Twenty *who the app is* and *how to set it up*.
**Конфигурационный слой** приложения Twenty описывает приложение *для платформы* — его идентичность, разрешения, которыми оно обладает, и код, который выполняется при установке или обновлении. Эти декларации не добавляют новых структур данных или поведения во время выполнения; они сообщают Twenty, *что это за приложение* и *как его настроить*.
```text
┌────────────────────────────────────────────────────────┐
@@ -26,26 +26,26 @@ A Twenty app's **config layer** is what describes the app *to the platform* —
└──────────────────────────────────┘
```
## In this section
## В этом разделе
<CardGroup cols={2}>
<Card title="Application Config" icon="rocket" href="/l/ru/developers/extend/apps/config/application">
`defineApplication` — identity, default role, variables, marketplace metadata.
<Card title="Конфигурация приложения" icon="rocket" href="/l/ru/developers/extend/apps/config/application">
`defineApplication` — идентичность, роль по умолчанию, переменные, метаданные маркетплейса.
</Card>
<Card title="Roles & Permissions" icon="shield-halved" href="/l/ru/developers/extend/apps/config/roles">
`defineRole` — declare what your app's logic functions can read and write.
<Card title="Роли и разрешения" icon="shield-halved" href="/l/ru/developers/extend/apps/config/roles">
`defineRole` — определите, что логические функции вашего приложения могут читать и записывать.
</Card>
<Card title="Install Hooks" icon="wrench" href="/l/ru/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction` and `definePostInstallLogicFunction` — back up data, seed defaults, validate upgrades.
<Card title="Хуки установки" icon="wrench" href="/l/ru/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction` и `definePostInstallLogicFunction` — создавайте резервные копии данных, заполняйте значения по умолчанию, проверяйте обновления.
</Card>
</CardGroup>
## How the pieces relate
## Связь между частями
* **Application** is the entry point. Every app has exactly one `defineApplication()` call, and it points at one **Role** as its default.
* The **Role** controls what the app's logic functions and front components can read and write. Follow least-privilege: only grant the permissions your code actually needs.
* **Install Hooks** run during install or upgrade — pre-install before the metadata migration (so it can refuse a risky upgrade), post-install after the migration (so it can seed default data against the new schema).
* **Приложение** — это точка входа. У каждого приложения есть ровно один вызов `defineApplication()`, и он указывает на одну **роль** как роль по умолчанию.
* **Роль** управляет тем, что логические функции и фронтенд‑компоненты приложения могут читать и записывать. Следуйте принципу наименьших привилегий: выдавайте только те разрешения, которые вашему коду действительно нужны.
* **Хуки установки** запускаются при установке или обновлении — предустановочный до миграции метаданных (чтобы можно было отклонить рискованное обновление), постустановочный после миграции (чтобы можно было заполнить данные по умолчанию в соответствии с новой схемой).
<Note>
Install hooks share the [logic function](/l/ru/developers/extend/apps/logic/logic-functions) runtime — same handler signature, same environment variables, same typed API client — but they're declared with their own define functions and live outside the regular trigger model (HTTP, cron, database events).
Хуки установки используют то же окружение выполнения, что и [логическая функция](/l/ru/developers/extend/apps/logic/logic-functions) — тот же формат обработчика, те же переменные окружения, тот же типизированный клиент API, — но объявляются через собственные функции `define` и находятся вне обычной модели триггеров (HTTP, cron, события базы данных).
</Note>
@@ -1,25 +1,25 @@
---
title: Public Assets
description: Ship static files — images, icons, fonts — alongside your app via the public/ folder.
title: Публичные ресурсы
description: Отправляйте статические файлы — изображения, иконки, шрифты — вместе с вашим приложением через папку public/.
icon: folder-open
---
The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server.
Папка `public/` в корне вашего приложения содержит статические файлы — изображения, значки, шрифты и любые другие ресурсы, необходимые вашему приложению во время выполнения. Эти файлы автоматически включаются в сборки, синхронизируются в режиме разработки и загружаются на сервер.
Files placed in `public/` are:
Файлы, размещённые в `public/`, являются:
* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them.
* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components.
* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic.
* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published.
* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed.
* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output.
* **Публично доступными** — после синхронизации с сервером ресурсы доступны по публичному URL. Для доступа к ним аутентификация не требуется.
* **Доступными в компонентах фронтенда** — используйте URL ресурсов для отображения изображений, значков или любого медиа внутри ваших компонентов React.
* **Доступными в логических функциях** — используйте URL ресурсов в письмах, ответах API или любой серверной логике.
* **Используются для метаданных маркетплейса** — поля `logoUrl` и `screenshots` в `defineApplication()` ссылаются на файлы из этой папки (например, `public/logo.png`). Они отображаются в маркетплейсе при публикации вашего приложения.
* **Автосинхронизация в режиме разработки** — когда вы добавляете, обновляете или удаляете файл в `public/`, он автоматически синхронизируется с сервером. Перезапуск не требуется.
* **Включены в сборки** — `yarn twenty build` упаковывает все публичные ресурсы в выходной дистрибутив.
## Accessing public assets with `getPublicAssetUrl`
## Доступ к публичным ресурсам с помощью `getPublicAssetUrl`
Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**.
Используйте хелпер `getPublicAssetUrl` из `twenty-sdk`, чтобы получить полный URL файла в каталоге `public/` вашего приложения. Он работает как в **логических функциях**, так и в **компонентах фронтенда**.
**In a logic function:**
**В логической функции:**
```ts src/logic-functions/send-invoice.ts
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define';
@@ -44,7 +44,7 @@ export default defineLogicFunction({
});
```
**In a front component:**
**В компоненте фронтенда:**
```tsx src/front-components/company-card.tsx
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
@@ -56,4 +56,4 @@ export default defineFrontComponent(() => {
});
```
The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present.
Аргумент `path` задаётся относительно папки `public/` вашего приложения. И `getPublicAssetUrl('logo.png')`, и `getPublicAssetUrl('public/logo.png')` приводят к одному и тому же URL — префикс `public/`, если он есть, удаляется автоматически.
@@ -1,10 +1,10 @@
---
title: Roles & Permissions
description: Declare what objects and fields your app's logic functions and front components can read and write.
title: Роли и разрешения
description: Укажите, к каким объектам и полям логические функции и фронтенд‑компоненты вашего приложения могут выполнять чтение и запись.
icon: shield-halved
---
A **role** is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app's logic functions and front components inherit the permissions of the role declared as `defaultRoleUniversalIdentifier` in [`defineApplication`](/l/ru/developers/extend/apps/config/application).
**Роль** — это набор разрешений: какие объекты приложение может читать или изменять, какие поля оно может видеть и какие возможности платформенного уровня оно может использовать. Все логические функции и фронтенд‑компоненты каждого приложения наследуют разрешения роли, объявленной как `defaultRoleUniversalIdentifier` в [`defineApplication`](/l/ru/developers/extend/apps/config/application).
```ts src/roles/restricted-company-role.ts
import {
@@ -49,9 +49,9 @@ export default defineRole({
});
```
## The default function role
## Роль функции по умолчанию
When you scaffold a new app, the CLI creates a default role file:
Когда вы генерируете новое приложение, CLI создаёт файл роли по умолчанию:
```ts src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
@@ -77,14 +77,14 @@ export default defineRole({
});
```
This role's `universalIdentifier` is referenced from `application-config.ts` as `defaultRoleUniversalIdentifier`:
Значение `universalIdentifier` этой роли указывается в `application-config.ts` как `defaultRoleUniversalIdentifier`:
* **`*.role.ts`** declares what the role can do.
* **`application-config.ts`** points to that role so your functions inherit its permissions.
* **`*.role.ts`** определяет, что может делать роль.
* **`application-config.ts`** указывает на эту роль, чтобы ваши функции наследовали её права.
## Best practices
## Лучшие практики
* Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production.
* Replace `objectPermissions` and `fieldPermissions` with the exact objects and fields your functions actually need.
* `permissionFlags` control access to platform-level capabilities. Keep them minimal.
* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
* Начните с сгенерированной роли и постепенно ограничивайте её — роль по умолчанию предоставляет широкий доступ на чтение, что редко подходит для продакшена.
* Замените `objectPermissions` и `fieldPermissions` на точные объекты и поля, которые действительно нужны вашим функциям.
* `permissionFlags` управляют доступом к возможностям на уровне платформы. Сведите их к минимуму.
* См. рабочий пример: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
@@ -1,10 +1,10 @@
---
title: Extending Objects
description: Add fields to standard Twenty objects (Person, Company, …) or to objects from other apps using defineField.
title: Расширение объектов
description: Добавляйте поля к стандартным объектам Twenty (Person, Company, …) или к объектам из других приложений с помощью `defineField`.
icon: wand-magic-sparkles
---
Use `defineField()` to add a field to an object you don't own — a standard Twenty object like Person or Company, or an object shipped by another installed app. Unlike inline fields declared inside [`defineObject`](/l/ru/developers/extend/apps/data/objects), standalone fields require an `objectUniversalIdentifier` to specify which object they extend.
Используйте `defineField()` для добавления поля к объекту, которым вы не владеете — стандартному объекту Twenty, такому как Person или Company, или объекту, поставляемому другим установленным приложением. В отличие от встроенных полей, объявленных внутри [`defineObject`](/l/ru/developers/extend/apps/data/objects), отдельные поля требуют `objectUniversalIdentifier`, чтобы указать, какой объект они расширяют.
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
@@ -24,9 +24,9 @@ export default defineField({
});
```
## Key points
## Основные моменты
* `objectUniversalIdentifier` identifies the target object. For standard Twenty objects, import the constant from `twenty-sdk`:
* `objectUniversalIdentifier` определяет целевой объект. Для стандартных объектов Twenty импортируйте константу из `twenty-sdk`:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
@@ -37,12 +37,12 @@ export default defineField({
// …
```
* When defining fields **inline inside `defineObject()`**, you do **not** need `objectUniversalIdentifier` — it's inherited from the parent object.
* При определении полей **непосредственно внутри `defineObject()`** вам **не** нужен `objectUniversalIdentifier` — он наследуется от родительского объекта.
* `defineField()` is the only way to add fields to objects you didn't create with `defineObject()`.
* `defineField()` — единственный способ добавить поля к объектам, которые вы не создавали с помощью `defineObject()`.
* File location is up to you. The convention is `src/fields/\<name>.field.ts`, but the SDK detects fields anywhere in `src/`.
* Расположение файла зависит от вас. Принятое соглашение — `src/fields/\<name>.field.ts`, но SDK обнаруживает поля в любом месте внутри `src/`.
## Adding a relation to an existing object
## Добавление связи к существующему объекту
To add a relation field (e.g. linking your custom object to a standard `Person`), use `defineField()` with `FieldType.RELATION`. The pattern is the same as for inline relations but with `objectUniversalIdentifier` set explicitly. See [Relations](/l/ru/developers/extend/apps/data/relations) for the bidirectional pattern.
Чтобы добавить поле связи (например, связать ваш пользовательский объект со стандартным `Person`), используйте `defineField()` с `FieldType.RELATION`. Шаблон тот же, что и для встроенных связей, но с явным указанием `objectUniversalIdentifier`. Смотрите раздел [Relations](/l/ru/developers/extend/apps/data/relations) для двунаправленного шаблона.
@@ -1,10 +1,10 @@
---
title: Объекты
description: Declare new record types — custom tables with their own fields — using defineObject.
description: Объявляйте новые типы записей — пользовательские таблицы с собственными полями — с помощью defineObject.
icon: таблица
---
Custom **objects** are new record types your app adds to a workspace — Post Card, Invoice, Subscription, anything specific to your domain. Each object declares its schema (fields, relations, default values) and a stable universal identifier that survives across syncs and deploys.
Пользовательские **объекты** — это новые типы записей, которые ваше приложение добавляет в рабочее пространство — открытка, счёт-фактура, подписка, что‑то специфичное для вашей предметной области. Каждый объект объявляет свою схему (поля, связи, значения по умолчанию) и стабильный универсальный идентификатор, который сохраняется между синхронизациями и деплоями.
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
@@ -79,15 +79,15 @@ export default defineObject({
* `universalIdentifier` должен быть уникальным и стабильным между развёртываниями.
* Каждому полю требуются `name`, `type`, `label` и собственный стабильный `universalIdentifier`.
* Массив `fields` необязателен — вы можете определять объекты без пользовательских полей.
* Inline fields defined here do **not** need an `objectUniversalIdentifier` — it's inherited from the parent object. Use [`defineField()`](/l/ru/developers/extend/apps/data/extending-objects) to add fields to objects you don't own.
* You can scaffold new objects with `yarn twenty add object`, which guides you through naming, fields, and relationships. See [Architecture → Scaffolding entities](/l/ru/developers/extend/apps/getting-started/scaffolding).
* Встроенным полям, определённым здесь, **не** нужен `objectUniversalIdentifier` — он наследуется от родительского объекта. Используйте [`defineField()`](/l/ru/developers/extend/apps/data/extending-objects), чтобы добавить поля к объектам, которые вам не принадлежат.
* Вы можете сгенерировать новые объекты с помощью `yarn twenty add object`, который проведёт вас через выбор именования, полей и связей. См. [Architecture → Scaffolding entities](/l/ru/developers/extend/apps/getting-started/scaffolding).
<Note>
**Base fields are added automatically.** When you define a custom object, Twenty creates standard fields like `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`, and `deletedAt` for you. You don't need to declare them in your `fields` array — only your custom fields. You can override a default field by declaring one with the same name, but this is rarely a good idea.
**Базовые поля добавляются автоматически.** Когда вы определяете пользовательский объект, Twenty создаёт для вас стандартные поля, такие как `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` и `deletedAt`. Вам не нужно объявлять их в массиве `fields` — только ваши пользовательские поля. Вы можете переопределить базовое поле, объявив поле с тем же именем, но это редко бывает хорошей идеей.
</Note>
## Что дальше
* **Connect this object to others** — see [Relations](/l/ru/developers/extend/apps/data/relations) for the bidirectional relation pattern.
* **Add fields to objects from other apps** — see [Extending Objects](/l/ru/developers/extend/apps/data/extending-objects) for `defineField()`.
* **Display this object in the UI** — see [Views](/l/ru/developers/extend/apps/layout/views) and [Navigation Menu Items](/l/ru/developers/extend/apps/layout/navigation-menu-items) to put it in the sidebar.
* **Свяжите этот объект с другими** — см. [Relations](/l/ru/developers/extend/apps/data/relations) для двунаправленного шаблона связей.
* **Добавляйте поля к объектам из других приложений** — см. [Extending Objects](/l/ru/developers/extend/apps/data/extending-objects) по `defineField()`.
* **Отобразите этот объект в интерфейсе** — см. [Views](/l/ru/developers/extend/apps/layout/views) и [Navigation Menu Items](/l/ru/developers/extend/apps/layout/navigation-menu-items), чтобы поместить его в боковую панель.
@@ -1,10 +1,10 @@
---
title: Overview
description: Shape the data your app adds to a workspace — objects, fields, and relations.
title: Обзор
description: Определяйте структуру данных, которые ваше приложение добавляет в рабочее пространство — объекты, поля и связи.
icon: database
---
A Twenty app's **data layer** is the data your app *adds* to a workspace — the new record types it declares, the columns it adds to existing objects, and how those records connect to each other.
**Уровень данных** приложения Twenty — это данные, которые ваше приложение *добавляет* в рабочее пространство: новые типы записей, которые оно объявляет, столбцы, которые оно добавляет к существующим объектам, и то, как эти записи связываются друг с другом.
```text
┌──────────────────────────────────────────────────┐
@@ -23,30 +23,30 @@ A Twenty app's **data layer** is the data your app *adds* to a workspace — the
└──────────────────────────────────────────────────┘
```
## In this section
## В этом разделе
<CardGroup cols={2}>
<Card title="Objects" icon="table" href="/l/ru/developers/extend/apps/data/objects">
`defineObject` — declare new record types with their own fields.
<Card title="Объекты" icon="таблица" href="/l/ru/developers/extend/apps/data/objects">
`defineObject` — объявляйте новые типы записей с их собственными полями.
</Card>
<Card title="Extending Objects" icon="wand-magic-sparkles" href="/l/ru/developers/extend/apps/data/extending-objects">
`defineField` — add fields to standard or other apps' objects.
<Card title="Расширение объектов" icon="wand-magic-sparkles" href="/l/ru/developers/extend/apps/data/extending-objects">
`defineField` — добавляйте поля к стандартным объектам или объектам других приложений.
</Card>
<Card title="Relations" icon="diagram-project" href="/l/ru/developers/extend/apps/data/relations">
Bidirectional `MANY_TO_ONE` / `ONE_TO_MANY` connections between objects.
<Card title="Связи" icon="diagram-project" href="/l/ru/developers/extend/apps/data/relations">
Двусторонние связи `MANY_TO_ONE` / `ONE_TO_MANY` между объектами.
</Card>
</CardGroup>
## Entities at a glance
## Сущности одним взглядом
| Entity | Purpose | Defined with |
| ------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Object** | A new custom record type (e.g. PostCard, Invoice) with its own fields | `defineObject()` |
| **Field** | A column on an object. Standalone fields can extend objects you didn't create (e.g. add `loyaltyTier` to Company) | `defineField()` |
| **Relation** | A bidirectional link between two objects — both sides declared as fields | `defineField()` with `FieldType.RELATION` |
| Сущность | Назначение | Определяется с помощью |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **Объект** | Новый пользовательский тип записей (например, PostCard, Invoice) с собственными полями | `defineObject()` |
| **Поле** | Столбец в объекте. Отдельные поля могут расширять объекты, которые вы не создавали (например, добавить `loyaltyTier` к Company) | `defineField()` |
| **Связь** | Двусторонняя связь между двумя объектами — обе стороны объявлены как поля | `defineField()` с `FieldType.RELATION` |
The SDK detects these via AST analysis at build time, so file organization is up to you — the convention is `src/objects/` and `src/fields/`. Stable `universalIdentifier` UUIDs tie everything together across deploys.
SDK обнаруживает их с помощью анализа AST во время сборки, поэтому организация файлов остается на ваше усмотрение — по соглашению используются `src/objects/` и `src/fields/`. Стабильные UUID `universalIdentifier` связывают все воедино между развертываниями.
<Note>
Looking for **Application Config** or **Roles & Permissions**? Those describe the app itself rather than the data it adds — they live under [Config](/l/ru/developers/extend/apps/config/overview). Looking for **Connections** (Linear, GitHub, Slack OAuth)? Those exist to be called *from* logic functions and live under [Logic](/l/ru/developers/extend/apps/logic/connections).
Ищете **Application Config** или **Roles & Permissions**? Они описывают само приложение, а не данные, которые оно добавляет, — их можно найти в разделе [Config](/l/ru/developers/extend/apps/config/overview). Ищете **Connections** (Linear, GitHub, Slack OAuth)? Они существуют для вызова *из* логических функций и находятся в разделе [Logic](/l/ru/developers/extend/apps/logic/connections).
</Note>
@@ -1,30 +1,30 @@
---
title: Связи
description: Connect objects together with bidirectional MANY_TO_ONE / ONE_TO_MANY relations.
description: Связывайте объекты между собой двунаправленными связями MANY_TO_ONE / ONE_TO_MANY.
icon: diagram-project
---
Relations connect two objects together. In Twenty, relations are always **bidirectional** — every relation has two sides, and each side is declared as a field that references the other.
Отношения связывают два объекта между собой. В Twenty отношения всегда **двунаправленные** — у каждого отношения есть две стороны, и каждая сторона объявляется как поле, ссылающееся на другую.
| Тип отношения | Описание | Есть внешний ключ? |
| ------------- | --------------------------------------------------------------------- | --------------------- |
| `MANY_TO_ONE` | Многие записи этого объекта указывают на одну запись целевого объекта | Да (`joinColumnName`) |
| `ONE_TO_MANY` | Одна запись этого объекта имеет много записей целевого объекта | No (the inverse side) |
| Тип отношения | Описание | Есть внешний ключ? |
| ------------- | --------------------------------------------------------------------- | ---------------------- |
| `MANY_TO_ONE` | Многие записи этого объекта указывают на одну запись целевого объекта | Да (`joinColumnName`) |
| `ONE_TO_MANY` | Одна запись этого объекта имеет много записей целевого объекта | Нет (обратная сторона) |
## How relations work
## Как работают отношения
Every relation requires **two fields** that reference each other:
Каждое отношение требует **двух полей**, которые ссылаются друг на друга:
1. The **MANY_TO_ONE** side — lives on the object that holds the foreign key.
2. The **ONE_TO_MANY** side — lives on the object that owns the collection.
1. Сторона **MANY_TO_ONE** — находится в объекте, который содержит внешний ключ.
2. Сторона **ONE_TO_MANY** — находится в объекте, которому принадлежит коллекция.
Both fields use `FieldType.RELATION` and cross-reference each other via `relationTargetFieldMetadataUniversalIdentifier`.
Оба поля используют `FieldType.RELATION` и ссылаются друг на друга через `relationTargetFieldMetadataUniversalIdentifier`.
## Example: Post Card has many Recipients
## Пример: Почтовая открытка имеет много получателей
A `PostCard` can be sent to many `PostCardRecipient` records. Each recipient belongs to exactly one post card.
`PostCard` может быть отправлен множеству записей `PostCardRecipient`. Каждый получатель относится ровно к одной открытке.
**Step 1: Define the ONE_TO_MANY side on PostCard** (the "one" side):
**Шаг 1: Определите сторону ONE_TO_MANY на PostCard** (сторона "one"):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
@@ -51,7 +51,7 @@ export default defineField({
});
```
**Step 2: Define the MANY_TO_ONE side on PostCardRecipient** (the "many" side — holds the foreign key):
**Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient** (сторона "many" — содержит внешний ключ):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
@@ -81,12 +81,12 @@ export default defineField({
```
<Note>
**Circular imports:** both relation fields reference each other's `universalIdentifier`. To avoid circular import issues, export your field IDs as named constants from each file and import them in the other. The build system resolves these at compile time.
**Циклические импорты:** оба поля отношений ссылаются на `universalIdentifier` друг друга. Чтобы избежать проблем с циклическими импортами, экспортируйте идентификаторы полей как именованные константы из каждого файла и импортируйте их в другом. Система сборки разрешает это на этапе компиляции.
</Note>
## Relating to standard objects
## Связывание со стандартными объектами
To create a relation with a built-in Twenty object (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
@@ -120,20 +120,20 @@ export default defineField({
});
```
## Relation field properties
## Свойства поля отношения
| Property | Required | Description |
| ------------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
| `type` | Yes | Must be `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Yes | The `universalIdentifier` of the target object |
| `relationTargetFieldMetadataUniversalIdentifier` | Yes | The `universalIdentifier` of the matching field on the target object |
| `universalSettings.relationType` | Yes | `RelationType.MANY_TO_ONE` or `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | MANY_TO_ONE only | What happens when the referenced record is deleted: `CASCADE`, `SET_NULL`, `RESTRICT`, or `NO_ACTION` |
| `universalSettings.joinColumnName` | MANY_TO_ONE only | Database column name for the foreign key (e.g., `postCardId`) |
| Свойство | Обязательно | Описание |
| ------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------- |
| `type` | Да | Должно быть `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Да | `universalIdentifier` целевого объекта |
| `relationTargetFieldMetadataUniversalIdentifier` | Да | `universalIdentifier` соответствующего поля на целевом объекте |
| `universalSettings.relationType` | Да | `RelationType.MANY_TO_ONE` или `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | Только для MANY_TO_ONE | Что происходит при удалении связанной записи: `CASCADE`, `SET_NULL`, `RESTRICT` или `NO_ACTION` |
| `universalSettings.joinColumnName` | Только для MANY_TO_ONE | Имя столбца базы данных для внешнего ключа (например, `postCardId`) |
## Inline relation fields
## Встроенные поля связей
You can also declare a relation directly inside [`defineObject`](/l/ru/developers/extend/apps/data/objects). When inline, omit `objectUniversalIdentifier` — it's inherited from the parent object:
Вы также можете объявить связь напрямую внутри [`defineObject`](/l/ru/developers/extend/apps/data/objects). При встроенном объявлении опустите `objectUniversalIdentifier` — он наследуется от родительского объекта:
```ts
export default defineObject({
@@ -1,14 +1,14 @@
---
title: Concepts
description: How Twenty apps work — entity model, sandboxing, and the install lifecycle.
title: Концепции
description: Как работают приложения Twenty — модель сущностей, песочницы и жизненный цикл установки.
icon: sitemap
---
Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls.
Приложения Twenty — это пакеты TypeScript, которые расширяют ваше рабочее пространство пользовательскими объектами, логикой, компонентами интерфейса и возможностями ИИ. Они работают на платформе Twenty с полной изоляцией в песочнице и контролем прав доступа.
## How apps work
## Как работают приложения
An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. These functions validate your configuration at build time and provide IDE autocompletion and type safety.
Приложение — это набор **сущностей**, объявленных с помощью функций `defineEntity()` из пакета `twenty-sdk`. SDK обнаруживает эти объявления посредством анализа AST на этапе сборки и формирует **манифест** — полное описание того, что ваше приложение добавляет в рабочее пространство. Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов.
```
your-app/
@@ -29,35 +29,35 @@ your-app/
```
<Note>
**File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement.
**Организация файлов — на ваше усмотрение.** Обнаружение сущностей основано на AST — SDK находит вызовы `export default defineEntity(...)` независимо от расположения файла. Структура папок выше — это соглашение, а не требование.
</Note>
## Entity types
## Типы сущностей
| Entity | Purpose | Docs |
| ------------------------ | ------------------------------------------ | ----------------------------------------------------------------------------- |
| **Application** | App identity, default role, variables | [Application Config](/l/ru/developers/extend/apps/config/application) |
| **Role** | Permission sets on objects and fields | [Roles & Permissions](/l/ru/developers/extend/apps/config/roles) |
| **Object** | Custom record types with fields | [Objects](/l/ru/developers/extend/apps/data/objects) |
| **Field** | Add fields to objects from other apps | [Extending Objects](/l/ru/developers/extend/apps/data/extending-objects) |
| **Relation** | Bidirectional links between objects | [Relations](/l/ru/developers/extend/apps/data/relations) |
| **Logic Function** | Server-side TypeScript with triggers | [Logic Functions](/l/ru/developers/extend/apps/logic/logic-functions) |
| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/ru/developers/extend/apps/logic/skills-and-agents) |
| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/ru/developers/extend/apps/logic/skills-and-agents) |
| **Connection Provider** | OAuth credentials for third-party APIs | [Connections](/l/ru/developers/extend/apps/logic/connections) |
| **View** | Pre-configured record list views | [Views](/l/ru/developers/extend/apps/layout/views) |
| **Navigation Menu Item** | Custom sidebar entries | [Navigation Menu Items](/l/ru/developers/extend/apps/layout/navigation-menu-items) |
| **Page Layout** | Tabs and widgets on a record's detail page | [Page Layouts](/l/ru/developers/extend/apps/layout/page-layouts) |
| **Front Component** | Sandboxed React UI inside Twenty | [Front Components](/l/ru/developers/extend/apps/layout/front-components) |
| **Command Menu Item** | Quick actions and Cmd+K entries | [Command Menu Items](/l/ru/developers/extend/apps/layout/command-menu-items) |
| Сущность | Назначение | Документация |
| ------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Приложение** | Идентификация приложения, роль по умолчанию, переменные | [Конфигурация приложения](/l/ru/developers/extend/apps/config/application) |
| **Роль** | Наборы прав для объектов и полей | [Роли и права доступа](/l/ru/developers/extend/apps/config/roles) |
| **Объект** | Пользовательские типы записей с полями | [Объекты](/l/ru/developers/extend/apps/data/objects) |
| **Поле** | Добавляйте поля к объектам из других приложений | [Расширение объектов](/l/ru/developers/extend/apps/data/extending-objects) |
| **Связь** | Двунаправленные связи между объектами | [Связи](/l/ru/developers/extend/apps/data/relations) |
| **Логическая функция** | Серверный TypeScript с триггерами | [Логические функции](/l/ru/developers/extend/apps/logic/logic-functions) |
| **Навык** | Повторно используемые инструкции для ИИ-агента | [Навыки и агенты](/l/ru/developers/extend/apps/logic/skills-and-agents) |
| **Агент** | ИИ-агенты с пользовательскими промптами | [Навыки и агенты](/l/ru/developers/extend/apps/logic/skills-and-agents) |
| **Провайдер подключения** | OAuth-учетные данные для сторонних API | [Подключения](/l/ru/developers/extend/apps/logic/connections) |
| **Представление** | Преднастроенные представления списков записей | [Представления](/l/ru/developers/extend/apps/layout/views) |
| **Пункт меню навигации** | Пользовательские элементы боковой панели | [Элементы меню навигации](/l/ru/developers/extend/apps/layout/navigation-menu-items) |
| **Макет страницы** | Вкладки и виджеты на странице сведений о записи | [Макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts) |
| **Компонент фронтенда** | Изолированный в песочнице интерфейс React внутри Twenty | [Компоненты фронтенда](/l/ru/developers/extend/apps/layout/front-components) |
| **Элемент меню команд** | Быстрые действия и элементы Cmd+K | [Элементы меню команд](/l/ru/developers/extend/apps/layout/command-menu-items) |
## Sandboxing
## Изоляция в песочнице
* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions.
* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API.
* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`.
* **Логические функции** выполняются в изолированных процессах Node.js на сервере. Они получают доступ к данным только через типизированный клиент API, ограниченный правами роли приложения.
* **Компоненты фронтенда** запускаются в Web Workers с использованием Remote DOM — изолированы от основной страницы, но при этом рендерят нативные элементы DOM (не iframes). Они взаимодействуют с Twenty через хостовый API обмена сообщениями.
* **Права доступа** применяются на уровне API. Токен времени выполнения (`TWENTY_APP_ACCESS_TOKEN`) выводится из роли, определённой в `defineApplication()`.
## App lifecycle
## Жизненный цикл приложения
```
┌─────────────────────────────────────────────────────────┐
@@ -76,26 +76,26 @@ your-app/
└─────────────────────────────────────────────────────────┘
```
* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes.
* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest.
* **Pre/post-install hooks** — optional functions that run during installation. See [Install Hooks](/l/ru/developers/extend/apps/config/install-hooks) for details.
* **`yarn twenty dev`** — следит за исходными файлами и синхронизирует изменения в реальном времени с подключённым сервером Twenty. Типизированный клиент API автоматически пересоздаётся при изменении схемы.
* **`yarn twenty build`** — компилирует TypeScript, упаковывает логические функции и фронтенд-компоненты с помощью esbuild и формирует манифест.
* **Хуки до/после установки** — необязательные функции, которые выполняются во время установки. См. раздел [Install Hooks](/l/ru/developers/extend/apps/config/install-hooks) для подробностей.
## Next steps
## Следующие шаги
<CardGroup cols={2}>
<Card title="Config" icon="screwdriver-wrench" href="/l/ru/developers/extend/apps/config/overview">
Application identity, default role, and install hooks.
<Card title="Конфигурация" icon="screwdriver-wrench" href="/l/ru/developers/extend/apps/config/overview">
Идентификация приложения, роль по умолчанию и хуки установки.
</Card>
<Card title="Data" icon="database" href="/l/ru/developers/extend/apps/data/overview">
Objects, fields, and bidirectional relations.
<Card title="Данные" icon="database" href="/l/ru/developers/extend/apps/data/overview">
Объекты, поля и двунаправленные связи.
</Card>
<Card title="Logic" icon="bolt" href="/l/ru/developers/extend/apps/logic/overview">
Logic functions, skills, agents, and OAuth connections.
<Card title="Логика" icon="bolt" href="/l/ru/developers/extend/apps/logic/overview">
Логические функции, навыки, агенты и OAuth-подключения.
</Card>
<Card title="Layout" icon="table-columns" href="/l/ru/developers/extend/apps/layout/overview">
Views, navigation, page layouts, front components.
<Card title="Макет" icon="table-columns" href="/l/ru/developers/extend/apps/layout/overview">
Представления, навигация, макеты страниц, фронтенд-компоненты.
</Card>
<Card title="Operations" icon="rocket" href="/l/ru/developers/extend/apps/operations/overview">
CLI, testing, remotes, CI, and publishing your app.
<Card title="Операции" icon="rocket" href="/l/ru/developers/extend/apps/operations/overview">
CLI, тестирование, удаленные окружения, CI и публикация вашего приложения.
</Card>
</CardGroup>
@@ -1,61 +1,61 @@
---
title: Local Server
description: Manage the local Twenty Docker server — start, stop, upgrade, parallel test instance, and manual SDK setup.
title: Локальный сервер
description: Управление локальным сервером Twenty Docker — запуск, остановка, обновление, параллельный тестовый экземпляр и ручная настройка SDK.
icon: server
---
## Managing the local server
## Управление локальным сервером
Use `yarn twenty server` to control the local Twenty container:
Используйте `yarn twenty server` для управления локальным контейнером Twenty:
| Command | What it does |
| -------------------------------------- | -------------------------------------------- |
| `yarn twenty server start` | Start the server (pulls the image if needed) |
| `yarn twenty server start --port 3030` | Start on a custom port |
| `yarn twenty server stop` | Stop the server (preserves data) |
| `yarn twenty server status` | Show URL, version, and login credentials |
| `yarn twenty server logs` | Stream server logs |
| `yarn twenty server reset` | Wipe data and start fresh |
| `yarn twenty server upgrade` | Pull the latest `twenty-app-dev` image |
| `yarn twenty server upgrade 2.2.0` | Upgrade to a specific version |
| Команда | Что делает |
| -------------------------------------- | ---------------------------------------------------- |
| `yarn twenty server start` | Запустить сервер (при необходимости скачивает образ) |
| `yarn twenty server start --port 3030` | Запустить на пользовательском порту |
| `yarn twenty server stop` | Остановить сервер (данные сохраняются) |
| `yarn twenty server status` | Показать URL, версию и учётные данные для входа |
| `yarn twenty server logs` | Потоковый вывод журналов сервера |
| `yarn twenty server reset` | Стереть данные и начать заново |
| `yarn twenty server upgrade` | Скачать последний образ `twenty-app-dev` |
| `yarn twenty server upgrade 2.2.0` | Обновить до конкретной версии |
Data persists across restarts in two Docker volumes (`twenty-app-dev-data` for PostgreSQL, `twenty-app-dev-storage` for files). Use `reset` to wipe everything.
Данные сохраняются между перезапусками в двух томах Docker (`twenty-app-dev-data` для PostgreSQL, `twenty-app-dev-storage` для файлов). Используйте `reset`, чтобы стереть всё.
## Upgrading the server image
## Обновление образа сервера
`yarn twenty server upgrade` pulls the latest image, compares digests, and only recreates the container if anything actually changed. Volumes are preserved — only the container is replaced. If a new image was pulled and the container was running, the upgrade automatically starts a new container; run `yarn twenty server start` afterward to wait for it to become healthy.
`yarn twenty server upgrade` скачивает последний образ, сравнивает дайджесты и пересоздаёт контейнер только если действительно что-то изменилось. Ваши тома данных сохраняются — заменяется только контейнер. Если был скачан новый образ и контейнер работал, при обновлении автоматически запускается новый контейнер; затем выполните `yarn twenty server start`, чтобы дождаться его готовности.
```bash filename="Terminal"
yarn twenty server upgrade # Latest
yarn twenty server upgrade 2.2.0 # Specific version
```
Verify the running version with `yarn twenty server status` (it shows the `APP_VERSION` baked into the container).
Проверьте запущенную версию с помощью `yarn twenty server status` (эта команда показывает `APP_VERSION`, встроенную в контейнер).
## Running a parallel test instance
## Запуск параллельного тестового экземпляра
Pass `--test` to any `server` command to manage a second, fully isolated instance — useful for integration tests or experiments without touching your main dev data:
Передайте `--test` любой команде `server`, чтобы управлять вторым, полностью изолированным экземпляром — это полезно для запуска интеграционных тестов или экспериментов, не затрагивая ваши основные данные разработки.
| Command | What it does |
| ----------------------------------- | ----------------------------------------------- |
| `yarn twenty server start --test` | Start the test instance (defaults to port 2021) |
| `yarn twenty server stop --test` | Stop it |
| `yarn twenty server status --test` | Show its status |
| `yarn twenty server logs --test` | Stream its logs |
| `yarn twenty server reset --test` | Wipe its data |
| `yarn twenty server upgrade --test` | Upgrade its image |
| Команда | Что делает |
| ----------------------------------- | ------------------------------------------------------- |
| `yarn twenty server start --test` | Запустить тестовый экземпляр (по умолчанию — порт 2021) |
| `yarn twenty server stop --test` | Остановить его |
| `yarn twenty server status --test` | Показать его статус |
| `yarn twenty server logs --test` | Транслировать его журналы |
| `yarn twenty server reset --test` | Стереть его данные |
| `yarn twenty server upgrade --test` | Обновить его образ |
The test instance has its own container (`twenty-app-dev-test`), volumes (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`), and config — it runs alongside your main instance without conflicts. Combine `--test` with `--port` to override 2021.
Тестовый экземпляр запускается в собственном контейнере Docker (`twenty-app-dev-test`) с выделенными томами (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`) и собственной конфигурацией, поэтому он может работать параллельно с вашим основным экземпляром без конфликтов. Совместите `--test` с `--port`, чтобы переопределить значение по умолчанию (2021).
## Manual setup (without the scaffolder)
## Ручная настройка (без генератора)
Skip the scaffolder if you're adding the SDK to an existing project:
Пропустите генератор каркаса, если вы добавляете SDK в существующий проект:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
```
Add the script to `package.json`:
Добавьте скрипт в `package.json`:
```json filename="package.json"
{
@@ -65,8 +65,8 @@ Add the script to `package.json`:
}
```
You can now run `yarn twenty dev`, `yarn twenty server start`, and the rest.
Теперь вы можете запускать `yarn twenty dev`, `yarn twenty server start` и остальные команды.
<Note>
Don't install `twenty-sdk` globally — pin it per project so each app uses its own version.
Не устанавливайте `twenty-sdk` глобально — фиксируйте версию в каждом проекте, чтобы каждое приложение использовало свою собственную версию.
</Note>
@@ -1,10 +1,10 @@
---
title: Project Structure
description: What's inside a scaffolded Twenty app — files, folders, and what each one does.
title: Структура проекта
description: Что находится внутри сгенерированного приложения Twenty — файлы, папки и назначение каждого из них.
icon: folder-tree
---
A new app generated by `npx create-twenty-app` looks like this:
Новое приложение, сгенерированное с помощью `npx create-twenty-app`, выглядит так:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -25,16 +25,16 @@ my-twenty-app/
README.md, LLMS.md
```
## Key files
## Ключевые файлы
| File / Folder | Purpose |
| ---------------------------------------- | -------------------------------------------------------------- |
| `src/application-config.ts` | **Required.** The main configuration file for your app. |
| `src/default-role.ts` | Default role controlling what your logic functions can access. |
| `src/constants/universal-identifiers.ts` | Auto-generated UUIDs and metadata (display name, description). |
| `src/__tests__/` | Integration tests (setup + example test). |
| `public/` | Static assets (images, fonts) served with your app. |
| Файл / Папка | Назначение |
| ---------------------------------------- | ------------------------------------------------------------------------------------ |
| `src/application-config.ts` | **Обязательно.** Основной файл конфигурации для вашего приложения. |
| `src/default-role.ts` | Роль по умолчанию, контролирующая, к чему имеют доступ ваши логические функции. |
| `src/constants/universal-identifiers.ts` | Автоматически генерируемые UUID и метаданные (отображаемое имя, описание). |
| `src/__tests__/` | Интеграционные тесты (настройка + пример теста). |
| `public/` | Статические ресурсы (изображения, шрифты), обслуживаемые вместе с вашим приложением. |
<Note>
**File organization is up to you.** The folders above are conventions — the SDK detects entities via AST analysis on `export default defineEntity(...)` calls regardless of where the file lives.
**Организация файлов — на ваше усмотрение.** Приведенные выше папки являются соглашениями — SDK обнаруживает сущности с помощью AST-анализа вызовов `export default defineEntity(...)` независимо от того, где расположен файл.
</Note>
@@ -1,184 +1,184 @@
---
title: Quick Start
title: Быстрый старт
icon: rocket
description: Create your first Twenty app in minutes.
description: Создайте своё первое приложение Twenty за считанные минуты.
---
## Prerequisites
## Требования
* **Node.js 24+** — [Download](https://nodejs.org/)
* **Yarn 4** — bundled with Node via Corepack. Enable it: `corepack enable`
* **Docker** — [Download](https://www.docker.com/products/docker-desktop/). Needed to run a local Twenty server. Skip if you already have Twenty running elsewhere.
* **Node.js 24+** — [Скачать здесь](https://nodejs.org/)
* **Yarn 4** — поставляется вместе с Node.js через Corepack. Включите его, выполнив `corepack enable`
* **Docker** — [Скачать здесь](https://www.docker.com/products/docker-desktop/). Требуется для запуска локального экземпляра Twenty. Пропустите, если у вас уже запущен Twenty в другом месте.
Building a Twenty app has three phases. The scaffolder collapses them into one happy-path command, but each phase is a separate concept — when something fails, knowing which phase you're in tells you what to fix.
Создание приложения Twenty включает три фазы. Генератор каркаса объединяет их в одну команду для идеального сценария (happy path), но каждая фаза — отдельная концепция: когда что-то идёт не так, понимание того, на какой фазе вы находитесь, подскажет, что исправить.
| Phase | What you do | Tool | Result |
| ------------------- | ---------------------------------- | ----------------------------- | ----------------------------- |
| **1. Scaffold** | Generate the app's source code | `npx create-twenty-app` | A TypeScript project on disk |
| **2. Run a server** | Start a Twenty server to sync into | Docker + `yarn twenty server` | A running Twenty instance |
| **3. Sync** | Live-sync your code to the server | `yarn twenty dev` | Your changes appear in the UI |
| Фаза | Что вы делаете | Инструмент | Результат |
| ----------------------- | ------------------------------------------------- | ----------------------------- | -------------------------------------- |
| **1. Создание каркаса** | Сгенерировать исходный код приложения | `npx create-twenty-app` | Проект TypeScript на диске |
| **2. Запустить сервер** | Запустить сервер Twenty для синхронизации | Docker + `yarn twenty server` | Запущенный экземпляр Twenty |
| **3. Синхронизация** | Синхронизируйте код с сервером в реальном времени | `yarn twenty dev` | Ваши изменения появляются в интерфейсе |
---
## Phase 1 — Scaffold your project
## Фаза 1 — Сгенерируйте каркас проекта
Create a new app from the template:
Создайте новое приложение из шаблона:
```bash filename="Terminal"
npx create-twenty-app@latest my-twenty-app
```
You'll be prompted for a name and description — press **Enter** for the defaults. This generates a TypeScript project in `my-twenty-app/` with a starter `application-config.ts`, a default role, a CI workflow, and an integration test.
Вам будет предложено ввести имя и описание — нажмите **Enter**, чтобы принять значения по умолчанию. Это создаст проект TypeScript в `my-twenty-app/` с начальным файлом `application-config.ts`, ролью по умолчанию, рабочим процессом CI и интеграционным тестом.
**After this phase:** you have an app's source code on your machine. It isn't running yet — that's Phase 2.
**После этой фазы:** у вас есть исходный код приложения на вашем компьютере. Он ещё не запущен — это фаза 2.
---
## Phase 2 — Run a local Twenty server
## Фаза 2 — Запустите локальный сервер Twenty
Your app needs a Twenty server to sync into. The server is a full Twenty instance — UI, GraphQL API, PostgreSQL — running locally in Docker. Your local code uploads its definitions to that server, which makes them appear in the UI.
Вашему приложению нужен сервер Twenty для синхронизации. Сервер — это полноценный экземпляр Twenty — UI, GraphQL API, PostgreSQL — работающий локально в Docker. Ваш локальный код загружает свои определения на этот сервер, благодаря чему они появляются в интерфейсе.
The scaffolder offers to start one for you:
Генератор каркаса предложит запустить его за вас:
> **Would you like to set up a local Twenty instance?**
> **Хотите настроить локальный экземпляр Twenty?**
* **Yes (recommended)** — pulls the `twentycrm/twenty-app-dev` Docker image and starts it on port `2020`. Make sure Docker is running first.
* **No** — choose this if you already have a Twenty server you want to connect to. You can wire it up later with `yarn twenty remote add`.
* **Да (рекомендуется)** — скачивает Docker-образ `twentycrm/twenty-app-dev` и запускает его на порту `2020`. Сначала убедитесь, что Docker запущен.
* **Нет** — выберите это, если у вас уже есть сервер Twenty, к которому вы хотите подключиться. Позже вы можете подключить его с помощью `yarn twenty remote add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Should start local instance?" />
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Запустить локальный экземпляр?" />
</div>
Once the server is up, a browser opens for sign-in. Use the pre-seeded demo account:
Когда сервер будет запущен, откроется браузер для входа. Используйте предварительно созданную демонстрационную учётную запись:
* **Email:** `tim@apple.dev`
* **Password:** `tim@apple.dev`
* **Электронная почта:** `tim@apple.dev`
* **Пароль:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty login screen" />
<img src="/images/docs/developers/extends/apps/login.png" alt="Экран входа в Twenty" />
</div>
Click **Authorize** on the next screen — this gives the CLI access to your workspace.
На следующем экране нажмите **Authorize** — это даст CLI доступ к вашему рабочему пространству.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI authorization screen" />
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Экран авторизации Twenty CLI" />
</div>
Your terminal will confirm everything is set up.
В вашем терминале появится подтверждение, что всё настроено.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App scaffolded successfully" />
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Каркас приложения успешно создан" />
</div>
**After this phase:** you have a running Twenty server at [http://localhost:2020](http://localhost:2020) with your CLI authorized to sync to it.
**После этой фазы:** у вас запущен сервер Twenty на [http://localhost:2020](http://localhost:2020), а ваш CLI авторизован для синхронизации с ним.
<Note>
If Docker isn't installed or running, the scaffolder will tell you the right start command for your OS. Once Docker is up, you can resume with `yarn twenty server start` — no need to re-scaffold.
Если Docker не установлен или не запущен, генератор каркаса подскажет правильную команду запуска для вашей ОС. Когда Docker будет запущен, вы можете продолжить с `yarn twenty server start` — заново генерировать каркас не нужно.
</Note>
---
## Phase 3 — Sync your changes
## Фаза 3 — Синхронизируйте свои изменения
This is the inner loop you'll spend most of your time in.
Это внутренний цикл, в котором вы проведёте большую часть времени.
```bash filename="Terminal"
cd my-twenty-app
yarn twenty dev
```
This watches `src/`, rebuilds on every change, and syncs the result to the server. Edit a file, save, and within a second the server reflects the change. You'll see a live status panel in your terminal.
Эта команда отслеживает `src/`, пересобирает при каждом изменении и синхронизирует результат с сервером. Отредактируйте файл, сохраните — и через секунду сервер отразит изменения. В терминале появится панель текущего статуса.
For more detailed output (build logs, sync requests, error traces), add `--verbose`.
Для более подробного вывода (журналы сборки, запросы синхронизации, трассировки ошибок) добавьте `--verbose`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.png" alt="Dev mode terminal output" />
<img src="/images/docs/developers/extends/apps/dev.png" alt="Вывод терминала в режиме разработки" />
</div>
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). You should see your app under **Your Apps**.
Откройте [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). Вы должны увидеть своё приложение в разделе **Your Apps**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps list showing My twenty app" />
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Список Your Apps с приложением My twenty app" />
</div>
Click **My twenty app** to see its **application registration** — a server-level record describing your app (name, identifier, OAuth credentials, source). One registration can be installed across multiple workspaces on the same server.
Нажмите **My twenty app**, чтобы открыть его регистрацию приложения — запись на уровне сервера, описывающую ваше приложение (имя, идентификатор, учётные данные OAuth, источник). Одну и ту же регистрацию можно установить в нескольких рабочих пространствах на одном сервере.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Сведения о регистрации приложения" />
</div>
Click **View installed app** to see the workspace install. The **About** tab shows version and management options.
Нажмите **View installed app**, чтобы посмотреть установку в рабочем пространстве. Вкладка **About** показывает версию и параметры управления.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app" />
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Установленное приложение" />
</div>
**After this phase:** you have a live development loop. Edit any file in `src/` and it appears in the UI.
**После этой фазы:** у вас есть интерактивный цикл разработки. Отредактируйте любой файл в `src/`, и он появится в интерфейсе.
### One-shot sync for CI and scripts
### Разовая синхронизация для CI и скриптов
Pass `--once` to run a single build + sync and exit — same pipeline, no watcher:
Передайте `--once`, чтобы выполнить одну сборку и синхронизацию и завершить работу — тот же конвейер, без наблюдателя:
```bash filename="Terminal"
yarn twenty dev --once
```
| Command | Behavior | When to use |
| ------------------------ | ------------------------------------------------------------- | ---------------------------------------------------- |
| `yarn twenty dev` | Watches and re-syncs on every change. Runs until you stop it. | Interactive local development. |
| `yarn twenty dev --once` | Single build + sync, exits `0` on success, `1` on failure. | CI, pre-commit hooks, AI agents, scripted workflows. |
| Команда | Поведение | Когда использовать |
| ------------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `yarn twenty dev` | Отслеживает и повторно синхронизирует при каждом изменении. Продолжает работать, пока вы его не остановите. | Интерактивная локальная разработка. |
| `yarn twenty dev --once` | Одна сборка и синхронизация, завершает работу с кодом `0` при успехе и `1` при ошибке. | CI, хуки pre-commit, AI-агенты, скриптовые рабочие процессы. |
Both modes need a server in development mode and an authenticated remote.
Оба режима требуют сервер в режиме разработки и аутентифицированный удалённый сервер.
<Warning>
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests — use `yarn twenty deploy` to deploy to production servers. See [Publishing](/l/ru/developers/extend/apps/operations/publishing).
Режим разработки доступен только на экземплярах Twenty, запущенных в режиме разработки (`NODE_ENV=development`). Экземпляры в продакшене отклоняют запросы синхронизации из режима разработки — используйте `yarn twenty deploy` для развёртывания на производственные серверы. См. [Публикация](/l/ru/developers/extend/apps/operations/publishing).
</Warning>
---
## Starting from an example
## Начните с примера
Use `--example` to start with a more complete project (custom objects, fields, logic functions, front components):
Используйте `--example`, чтобы начать с более полного проекта (пользовательские объекты, поля, логические функции, фронтенд-компоненты):
```bash filename="Terminal"
npx create-twenty-app@latest my-twenty-app --example postcard
```
Examples live in [twenty-apps/examples](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples). You can also scaffold individual entities into an existing project with `yarn twenty add` — see [Scaffolding](/l/ru/developers/extend/apps/getting-started/scaffolding).
Примеры берутся из каталога [twenty-apps/examples](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples) на GitHub. Вы также можете сгенерировать каркас отдельных сущностей в существующем проекте с помощью `yarn twenty add` — см. [Scaffolding](/l/ru/developers/extend/apps/getting-started/scaffolding).
---
## What you can build
## Что вы можете создать
Apps are composed of **entities** — each defined as a TypeScript file with a single `export default`:
Приложения состоят из **сущностей** — каждая определена как файл TypeScript с одним `export default`:
| Entity | What it does |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **Objects & Fields** | Custom data models (Post Card, Invoice, etc.) with typed fields |
| **Logic functions** | Server-side TypeScript triggered by HTTP routes, cron schedules, or database events |
| **Front components** | React components that render inside Twenty's UI (side panel, widgets, command menu) |
| **Skills & Agents** | AI capabilities — reusable instructions and autonomous assistants |
| **Views & Navigation** | Pre-configured list views and sidebar menu items |
| **Page layouts** | Custom record detail pages with tabs and widgets |
| Сущность | Что делает |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Объекты и поля** | Пользовательские модели данных (почтовая открытка, счёт и т. д.) с типизированными полями |
| **Логические функции** | Серверный TypeScript, запускаемый HTTP-маршрутами, расписаниями cron или событиями базы данных |
| **Фронтенд-компоненты** | React-компоненты, которые отображаются внутри интерфейса Twenty (боковая панель, виджеты, командное меню) |
| **Навыки и агенты** | Возможности ИИ — многократно используемые инструкции и автономные помощники |
| **Представления и навигация** | Предварительно настроенные представления списков и элементы бокового меню |
| **Макеты страниц** | Пользовательские страницы сведений о записи с вкладками и виджетами |
Full reference: [Concepts](/l/ru/developers/extend/apps/getting-started/concepts).
Полная справка: [Concepts](/l/ru/developers/extend/apps/getting-started/concepts).
## Next steps
## Следующие шаги
<CardGroup cols={2}>
<Card title="Config" icon="screwdriver-wrench" href="/l/ru/developers/extend/apps/config/overview">
Application identity, default role, install hooks, public assets.
<Card title="Конфигурация" icon="screwdriver-wrench" href="/l/ru/developers/extend/apps/config/overview">
Идентификация приложения, роль по умолчанию, хуки установки, публичные ассеты.
</Card>
<Card title="Data" icon="database" href="/l/ru/developers/extend/apps/data/overview">
Objects, fields, and bidirectional relations.
<Card title="Данные" icon="database" href="/l/ru/developers/extend/apps/data/overview">
Объекты, поля и двунаправленные связи.
</Card>
<Card title="Logic" icon="bolt" href="/l/ru/developers/extend/apps/logic/overview">
Logic functions, skills, agents, and OAuth connections.
<Card title="Логика" icon="bolt" href="/l/ru/developers/extend/apps/logic/overview">
Логические функции, скиллы, агенты и OAuth-подключения.
</Card>
<Card title="Layout" icon="table-columns" href="/l/ru/developers/extend/apps/layout/overview">
Views, navigation, page layouts, front components.
<Card title="Макет" icon="table-columns" href="/l/ru/developers/extend/apps/layout/overview">
Представления, навигация, макеты страниц, фронтовые компоненты.
</Card>
<Card title="Operations" icon="rocket" href="/l/ru/developers/extend/apps/operations/overview">
CLI, testing, remotes, CI, and publishing your app.
<Card title="Операции" icon="rocket" href="/l/ru/developers/extend/apps/operations/overview">
CLI, тестирование, ремоуты, CI и публикация вашего приложения.
</Card>
</CardGroup>
@@ -1,18 +1,18 @@
---
title: Scaffolding
description: Generate entity files interactively with yarn twenty add — objects, fields, views, logic functions, and more.
title: Создание каркаса
description: Интерактивно генерируйте файлы сущностей с помощью yarn twenty add — объекты, поля, представления, логические функции и многое другое.
icon: wand-magic-sparkles
---
Instead of creating entity files by hand, use the interactive scaffolder:
Вместо ручного создания файлов сущностей используйте интерактивный генератор:
```bash filename="Terminal"
yarn twenty add
```
It prompts you to pick an entity type and walks you through the required fields, then writes a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call.
Он предлагает выбрать тип сущности и пошагово проводит через обязательные поля, а затем записывает готовый к использованию файл со стабильным `universalIdentifier` и корректным вызовом `defineEntity()`.
You can also pass the entity type directly to skip the first prompt:
Вы также можете передать тип сущности напрямую, чтобы пропустить первый запрос:
```bash filename="Terminal"
yarn twenty add object
@@ -20,38 +20,38 @@ yarn twenty add logicFunction
yarn twenty add frontComponent
```
## Available entity types
## Доступные типы сущностей
| Entity type | Command | Generated file |
| Тип сущности | Команда | Сгенерированный файл |
| -------------------- | ------------------------------------ | ------------------------------------------------------- |
| Object | `yarn twenty add object` | `src/objects/\<name>.ts` |
| Field | `yarn twenty add field` | `src/fields/\<name>.ts` |
| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
| Front component | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
| Role | `yarn twenty add role` | `src/roles/\<name>.ts` |
| Skill | `yarn twenty add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty add agent` | `src/agents/\<name>.ts` |
| View | `yarn twenty add view` | `src/views/\<name>.ts` |
| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
| Объект | `yarn twenty add object` | `src/objects/\<name>.ts` |
| Поле | `yarn twenty add field` | `src/fields/\<name>.ts` |
| Логическая функция | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
| Компонент фронтенда | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
| Роль | `yarn twenty add role` | `src/roles/\<name>.ts` |
| Навык | `yarn twenty add skill` | `src/skills/\<name>.ts` |
| Агент | `yarn twenty add agent` | `src/agents/\<name>.ts` |
| Представление | `yarn twenty add view` | `src/views/\<name>.ts` |
| Пункт меню навигации | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Макет страницы | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
## What the scaffolder generates
## Что генерирует скэффолдер
Each entity type has its own template. For example, `yarn twenty add object` asks for:
У каждого типа сущности есть свой шаблон. Например, `yarn twenty add object` запрашивает:
1. **Name (singular)** — e.g., `invoice`
2. **Name (plural)** — e.g., `invoices`
3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`)
4. **Label (plural)** — auto-populated (e.g., `Invoices`)
5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object.
1. **Имя (единственное число)** — например, `invoice`
2. **Имя (множественное число)** — например, `invoices`
3. **Метка (единственное число)** — заполняется автоматически из имени (например, `Invoice`)
4. **Метка (множественное число)** — заполняется автоматически (например, `Invoices`)
5. **Создать представление и пункт навигации?** — если вы ответите «да», скэффолдер также сгенерирует соответствующее представление и ссылку в боковой панели для нового объекта.
Other entity types have simpler prompts — most only ask for a name.
У других типов сущностей подсказки проще — в большинстве случаев запрашивается только имя.
The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`.
Тип сущности `field` более детализирован: он запрашивает имя поля, метку, тип (из списка всех доступных типов полей, таких как `TEXT`, `NUMBER`, `SELECT`, `RELATION` и т. д.), а также `universalIdentifier` целевого объекта.
## Custom output path
## Пользовательский путь вывода
Use the `--path` flag to place the generated file in a custom location:
Используйте флаг `--path`, чтобы поместить сгенерированный файл в пользовательское расположение:
```bash filename="Terminal"
yarn twenty add logicFunction --path src/custom-folder
@@ -1,10 +1,10 @@
---
title: Command Menu Items
description: Surface front components as quick actions and command menu (Cmd+K) entries with defineCommandMenuItem.
title: Элементы меню команд
description: Выводите front-компоненты как быстрые действия и элементы командного меню (Cmd+K) с помощью `defineCommandMenuItem`.
icon: terminal
---
A **command menu item** is the bridge between the user and a [front component](/l/ru/developers/extend/apps/layout/front-components). It registers the component in Twenty's command menu (Cmd+K) and, optionally, as a pinned quick-action button in the top-right corner of the page.
**Элемент командного меню** — это мост между пользователем и [front-компонентом](/l/ru/developers/extend/apps/layout/front-components). Он регистрирует компонент в командном меню Twenty (Cmd+K) и, при необходимости, как закреплённую кнопку быстрого действия в правом верхнем углу страницы.
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -20,25 +20,25 @@ export default defineCommandMenuItem({
});
```
## Configuration fields
## Поля конфигурации
| Field | Required | Description |
| --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | Yes | Stable unique ID for the command |
| `label` | Yes | Full label shown in the command menu (Cmd+K) |
| `frontComponentUniversalIdentifier` | Yes | The `universalIdentifier` of the front component this command opens |
| `shortLabel` | No | Shorter label displayed on the pinned quick-action button |
| `icon` | No | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) |
| `isPinned` | No | When `true`, shows the command as a quick-action button in the top-right corner of the page |
| `availabilityType` | No | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) |
| `availabilityObjectUniversalIdentifier` | No | Restrict the command to pages of a specific object type (e.g. only on Company records) |
| `conditionalAvailabilityExpression` | No | A boolean expression that dynamically controls visibility (see below) |
| Поле | Обязательно | Описание |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Да | Стабильный уникальный идентификатор для команды |
| `label` | Да | Полная метка, отображаемая в меню команд (Cmd+K) |
| `frontComponentUniversalIdentifier` | Да | `universalIdentifier` фронтенд-компонента, который открывается этой командой |
| `shortLabel` | Нет | Короткая метка, отображаемая на закреплённой кнопке быстрого действия |
| `icon` | Нет | Имя значка, отображаемое рядом с меткой (например, `'IconBolt'`, `'IconSend'`) |
| `isPinned` | Нет | При значении `true` показывает команду как кнопку быстрого действия в правом верхнем углу страницы |
| `availabilityType` | Нет | Определяет, где отображается команда: `'GLOBAL'` (доступна всегда), `'RECORD_SELECTION'` (только при выборе записей) или `'FALLBACK'` (показывается, когда другие команды не подходят) |
| `availabilityObjectUniversalIdentifier` | Нет | Ограничивает команду страницами определённого типа объектов (например, только для записей Company) |
| `conditionalAvailabilityExpression` | Нет | Логическое выражение, которое динамически управляет видимостью (см. ниже) |
## Headless commands
## Команды без интерфейса
A command menu item paired with a [headless front component](/l/ru/developers/extend/apps/layout/front-components#headless-vs-non-headless) is the idiomatic way to ship a one-click action — run code, navigate, or confirm and execute. The Front Components page covers the [SDK Command components](/l/ru/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) that handle the action-and-unmount pattern.
Элемент командного меню в паре с [front-компонентом без интерфейса](/l/ru/developers/extend/apps/layout/front-components#headless-vs-non-headless) — идиоматичный способ предоставить действие в один клик: выполнить код, перейти по навигации или подтвердить и выполнить действие. Страница Front Components описывает [SDK Command components](/l/ru/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), которые реализуют шаблон «действие и размонтирование».
A typical flow:
Типичный поток:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -79,9 +79,9 @@ export default defineCommandMenuItem({
});
```
## Conditional availability expressions
## Выражения условной доступности
The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions:
Поле `conditionalAvailabilityExpression` позволяет управлять видимостью команды в зависимости от текущего контекста страницы. Импортируйте типизированные переменные и операторы из `twenty-sdk`, чтобы составлять выражения:
```ts src/command-menu-items/bulk-update.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -103,42 +103,42 @@ export default defineCommandMenuItem({
});
```
### Context variables
### Переменные контекста
These represent the current state of the page:
Они представляют текущее состояние страницы:
| Variable | Type | Description |
| ------------------------------ | --------- | ---------------------------------------------------------------- |
| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) |
| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel |
| `numberOfSelectedRecords` | `number` | Number of currently selected records |
| `isSelectAll` | `boolean` | Whether "select all" is active |
| `selectedRecords` | `array` | The selected record objects |
| `favoriteRecordIds` | `array` | IDs of favorited records |
| `objectPermissions` | `object` | Permissions for the current object type |
| `targetObjectReadPermissions` | `object` | Read permissions for the target object |
| `targetObjectWritePermissions` | `object` | Write permissions for the target object |
| `featureFlags` | `object` | Active feature flags |
| `objectMetadataItem` | `object` | Metadata of the current object type |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter |
| Переменная | Тип | Описание |
| ------------------------------ | --------- | ------------------------------------------------------------------------ |
| `pageType` | `string` | Текущий тип страницы (например, `'RecordIndexPage'`, `'RecordShowPage'`) |
| `isInSidePanel` | `boolean` | Указывает, рендерится ли компонент в боковой панели |
| `numberOfSelectedRecords` | `number` | Количество выбранных в данный момент записей |
| `isSelectAll` | `boolean` | Активен ли режим "выбрать все" |
| `selectedRecords` | `array` | Объекты выбранных записей |
| `favoriteRecordIds` | `array` | ID избранных записей |
| `objectPermissions` | `object` | Разрешения для текущего типа объекта |
| `targetObjectReadPermissions` | `object` | Права на чтение для целевого объекта |
| `targetObjectWritePermissions` | `object` | Права на запись для целевого объекта |
| `featureFlags` | `object` | Активные флаги функций |
| `objectMetadataItem` | `object` | Метаданные текущего типа объекта |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Есть ли у текущего представления фильтр мягкого удаления |
### Operators
### Операторы
Combine variables into boolean expressions:
Комбинируйте переменные в логические выражения:
| Operator | Description |
| ----------------------------------- | ----------------------------------------------------------------- |
| `isDefined(value)` | `true` if the value is not null/undefined |
| `isNonEmptyString(value)` | `true` if the value is a non-empty string |
| `includes(array, value)` | `true` if the array contains the value |
| `includesEvery(array, prop, value)` | `true` if every item's property includes the value |
| `every(array, prop)` | `true` if the property is truthy on every item |
| `everyDefined(array, prop)` | `true` if the property is defined on every item |
| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item |
| `some(array, prop)` | `true` if the property is truthy on at least one item |
| `someDefined(array, prop)` | `true` if the property is defined on at least one item |
| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item |
| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item |
| `none(array, prop)` | `true` if the property is falsy on every item |
| `noneDefined(array, prop)` | `true` if the property is undefined on every item |
| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item |
| Оператор | Описание |
| ----------------------------------- | ------------------------------------------------------------------------- |
| `isDefined(value)` | `true`, если значение не null/undefined |
| `isNonEmptyString(value)` | `true`, если значение — непустая строка |
| `includes(array, value)` | `true`, если массив содержит значение |
| `includesEvery(array, prop, value)` | `true`, если свойство каждого элемента включает значение |
| `every(array, prop)` | `true`, если свойство истинно для каждого элемента |
| `everyDefined(array, prop)` | `true`, если свойство определено у каждого элемента |
| `everyEquals(array, prop, value)` | `true`, если свойство равно значению у каждого элемента |
| `some(array, prop)` | `true`, если свойство истинно хотя бы у одного элемента |
| `someDefined(array, prop)` | `true`, если свойство определено хотя бы у одного элемента |
| `someEquals(array, prop, value)` | `true`, если свойство равно значению хотя бы у одного элемента |
| `someNonEmptyString(array, prop)` | `true`, если свойство является непустой строкой хотя бы у одного элемента |
| `none(array, prop)` | `true`, если свойство ложно для каждого элемента |
| `noneDefined(array, prop)` | `true`, если свойство не определено ни у одного элемента |
| `noneEquals(array, prop, value)` | `true`, если свойство не равно значению ни у одного элемента |
@@ -1,26 +1,26 @@
---
title: Front Components
description: Build React components that render inside Twenty's UI with sandboxed isolation.
title: Компоненты фронтенда
description: Создавайте компоненты React, которые отображаются внутри интерфейса Twenty в изолированной песочнице.
icon: window-maximize
---
Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe.
Фронтенд-компоненты — это компоненты React, которые отображаются непосредственно внутри интерфейса Twenty. Они выполняются в изолированном Web Worker с использованием Remote DOM — ваш код изолирован (sandboxed), но рендерится нативно на странице, а не в iframe.
## Where front components can be used
## Где можно использовать фронт-компоненты
Front components can render in two locations within Twenty:
Фронт-компоненты могут отображаться в двух местах внутри Twenty:
* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu.
* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside [page layouts](/l/ru/developers/extend/apps/layout/page-layouts). When configuring a dashboard or a record page layout, users can add a front component widget.
* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд.
* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts). При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента.
A front component on its own isn't reachable from the UI — you need to *surface* it. The two ways to do that are:
Сам по себе фронт-компонент недоступен из интерфейса — его нужно *сделать доступным*. Сделать это можно двумя способами:
* **Pair it with a [command menu item](/l/ru/developers/extend/apps/layout/command-menu-items)** — registers it in the command menu (Cmd+K) and, optionally, as a pinned quick-action.
* **Embed it as a widget in a [page layout](/l/ru/developers/extend/apps/layout/page-layouts)** — places it on a record's detail page or dashboard.
* **Связать его с [элементом командного меню](/l/ru/developers/extend/apps/layout/command-menu-items)** — регистрирует его в командном меню (Cmd+K) и, при необходимости, как закреплённое быстрое действие.
* **Встроить его как виджет в [макет страницы](/l/ru/developers/extend/apps/layout/page-layouts)** — размещает его на странице деталей записи или на дашборде.
## Basic example
## Простой пример
The quickest way to see a front component in action is to pair it with a [`defineCommandMenuItem`](/l/ru/developers/extend/apps/layout/command-menu-items), so it appears as a quick-action button in the top-right corner of the page:
Самый быстрый способ увидеть фронт-компонент в действии — связать его с [`defineCommandMenuItem`](/l/ru/developers/extend/apps/layout/command-menu-items), чтобы он появился как кнопка быстрого действия в правом верхнем углу страницы:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -56,35 +56,35 @@ export default defineCommandMenuItem({
});
```
After syncing with `yarn twenty dev` (or running a one-shot `yarn twenty dev --once`), the quick action appears in the top-right corner of the page:
После синхронизации с помощью `yarn twenty dev` (или однократного запуска `yarn twenty dev --once`) быстрое действие появится в правом верхнем углу страницы:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Quick action button in the top-right corner" />
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Кнопка быстрого действия в правом верхнем углу" />
</div>
Click it to render the component inline.
Нажмите её, чтобы отобразить компонент инлайн.
## Configuration fields
## Поля конфигурации
| Field | Required | Description |
| --------------------- | -------- | ------------------------------------------------------------ |
| `universalIdentifier` | Yes | Stable unique ID for this component |
| `component` | Yes | A React component function |
| `name` | No | Display name |
| `description` | No | Description of what the component does |
| `isHeadless` | No | Set to `true` if the component has no visible UI (see below) |
| Поле | Обязательно | Описание |
| --------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Да | Стабильный уникальный идентификатор для этого компонента |
| `component` | Да | Функция компонента React |
| `name` | Нет | Отображаемое имя |
| `description` | Нет | Описание того, что делает компонент |
| `isHeadless` | Нет | Установите значение `true`, если у компонента нет видимого пользовательского интерфейса (см. ниже) |
## Placing a front component on a page
## Размещение фронт-компонента на странице
Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See [Page Layouts](/l/ru/developers/extend/apps/layout/page-layouts) for details.
Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в **макет страницы**. См. [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts) для подробностей.
## Headless vs non-headless
## Headless и non-headless
Front components come in two rendering modes controlled by the `isHeadless` option:
Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`:
**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted.
**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен.
**Headless (`isHeadless: true`)** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below.
**Headless (`isHeadless: true`)** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже.
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -110,20 +110,20 @@ export default defineFrontComponent({
});
```
Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API.
Поскольку компонент возвращает `null`, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом.
## SDK Command components
## Компоненты SDK Command
The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done.
Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении.
Import them from `twenty-sdk/command`:
Импортируйте их из `twenty-sdk/command`:
* **`Command`** — Runs an async callback via the `execute` prop.
* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`.
* **`Command`** — запускает асинхронный колбэк через проп `execute`.
* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — открывает конкретную страницу боковой панели. Пропы: `page`, `pageTitle`, `pageIcon`.
Here is a full example of a headless front component using `Command` to run an action from the command menu:
Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -165,7 +165,7 @@ export default defineCommandMenuItem({
});
```
And an example using `CommandModal` to ask for confirmation before executing:
А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением:
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -196,9 +196,9 @@ export default defineFrontComponent({
});
```
## Accessing runtime context
## Доступ к контексту времени выполнения
Inside your component, use SDK hooks to access the current user, record, and component instance:
Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента:
```tsx src/front-components/record-info.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -229,31 +229,31 @@ export default defineFrontComponent({
});
```
Available hooks:
Доступные хуки:
| Hook | Returns | Description |
| --------------------------------------------- | ------------------ | ---------------------------------------------------------- |
| `useUserId()` | `string` or `null` | The current user's ID |
| `useSelectedRecordIds()` | `string[]` | All selected record IDs (empty array if none selected) |
| `useRecordId()` | `string` or `null` | **Deprecated.** Use `useSelectedRecordIds()` instead |
| `useFrontComponentId()` | `string` | This component instance's ID |
| `useFrontComponentExecutionContext(selector)` | varies | Access the full execution context with a selector function |
| Хук | Возвращает | Описание |
| --------------------------------------------- | ------------------- | ---------------------------------------------------------------------------- |
| `useUserId()` | `string` или `null` | ID текущего пользователя |
| `useSelectedRecordIds()` | `string[]` | Все выбранные идентификаторы записей (пустой массив, если ничего не выбрано) |
| `useRecordId()` | `string` или `null` | **Устарело.** Используйте `useSelectedRecordIds()` вместо этого |
| `useFrontComponentId()` | `string` | ID этого экземпляра компонента |
| `useFrontComponentExecutionContext(selector)` | различается | Доступ к полному контексту выполнения с помощью функции-селектора |
## Host communication API
## API взаимодействия с хостом
Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`:
Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`:
| Function | Description |
| ----------------------------------------------- | ----------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app |
| `openSidePanelPage(params)` | Open a side panel |
| `closeSidePanel()` | Close the side panel |
| `openCommandConfirmationModal(params)` | Show a confirmation dialog |
| `enqueueSnackbar(params)` | Show a toast notification |
| `unmountFrontComponent()` | Unmount the component |
| `updateProgress(progress)` | Update a progress indicator |
| Функция | Описание |
| ----------------------------------------------- | -------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Перейти на страницу в приложении |
| `openSidePanelPage(params)` | Открыть боковую панель |
| `closeSidePanel()` | Закрыть боковую панель |
| `openCommandConfirmationModal(params)` | Показать диалог подтверждения |
| `enqueueSnackbar(params)` | Показать всплывающее уведомление |
| `unmountFrontComponent()` | Размонтировать компонент |
| `updateProgress(progress)` | Обновить индикатор прогресса |
Here is an example that uses the host API to show a snackbar and close the side panel after an action completes:
Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия:
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -298,9 +298,9 @@ export default defineFrontComponent({
});
```
### Working with multiple records
### Работа с несколькими записями
Use `useSelectedRecordIds()` to handle multiple selected records. This is useful for bulk operations:
Используйте `useSelectedRecordIds()` для обработки нескольких выбранных записей. Это полезно для массовых операций:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -353,9 +353,9 @@ export default defineFrontComponent({
});
```
## Public assets
## Публичные ресурсы
Front components can access files from the app's `public/` directory using `getPublicAssetUrl`:
Компоненты фронтенда могут получать доступ к файлам из каталога приложения `public/` с помощью `getPublicAssetUrl`:
```tsx
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
@@ -369,18 +369,18 @@ export default defineFrontComponent({
});
```
See the [public assets section](/l/ru/developers/extend/apps/config/public-assets) for details.
См. [раздел о публичных ресурсах](/l/ru/developers/extend/apps/config/public-assets) для подробностей.
## Styling
## Стилизация
Front components support multiple styling approaches. You can use:
Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать:
* **Inline styles** — `style={{ color: 'red' }}`
* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more)
* **Emotion** — CSS-in-JS with `@emotion/react`
* **Styled-components** — `styled.div` patterns
* **Tailwind CSS** — utility classes
* **Any CSS-in-JS library** compatible with React
* **Встроенные стили** — `style={{ color: 'red' }}`
* **Компоненты Twenty UI** — импорт из `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar и другие)
* **Emotion** — CSS-in-JS с `@emotion/react`
* **Styled-components** — паттерны `styled.div`
* **Tailwind CSS** — утилитарные классы
* **Любая библиотека CSS-in-JS**, совместимая с React
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -1,10 +1,10 @@
---
title: Navigation Menu Items
description: Add custom entries to the workspace sidebar — links to saved views or external URLs.
title: Элементы навигационного меню
description: Добавляйте на боковую панель рабочего пространства пользовательские элементы — ссылки на сохраненные представления или внешние URL-адреса.
icon: меню
---
A **navigation menu item** is an entry in the left sidebar. Use `defineNavigationMenuItem()` to ship custom sidebar links — typically one per [view](/l/ru/developers/extend/apps/layout/views) you ship — or to point at external URLs.
**Элемент навигационного меню** — это запись в левой боковой панели. Используйте `defineNavigationMenuItem()` для добавления пользовательских ссылок на боковую панель — обычно по одной на каждое [представление](/l/ru/developers/extend/apps/layout/views), которое вы поставляете, — или для указания на внешние URL-адреса.
```ts src/navigation-menu-items/example-navigation-menu-item.ts
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
@@ -23,22 +23,22 @@ export default defineNavigationMenuItem({
## Основные моменты
* `type` determines what the menu item links to. Each type pairs with a specific identifier field:
* `type` определяет, на что указывает элемент меню. Каждый тип сопоставляется с определенным полем идентификатора:
| Тип | Что делает | Required field |
| ------------------------------------ | ------------------------------------ | ----------------------------------------------------------------------------- |
| `NavigationMenuItemType.VIEW` | Opens a saved view | `viewUniversalIdentifier` |
| `NavigationMenuItemType.LINK` | Opens an external URL | `link` |
| `NavigationMenuItemType.FOLDER` | Groups nested items under a label | `name` (and child items reference the folder via `folderUniversalIdentifier`) |
| `NavigationMenuItemType.OBJECT` | Opens an object's default index page | `targetObjectUniversalIdentifier` |
| `NavigationMenuItemType.PAGE_LAYOUT` | Opens a standalone page layout | `pageLayoutUniversalIdentifier` |
| Тип | Что делает | Обязательное поле |
| ------------------------------------ | ----------------------------------------------- | --------------------------------------------------------------------------------- |
| `NavigationMenuItemType.VIEW` | Открывает сохраненное представление | `viewUniversalIdentifier` |
| `NavigationMenuItemType.LINK` | Открывает внешний URL-адрес | `link` |
| `NavigationMenuItemType.FOLDER` | Группирует вложенные элементы под меткой | `name` (а дочерние элементы ссылаются на папку через `folderUniversalIdentifier`) |
| `NavigationMenuItemType.OBJECT` | Открывает страницу индекса по умолчанию объекта | `targetObjectUniversalIdentifier` |
| `NavigationMenuItemType.PAGE_LAYOUT` | Открывает отдельный макет страницы | `pageLayoutUniversalIdentifier` |
* `position` controls ordering in the sidebar.
* `position` управляет порядком в боковой панели.
* `icon` and `color` are optional and customize how the entry looks.
* `icon` и `color` являются необязательными и настраивают внешний вид элемента.
* `folderUniversalIdentifier` is also available on any item to nest it inside a `FOLDER`-type parent.
* `folderUniversalIdentifier` также доступен для любого элемента, чтобы поместить его внутрь родительского элемента типа `FOLDER`.
<Note>
**Common pitfall:** creating an object without an associated view + navigation menu item makes that object invisible to users. Unless it's a technical/internal object, every custom object should have a default view *and* a sidebar entry pointing at it.
**Распространенная ошибка:** создание объекта без связанного представления и элемента навигационного меню делает этот объект невидимым для пользователей. Если только это не технический/внутренний объект, у каждого настраиваемого объекта должно быть представление по умолчанию *и* элемент боковой панели, указывающий на него.
</Note>
@@ -1,10 +1,10 @@
---
title: Overview
description: Place your app inside Twenty's UI — sidebar entries, saved views, record page tabs, and sandboxed React components.
title: Обзор
description: Разместите своё приложение внутри интерфейса Twenty — элементы боковой панели, сохранённые представления, вкладки страницы записи и изолированные React‑компоненты.
icon: table-columns
---
A Twenty app's **layout layer** is everything the user sees: where the app surfaces in the sidebar, which list views it ships, how its record detail pages are arranged, and which custom React components render inside those pages.
**Слой компоновки** приложения Twenty — это всё, что видит пользователь: где приложение отображается в боковой панели, какие списки представлений оно поставляет, как устроены его страницы деталей записей и какие пользовательские React‑компоненты отображаются внутри этих страниц.
```text
Sidebar Record list Record detail page
@@ -23,34 +23,34 @@ A Twenty app's **layout layer** is everything the user sees: where the app surfa
and filters └─────────────────────┘
```
## In this section
## В этом разделе
<CardGroup cols={2}>
<Card title="Views" icon="list" href="/l/ru/developers/extend/apps/layout/views">
`defineView` — saved list configurations: visible columns, filters, groups.
<Card title="Представления" icon="список" href="/l/ru/developers/extend/apps/layout/views">
`defineView` — сохранённые конфигурации списков: видимые столбцы, фильтры, группы.
</Card>
<Card title="Navigation Menu Items" icon="bars" href="/l/ru/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — sidebar entries pointing at views or external URLs.
<Card title="Элементы меню навигации" icon="меню" href="/l/ru/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — элементы боковой панели, ссылающиеся на представления или внешние URL.
</Card>
<Card title="Page Layouts" icon="table-columns" href="/l/ru/developers/extend/apps/layout/page-layouts">
`definePageLayout` and `definePageLayoutTab` — tabs and widgets on a record's detail page.
<Card title="Макеты страниц" icon="table-columns" href="/l/ru/developers/extend/apps/layout/page-layouts">
`definePageLayout` и `definePageLayoutTab` — вкладки и виджеты на странице деталей записи.
</Card>
<Card title="Front Components" icon="window-maximize" href="/l/ru/developers/extend/apps/layout/front-components">
`defineFrontComponent` — sandboxed React components that render inside Twenty.
<Card title="Компоненты фронтенда" icon="window-maximize" href="/l/ru/developers/extend/apps/layout/front-components">
`defineFrontComponent` — изолированные React‑компоненты, которые отображаются внутри Twenty.
</Card>
<Card title="Command Menu Items" icon="terminal" href="/l/ru/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — register front components as Cmd+K entries and quick actions.
<Card title="Элементы меню команд" icon="terminal" href="/l/ru/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — регистрирует фронтенд‑компоненты как элементы Cmd+K и быстрые действия.
</Card>
</CardGroup>
## Where the app surfaces
## Где отображается приложение
| Surface | What it controls | Entity |
| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
| **Sidebar** | A custom entry linking to a saved view or external URL | `defineNavigationMenuItem` |
| **Record list** | A saved configuration for an object — visible columns, order, filters, groups | `defineView` |
| **Record detail page** | The tabs and widgets on a record page (your own object's, or a standard one) | `definePageLayout`, `definePageLayoutTab` |
| **Inside any of the above** | A custom React widget — buttons, forms, dashboards, integrations | `defineFrontComponent` |
| **Command menu (Cmd+K)** | A pinned quick action or hidden command | `defineCommandMenuItem` |
| Область отображения | Что определяет | Сущность |
| ---------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Боковая панель** | Пользовательский элемент, который ссылается на сохранённое представление или внешний URL | `defineNavigationMenuItem` |
| **Список записей** | Сохранённая конфигурация для объекта — видимые столбцы, порядок, фильтры, группы | `defineView` |
| **Страница деталей записи** | Вкладки и виджеты на странице записи (вашего собственного объекта или стандартного) | `definePageLayout`, `definePageLayoutTab` |
| **Внутри любого из перечисленного выше** | Пользовательский React‑виджет — кнопки, формы, панели мониторинга, интеграции | `defineFrontComponent` |
| **Меню команд (Cmd+K)** | Закреплённое быстрое действие или скрытая команда | `defineCommandMenuItem` |
Front components run inside an isolated Web Worker using Remote DOM — they render *natively* in the page (not inside an iframe), but cannot reach the host page or DOM directly. Communication with Twenty happens through a message-passing host API.
Фронтенд‑компоненты выполняются внутри изолированного Web Worker с использованием Remote DOM — они отображаются *нативно* на странице (не внутри iframe), но не могут напрямую обращаться к хостовой странице или DOM. Взаимодействие с Twenty происходит через хостовый API обмена сообщениями.
@@ -1,19 +1,19 @@
---
title: Page Layouts
description: Customize record detail pages — tabs, widgets, and where front components render — using definePageLayout and definePageLayoutTab.
title: Макеты страниц
description: Настраивайте страницы деталей записей — вкладки, виджеты и места, где отображаются front components, — с помощью `definePageLayout` и `definePageLayoutTab`.
icon: table-columns
---
A **page layout** controls how a record's detail page is arranged: which tabs appear and what widgets they contain. Use `definePageLayout()` to declare a layout for an object you own, or `definePageLayoutTab()` to add a single tab to a layout that already exists (yours or a standard Twenty one).
**Макет страницы** управляет тем, как устроена страница деталей записи: какие вкладки отображаются и какие виджеты они содержат. Используйте `definePageLayout()` для объявления макета для объекта, которым вы владеете, или `definePageLayoutTab()` для добавления одной вкладки к макету, который уже существует (вашему или стандартному Twenty).
| Use case | Entity |
| ---------------------------------------------------------------------- | --------------------- |
| Define the entire layout for a record page on an object you own | `definePageLayout` |
| Add one tab to an existing layout (your own object, or a standard one) | `definePageLayoutTab` |
| Сценарий использования | Сущность |
| --------------------------------------------------------------------------------------------- | --------------------- |
| Определите весь макет для страницы записи на объекте, которым вы владеете | `definePageLayout` |
| Добавьте одну вкладку в существующий макет (для вашего собственного объекта или стандартного) | `definePageLayoutTab` |
## definePageLayout
Use this when you own the entire detail page — typically for a custom object you defined yourself.
Используйте это, когда вы управляете всей страницей деталей — обычно для пользовательского объекта, который вы определили сами.
```ts src/page-layouts/example-record-page-layout.ts
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
@@ -49,17 +49,17 @@ export default definePageLayout({
});
```
### Key points
### Основные моменты
* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object.
* `objectUniversalIdentifier` specifies which object this layout applies to.
* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout).
* Each `widget` inside a tab can render a [front component](/l/ru/developers/extend/apps/layout/front-components), a relation list, or other built-in widget types.
* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones.
* `type` обычно равен `'RECORD_PAGE'` для настройки детального представления конкретного объекта.
* `objectUniversalIdentifier` указывает, к какому объекту применяется этот макет.
* Каждая `tab` определяет раздел страницы с `title`, `position` и `layoutMode` (`CANVAS` для свободного макета).
* Каждый `widget` внутри вкладки может отображать [front component](/l/ru/developers/extend/apps/layout/front-components), список связей или другие встроенные типы виджетов.
* `position` у вкладок управляет их порядком. Используйте большие значения (например, 50), чтобы разместить пользовательские вкладки после встроенных.
## definePageLayoutTab
Use this when you only want to **add** a tab to an existing layout — for example, an analytics tab on the standard Company page, or an AI summary tab attached to your own object's layout.
Используйте это, когда вы хотите только **добавить** вкладку к существующему макету — например, вкладку аналитики на стандартной странице Company или вкладку с AI-сводкой, прикреплённую к макету вашего собственного объекта.
```ts src/page-layouts/example-extra-tab.ts
import {
@@ -94,9 +94,9 @@ export default definePageLayoutTab({
});
```
### Key points
### Основные моменты
* `pageLayoutUniversalIdentifier` is **required** and must point to a page layout that already exists at install time — either a standard Twenty layout or one defined by your own app. Cross-app references to layouts owned by another installed app are not supported today. When the parent layout is missing, installation fails with a clear validation error.
* `widgets` are scoped to this tab only — they reference [front components](/l/ru/developers/extend/apps/layout/front-components), views, etc. exactly like widgets defined inline in `definePageLayout`.
* `position` controls ordering against existing tabs on the targeted layout. Pick a value that places your tab where you want it relative to built-in tabs.
* Use this instead of `definePageLayout` when you only want to add to an existing layout. Use `definePageLayout` when you own the entire layout.
* `pageLayoutUniversalIdentifier` является **обязательным** и должен указывать на макет страницы, который уже существует на момент установки — либо стандартный макет Twenty, либо определённый вашим собственным приложением. Кросс-приложенческие ссылки на макеты, которыми владеет другое установленное приложение, на данный момент не поддерживаются. Если родительский макет отсутствует, установка завершается с понятной ошибкой проверки.
* `widgets` ограничены только этой вкладкой — они ссылаются на [front components](/l/ru/developers/extend/apps/layout/front-components), представления и т. п. точно так же, как виджеты, определённые непосредственно в `definePageLayout`.
* `position` управляет порядком относительно существующих вкладок в целевом макете. Выберите значение, которое поместит вашу вкладку в нужное место относительно встроенных вкладок.
* Используйте это вместо `definePageLayout`, когда вы хотите только добавить к существующему макету. Используйте `definePageLayout`, когда вы управляете всем макетом.
@@ -1,10 +1,10 @@
---
title: Views
description: Ship pre-configured saved views — column order, filters, groups — for objects in your app.
icon: list
title: Представления
description: Поставляйте предварительно настроенные сохранённые представления — порядок столбцов, фильтры, группы — для объектов в вашем приложении.
icon: список
---
A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app — typically a default index view for each custom object you create.
**Представление** — это сохранённая конфигурация того, как отображаются записи объекта: какие поля появляются, их порядок, видимость, а также применённые фильтры и группы. Используйте `defineView()` для поставки предварительно настроенных представлений вместе с вашим приложением — обычно это представление индекса по умолчанию для каждого создаваемого вами пользовательского объекта.
```ts src/views/example-view.ts
import { defineView, ViewKey } from 'twenty-sdk/define';
@@ -30,14 +30,14 @@ export default defineView({
});
```
## Key points
## Основные моменты
* `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object.
* `key` determines the view type — `ViewKey.INDEX` is the main list view for the object.
* `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`.
* You can also declare `filters`, `filterGroups`, `groups`, and `fieldGroups` for advanced configurations.
* `position` controls ordering when multiple views exist for the same object.
* `objectUniversalIdentifier` указывает, к какому объекту применяется это представление. Это может быть пользовательский объект, который вы определили, или стандартный объект Twenty.
* `key` определяет тип представления — `ViewKey.INDEX` — это основное представление списка для объекта.
* `fields` управляет тем, какие столбцы отображаются и в каком порядке. Каждое поле ссылается на `fieldMetadataUniversalIdentifier`.
* Также вы можете объявить `filters`, `filterGroups`, `groups` и `fieldGroups` для продвинутых конфигураций.
* `position` управляет порядком, когда для одного и того же объекта существует несколько представлений.
## How views show up in the UI
## Как представления отображаются в интерфейсе
A view by itself isn't reachable from the sidebar. To make it appear there, pair it with a [navigation menu item](/l/ru/developers/extend/apps/layout/navigation-menu-items) of type `VIEW` that points at the view's `universalIdentifier`. That's the canonical pattern: every custom object typically ships a default view + a sidebar entry that opens it.
Само по себе представление недоступно из боковой панели. Чтобы оно появилось там, свяжите его с [пунктом навигационного меню](/l/ru/developers/extend/apps/layout/navigation-menu-items) типа `VIEW`, который указывает на `universalIdentifier` представления. Это канонический шаблон: каждый пользовательский объект обычно поставляется с представлением по умолчанию и пунктом боковой панели, который его открывает.
@@ -1,20 +1,20 @@
---
title: Connections
description: Let your app act on a user's behalf in third-party services via OAuth.
title: Подключения
description: Разрешите вашему приложению действовать от имени пользователя в сторонних сервисах с помощью OAuth.
icon: plug
---
Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API.
Подключения — это учетные данные, которыми пользователь располагает для внешнего сервиса (Linear, GitHub, Slack, ...). Ваше приложение определяет, **как** получают эти учетные данные — через **провайдера подключения** — и использует их во время выполнения для выполнения аутентифицированных вызовов к стороннему API.
Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate.
На данный момент поддерживается только OAuth 2.0. Будущие типы учетных данных (персональные токены доступа, ключи API, базовая аутентификация) будут подключаться к тому же интерфейсу — приложения, уже использующие `defineConnectionProvider({ type: 'oauth', ... })` не потребуют миграции.
<AccordionGroup>
<Accordion title="defineConnectionProvider" description="Declare how your app's connections are obtained">
<Accordion title="defineConnectionProvider" description="Определите, как в вашем приложении получаются подключения">
A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace.
Провайдер подключения описывает процедуру OAuth-обмена, которая требуется вашему приложению. Пользователь нажимает "Добавить подключение" в настройках вашего приложения, подтверждает разрешения на экране согласия провайдера, и в его рабочем пространстве создается запись `ConnectedAccount`.
A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials.
Рабочей конфигурации нужны **два файла** — провайдер подключения и соответствующее объявление `serverVariables` в `defineApplication`, которое содержит учетные данные клиента OAuth.
```ts src/connection-providers/linear-connection.ts
import { defineConnectionProvider } from 'twenty-sdk/define';
@@ -71,16 +71,16 @@ export default defineApplication({
});
```
Key points:
Основные моменты:
* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`).
* `displayName` shows in the per-app settings tab and in the AI tool list.
* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo.
* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server.
* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled.
* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`.
* `name` — это уникальная строка-идентификатор, используемая в `listConnections({ providerName })` (kebab-case, должна соответствовать `^[a-z][a-z0-9-]*$`).
* `displayName` отображается на вкладке настроек приложения и в списке инструментов ИИ.
* `clientIdVariable` / `clientSecretVariable` — это **имена**, а не значения — они должны совпадать с ключами, объявленными в `defineApplication.serverVariables`. Фактические `client_id` и `client_secret` вводятся администратором сервера через интерфейс регистрации приложения и никогда не коммитятся в ваш репозиторий.
* Используйте `serverVariables` (не `applicationVariables`) — учетные данные OAuth являются общими для сервера, и на каждом сервере Twenty используется одно приложение OAuth.
* Пока оба `serverVariables` не заполнены, на вкладке настроек приложения показывается подсказка "нужен администратор сервера", а кнопка "Добавить подключение" отключена.
* `type: 'oauth'` — единственное поддерживаемое сегодня значение. Дискриминатор совместим с будущими версиями: будущие типы (`'pat'`, `'api-key'`, ...) добавят новые блоки подконфигурации рядом с `oauth`.
The OAuth callback URL your provider needs to whitelist is:
URL обратного вызова OAuth, который вашему провайдеру нужно добавить в список разрешенных:
```
https://<your-twenty-server>/apps/oauth/callback
@@ -88,9 +88,9 @@ https://<your-twenty-server>/apps/oauth/callback
</Accordion>
<Accordion title="listConnections / getConnection" description="Use connections from a logic function">
<Accordion title="listConnections / getConnection" description="Используйте подключения из логической функции">
Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens.
Внутри обработчика логической функции `listConnections({ providerName })` возвращает записи `ConnectedAccount` этого приложения для указанного провайдера с обновленными токенами доступа.
```ts src/logic-functions/handlers/create-linear-issue-handler.ts
import { listConnections } from 'twenty-sdk/logic-function';
@@ -135,34 +135,34 @@ export const createLinearIssueHandler = async (input: {
};
```
Each connection has:
Каждое подключение имеет:
| Field | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one |
| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) |
| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) |
| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers |
| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) |
| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) |
| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect |
| Поле | Описание |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id` | Уникальный идентификатор записи; передайте его в `getConnection(id)`, чтобы повторно получить одну запись |
| `visibility` | `'user'` (приватно для одного участника рабочего пространства) или `'workspace'` (доступно всем участникам) |
| `scopes` | Разрешения OAuth, предоставленные внешним провайдером (отличаются от `visibility` — это несвязанные вещи) |
| `userWorkspaceId` | Идентификатор userWorkspace владельца — полезно для выбора "подключения пользователя запроса" в триггерах HTTP-маршрутов |
| `accessToken` | Актуальный токен доступа OAuth (обновляется автоматически при истечении срока действия) |
| `name` / `handle` | Отображаемое имя подключения (автоматически определяется при обратном вызове OAuth, может быть переименовано пользователем) |
| `authFailedAt` | Устанавливается, если последняя попытка обновления не удалась; пользователю нужно переподключиться |
Key points:
Основные моменты:
* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers.
* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set).
* `getConnection(id)` is the single-row equivalent.
* Передайте `{ providerName }`, чтобы отфильтровать по провайдеру; опустите, чтобы получить все подключения этого приложения у всех провайдеров.
* Сервер прозрачно обновляет токен доступа перед возвратом. Ваш обработчик всегда получает рабочий токен (или установлено `authFailedAt`).
* `getConnection(id)` — эквивалент для одной записи.
</Accordion>
<Accordion title="Per-user vs workspace-shared visibility" description="How users choose between private and shared credentials">
<Accordion title="Индивидуальная и общая для рабочего пространства видимость" description="Как пользователи выбирают между приватными и общими учетными данными">
When a user clicks "Add connection," they're prompted to pick a visibility:
Когда пользователь нажимает "Добавить подключение", ему предлагается выбрать видимость:
* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not.
* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user.
* **Только для меня** — учетные данные приватны для подключившегося пользователя. Любая логическая функция, вызываемая от его имени (триггер HTTP-маршрута с `isAuthRequired: true`), видит их; триггеры cron и события базы данных — нет.
* **Общее для рабочего пространства** — любой участник рабочего пространства может использовать эти учетные данные. Триггеры cron/базы данных также видят их, поскольку у них нет пользователя запроса.
Use the right one for each handler:
Используйте подходящий вариант для каждого обработчика:
```ts
// HTTP-route trigger — prefer the request user's own connection.
@@ -174,19 +174,19 @@ const conn =
const conn = connections.find((c) => c.visibility === 'workspace');
```
Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side.
Допускается несколько подключений на пару (пользователь, провайдер), поэтому один и тот же пользователь может иметь "Personal Linear" и "Work Linear" одновременно.
</Accordion>
<Accordion title="One-time provider setup" description="Register your OAuth app with the third-party service">
<Accordion title="Единоразовая настройка провайдера" description="Зарегистрируйте свое приложение OAuth у стороннего сервиса">
For each connection provider, the server admin needs to register an OAuth app at the third party first.
Для каждого провайдера подключения администратору сервера сначала нужно зарегистрировать у стороннего сервиса приложение OAuth.
1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new).
2. Set the **Redirect URI** to `\<SERVER_URL>/apps/oauth/callback`.
3. Copy the generated **Client ID** and **Client Secret**.
4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`.
5. Workspace members can then add connections from the per-app **Connections** section.
1. Перейдите в настройки разработчика провайдера (например, https://linear.app/settings/api/applications/new).
2. Установите **Redirect URI** в значение `\<SERVER_URL>/apps/oauth/callback`.
3. Скопируйте сгенерированные **Client ID** и **Client Secret**.
4. Откройте установленное приложение в Twenty под учетной записью администратора сервера → задайте значения в соответствующих `serverVariables`.
5. Затем участники рабочего пространства смогут добавлять подключения в разделе **Подключения** конкретного приложения.
</Accordion>
@@ -1,15 +1,15 @@
---
title: Logic Functions
description: Define server-side TypeScript functions with HTTP, cron, and database event triggers.
title: Логические функции
description: Определяйте серверные функции на TypeScript с триггерами HTTP, cron и событиями базы данных.
icon: bolt
---
Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents.
Функции логики — это серверные функции на TypeScript, которые выполняются на платформе Twenty. Их можно запускать HTTP-запросами, расписаниями cron или событиями базы данных — а также предоставлять как инструменты для ИИ-агентов.
<AccordionGroup>
<Accordion title="defineLogicFunction" description="Define logic functions and their triggers">
<Accordion title="defineLogicFunction" description="Определяйте логические функции и их триггеры">
Each function file uses `defineLogicFunction()` to export a configuration with a handler and optional triggers.
Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами.
```ts src/logic-functions/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
@@ -50,15 +50,15 @@ export default defineLogicFunction({
});
```
Available trigger types:
* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create`
* **cron**: Runs your function on a schedule using a CRON expression.
* **databaseEvent**: Runs on workspace object lifecycle events. When the event operation is `updated`, specific fields to listen to can be specified in the `updatedFields` array. If left undefined or empty, any update will trigger the function.
> e.g. `person.updated`, `*.created`, `company.*`
Доступные типы триггеров:
* **httpRoute**: Публикует вашу функцию по HTTP-пути и методу **под конечной точкой `/s/`**:
> например, `path: '/post-card/create'` вызывается по адресу `https://your-twenty-server.com/s/post-card/create`
* **cron**: Запускает вашу функцию по расписанию с использованием выражения CRON.
* **databaseEvent**: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — `updated`, можно указать конкретные поля для отслеживания в массиве `updatedFields`. Если оставить не заданным или пустым, любое обновление будет вызывать функцию.
> например, `person.updated`, `*.created`, `company.*`
<Note>
You can also manually execute a function using the CLI:
Вы также можете вручную выполнить функцию с помощью CLI:
```bash filename="Terminal"
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
@@ -68,18 +68,17 @@ yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
You can watch logs with:
Вы можете просматривать логи с помощью:
```bash filename="Terminal"
yarn twenty logs
```
</Note>
#### Route trigger payload
#### Полезная нагрузка триггера маршрута
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the
[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
Import the `RoutePayload` type from `twenty-sdk`:
Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, который соответствует [формату AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
Импортируйте тип `RoutePayload` из `twenty-sdk`:
```ts
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
@@ -92,24 +91,24 @@ const handler = async (event: RoutePayload) => {
};
```
The `RoutePayload` type has the following structure:
Тип `RoutePayload` имеет следующую структуру:
| Property | Type | Description | Example |
| ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP headers (only those listed in `forwardedRequestHeaders`) | see section below |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Query string parameters (multiple values joined with commas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Parsed request body (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
| `isBase64Encoded` | `boolean` | Whether the body is base64 encoded | |
| `requestContext.http.method` | `string` | HTTP method (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Raw request path | |
| Свойство | Тип | Описание | Пример |
| ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | см. раздел ниже |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Параметры строки запроса (несколько значений объединяются запятыми) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Параметры пути, извлечённые из шаблона маршрута | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Исходное тело запроса в кодировке UTF-8, до разбора JSON. Полезно для проверки подписей вебхуков в стиле HMAC (например, `X-Hub-Signature-256` от GitHub, Stripe). `undefined`, если среда выполнения не сохранила его. | |
| `isBase64Encoded` | `boolean` | Является ли тело закодированным в base64 | |
| `requestContext.http.method` | `string` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Необработанный путь запроса | |
#### forwardedRequestHeaders
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons.
To access specific headers, list them in the `forwardedRequestHeaders` array:
По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности.
Чтобы получить доступ к определённым заголовкам, перечислите их в массиве `forwardedRequestHeaders`:
```ts
export default defineLogicFunction({
@@ -125,7 +124,7 @@ export default defineLogicFunction({
});
```
In your handler, access the forwarded headers like this:
В обработчике обращайтесь к переданным заголовкам следующим образом:
```ts
const handler = async (event: RoutePayload) => {
@@ -138,17 +137,17 @@ const handler = async (event: RoutePayload) => {
```
<Note>
Header names are normalized to lowercase. Access them using lowercase keys (e.g., `event.headers['content-type']`).
Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`).
</Note>
#### Exposing a function as an AI tool or workflow action
#### Предоставление функции в качестве инструмента ИИ или действия рабочего процесса
Logic functions can be exposed on two surfaces, each with its own trigger:
Функции логики могут быть представлены в двух интерфейсах, у каждого — свой триггер:
* **`toolTriggerSettings`** — makes the function discoverable by Twenty's AI features (chat, MCP, function calling). Uses standard JSON Schema, the format LLMs natively understand.
* **`workflowActionTriggerSettings`** — makes the function appear as a step in the visual workflow builder. Uses Twenty's rich `InputSchema` so the builder can render proper field editors, variable pickers, and labels.
* **`toolTriggerSettings`** — делает функцию обнаруживаемой для возможностей ИИ Twenty (чат, MCP, вызов функций). Использует стандартную JSON Schema — формат, который модели LLM изначально понимают.
* **`workflowActionTriggerSettings`** — делает функцию доступной как шаг в визуальном конструкторе рабочих процессов. Использует расширенную `InputSchema` от Twenty, чтобы конструктор мог отрисовывать корректные редакторы полей, селекторы переменных и подписи.
A function can opt into one, the other, or both. They sit alongside `cronTriggerSettings`, `databaseEventTriggerSettings`, and `httpRouteTriggerSettings` — same pattern, same shape.
Функция может выбрать один, другой или оба варианта. Они идут рядом с `cronTriggerSettings`, `databaseEventTriggerSettings` и `httpRouteTriggerSettings` — тот же шаблон, та же структура.
```ts src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
@@ -182,10 +181,10 @@ export default defineLogicFunction({
});
```
Key points:
Основные моменты:
* A function can mix surfaces — declare both `toolTriggerSettings` and `workflowActionTriggerSettings` to expose it in chat AND in the workflow builder.
* `toolTriggerSettings.inputSchema` and `workflowActionTriggerSettings.inputSchema` are both optional. When omitted, the manifest builder infers them from the handler source code (JSON Schema for the AI tool, Twenty's `InputSchema` for the workflow action). Provide one explicitly when you want richer typing — for example, with `FieldMetadataType`-aware fields like `CURRENCY` or `RELATION` for the workflow builder, or with `description` fields the AI agent can read:
* Функция может сочетать интерфейсы — объявите и `toolTriggerSettings`, и `workflowActionTriggerSettings`, чтобы сделать её доступной и в чате, и в конструкторе рабочих процессов.
* `toolTriggerSettings.inputSchema` и `workflowActionTriggerSettings.inputSchema` — обе необязательны. Если они опущены, конструктор манифеста выводит их из исходного кода обработчика (JSON Schema — для инструмента ИИ, `InputSchema` от Twenty — для действия рабочего процесса). Укажите её явно, когда вам нужна более богатая типизация — например, с полями, учитывающими `FieldMetadataType`, такими как `CURRENCY` или `RELATION`, для конструктора рабочих процессов, или с полями `description`, которые может прочитать ИИ-агент:
```ts
export default defineLogicFunction({
@@ -210,29 +209,29 @@ export default defineLogicFunction({
```
<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.
**Напишите хорошее описание в поле `description`.** Агенты ИИ опираются на поле `description` функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать.
</Note>
</Accordion>
</AccordionGroup>
<Note>
**Install hooks** — pre-install and post-install handlers — share this runtime but are declared with their own define functions and don't take trigger settings. See [Install Hooks](/l/ru/developers/extend/apps/config/install-hooks) for `definePreInstallLogicFunction` and `definePostInstallLogicFunction`.
**Хуки установки** — обработчики до установки и после установки — используют тот же рантайм, но объявляются с помощью собственных функций `define` и не принимают настройки триггеров. См. раздел [Install Hooks](/l/ru/developers/extend/apps/config/install-hooks) для `definePreInstallLogicFunction` и `definePostInstallLogicFunction`.
</Note>
## Typed API clients (twenty-client-sdk)
## Типизированные клиенты API (twenty-client-sdk)
The `twenty-client-sdk` package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components.
Пакет `twenty-client-sdk` предоставляет два типизированных клиента GraphQL для взаимодействия с API Twenty из ваших логических функций и фронт-компонентов.
| Client | Import | Endpoint | Generated? |
| ------------------- | ---------------------------- | ---------------------------------------------- | ---------------------- |
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — workspace data (records, objects) | Yes, at dev/build time |
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — workspace config, file uploads | No, ships pre-built |
| Клиент | Импорт | Конечная точка | Генерируется? |
| ------------------- | ---------------------------- | ----------------------------------------------------------------- | -------------------------------- |
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — данные рабочего пространства (записи, объекты) | Да, на этапе dev/build |
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — конфигурация рабочего пространства, загрузка файлов | Нет, поставляется в готовом виде |
<AccordionGroup>
<Accordion title="CoreApiClient" description="Query and mutate workspace data (records, objects)">
<Accordion title="CoreApiClient" description="Запрос и изменение данных рабочего пространства (записи, объекты)">
`CoreApiClient` is the main client for querying and mutating workspace data. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields.
`CoreApiClient` — основной клиент для запросов и изменений данных рабочего пространства. Он **генерируется из схемы вашего рабочего пространства** во время `yarn twenty dev` или `yarn twenty build`, поэтому полностью типизирован в соответствии с вашими объектами и полями.
```ts
import { CoreApiClient } from 'twenty-client-sdk/core';
@@ -269,15 +268,15 @@ const { createCompany } = await client.mutation({
});
```
The client uses a selection-set syntax: pass `true` to include a field, use `__args` for arguments, and nest objects for relations. You get full autocompletion and type checking based on your workspace schema.
Клиент использует синтаксис selection-set: передайте `true`, чтобы включить поле, используйте `__args` для аргументов и вкладывайте объекты для отношений. Вы получаете полное автодополнение и проверку типов на основе схемы вашего рабочего пространства.
<Note>
**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`.
**CoreApiClient генерируется на этапе dev/build.** Если вы используете его, не запустив сначала `yarn twenty dev` или `yarn twenty build`, он выбросит ошибку. Генерация происходит автоматически — CLI анализирует GraphQL-схему вашего рабочего пространства и создает типизированный клиент с помощью `@genql/cli`.
</Note>
#### Using CoreSchema for type annotations
#### Использование CoreSchema для аннотаций типов
`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters:
`CoreSchema` предоставляет типы TypeScript, соответствующие объектам вашего рабочего пространства — это полезно для типизации состояния компонентов или параметров функций:
```ts
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
@@ -299,9 +298,9 @@ setCompany(result.company);
```
</Accordion>
<Accordion title="MetadataApiClient" description="Workspace config, applications, and file uploads">
<Accordion title="MetadataApiClient" description="Конфигурация рабочего пространства, приложения и загрузка файлов">
`MetadataApiClient` ships pre-built with the SDK (no generation required). It queries the `/metadata` endpoint for workspace configuration, applications, and file uploads.
`MetadataApiClient` поставляется в готовом виде вместе с SDK (генерация не требуется). Он выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства, приложений и загрузки файлов.
```ts
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
@@ -328,9 +327,9 @@ const { objects } = await metadataClient.query({
});
```
#### Uploading files
#### Загрузка файлов
`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields:
`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа файла:
```ts
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
@@ -351,25 +350,25 @@ console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
```
| Parameter | Type | Description |
| ---------------------------------- | -------- | --------------------------------------------------------------- |
| `fileBuffer` | `Buffer` | The raw file contents |
| `filename` | `string` | The name of the file (used for storage and display) |
| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) |
| `fieldMetadataUniversalIdentifier` | `string` | The `universalIdentifier` of the file-type field on your object |
| Параметр | Тип | Описание |
| ---------------------------------- | -------- | ------------------------------------------------------------------ |
| `fileBuffer` | `Buffer` | Необработанное содержимое файла |
| `filename` | `string` | Имя файла (используется для хранения и отображения) |
| `contentType` | `string` | Тип MIME (по умолчанию `application/octet-stream`, если не указан) |
| `fieldMetadataUniversalIdentifier` | `string` | Значение `universalIdentifier` для поля типа файла в вашем объекте |
Key points:
* Uses the field's `universalIdentifier` (not its workspace-specific ID), so your upload code works across any workspace where your app is installed.
* The returned `url` is a signed URL you can use to access the uploaded file.
Основные моменты:
* Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение.
* Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу.
</Accordion>
</AccordionGroup>
<Note>
When your code runs on Twenty (logic functions or front components), the platform injects credentials as environment variables:
Когда ваш код выполняется на Twenty (логические функции или фронт-компоненты), платформа предоставляет учётные данные в виде переменных окружения:
* `TWENTY_API_URL` — Base URL of the Twenty API
* `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role
* `TWENTY_API_URL` — базовый URL API Twenty
* `TWENTY_APP_ACCESS_TOKEN` — краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения
You do **not** need to pass these to the clients — they read from `process.env` automatically. The API key's permissions are determined by the role referenced in `defaultRoleUniversalIdentifier` in your `application-config.ts`.
Вам не нужно передавать их клиентам — они автоматически читаются из `process.env`. Права ключа API определяются ролью, указанной в `defaultRoleUniversalIdentifier` в вашем `application-config.ts`.
</Note>
@@ -1,10 +1,10 @@
---
title: Overview
description: Server-side TypeScript that runs inside Twenty — triggered by HTTP routes, cron schedules, database events, AI tools, or workflow actions.
title: Обзор
description: Серверный TypeScript, который выполняется внутри Twenty — запускается HTTP-маршрутами, расписаниями cron, событиями базы данных, инструментами ИИ или действиями рабочих процессов.
icon: bolt
---
A Twenty app's **logic layer** is the code that *runs* — server-side TypeScript handlers reacting to HTTP requests, cron schedules, and record changes; AI skills and agents that live inside the workspace; and OAuth connections that let your functions act on a user's behalf in third-party services.
**Логический слой** приложения Twenty — это код, который *выполняется*: серверные обработчики TypeScript, реагирующие на HTTP-запросы, расписания cron и изменения записей; AI-навыки и агенты, работающие внутри рабочего пространства; а также OAuth-подключения, позволяющие вашим функциям действовать от имени пользователя в сторонних сервисах.
```text
┌─ HTTP route ──┐
@@ -22,34 +22,34 @@ A Twenty app's **logic layer** is the code that *runs* — server-side TypeScrip
└────────────────────────────┘
```
## In this section
## В этом разделе
<CardGroup cols={2}>
<Card title="Logic Functions" icon="bolt" href="/l/ru/developers/extend/apps/logic/logic-functions">
The core building block — trigger types, payloads, and the typed API client.
<Card title="Логические функции" icon="bolt" href="/l/ru/developers/extend/apps/logic/logic-functions">
Основной строительный блок — типы триггеров, полезные данные (payloads) и типизированный клиент API.
</Card>
<Card title="Skills & Agents" icon="robot" href="/l/ru/developers/extend/apps/logic/skills-and-agents">
Reusable AI agent instructions and assistants with custom system prompts.
<Card title="Навыки и агенты" icon="robot" href="/l/ru/developers/extend/apps/logic/skills-and-agents">
Повторно используемые инструкции AI-агента и ассистенты с пользовательскими системными подсказками.
</Card>
<Card title="Connections" icon="plug" href="/l/ru/developers/extend/apps/logic/connections">
OAuth credentials your app holds for third-party services — Linear, GitHub, Slack, and more.
<Card title="Подключения" icon="plug" href="/l/ru/developers/extend/apps/logic/connections">
OAuth-учетные данные, которые ваше приложение хранит для сторонних сервисов — Linear, GitHub, Slack и других.
</Card>
</CardGroup>
## Trigger types at a glance
## Типы триггеров одним взглядом
A logic function picks one or more triggers — every entry below is a separate field on `defineLogicFunction()`:
Логическая функция выбирает один или несколько триггеров — каждая запись ниже — это отдельное поле в `defineLogicFunction()`:
| Trigger | When it runs | Setting |
| ------------------- | ---------------------------------------------------------- | ------------------------------- |
| **HTTP route** | A request hits your `/s/\<path>` endpoint | `httpRouteTriggerSettings` |
| **Cron** | A CRON expression matches | `cronTriggerSettings` |
| **Database event** | A workspace record is created, updated, or deleted | `databaseEventTriggerSettings` |
| **AI tool** | A Twenty AI feature decides to call your function | `toolTriggerSettings` |
| **Workflow action** | A workflow step invokes your function | `workflowActionTriggerSettings` |
| Триггер | Когда запускается | Настройка |
| ------------------------------ | ------------------------------------------------------------------- | ------------------------------- |
| **HTTP-маршрут** | Запрос попадает на ваш конечный пункт `/s/\<path>` | `httpRouteTriggerSettings` |
| **Cron** | Срабатывает выражение CRON | `cronTriggerSettings` |
| **Событие базы данных** | Запись рабочего пространства создается, обновляется или удаляется | `databaseEventTriggerSettings` |
| **Инструмент ИИ** | Функция Twenty AI решает вызвать вашу функцию | `toolTriggerSettings` |
| **Действие рабочего процесса** | Шаг рабочего процесса вызывает вашу функцию | `workflowActionTriggerSettings` |
Functions run sandboxed in isolated Node.js processes and access the workspace through a typed API client scoped to the role declared on [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
Функции выполняются в изолированных процессах Node.js в песочнице и получают доступ к рабочему пространству через типизированный клиент API с областью действия, ограниченной ролью, объявленной в [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
<Note>
**Install-time hooks** — code that runs before or after the install — share this runtime but use their own define functions and live under [Config → Install Hooks](/l/ru/developers/extend/apps/config/install-hooks).
**Хуки времени установки** — код, который выполняется до или после установки, — используют то же окружение выполнения, но свои собственные функции `define*` и находятся в разделе [Config → Install Hooks](/l/ru/developers/extend/apps/config/install-hooks).
</Note>
@@ -1,19 +1,19 @@
---
title: Skills & Agents
description: Define AI skills and agents for your app.
title: Навыки и агенты
description: Определите навыки и агентов ИИ для вашего приложения.
icon: robot
---
<Warning>
Skills and agents are currently in alpha. The feature works but is still evolving.
Навыки и агенты сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
</Warning>
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
Приложения могут определять возможности ИИ, которые находятся внутри рабочего пространства — повторно используемые инструкции для навыков и агенты с настраиваемыми системными подсказками.
<AccordionGroup>
<Accordion title="defineSkill" description="Define AI agent skills">
<Accordion title="defineSkill" description="Определяйте навыки ИИ-агентов">
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией:
```ts src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk/define';
@@ -32,17 +32,17 @@ export default defineSkill({
});
```
Key points:
* `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.
Основные моменты:
* `name` — уникальная строка-идентификатор навыка (рекомендуется kebab-case).
* `label` — читаемое человеком отображаемое имя, показываемое в UI.
* `content` содержит инструкции навыка — это текст, который использует агент ИИ.
* `icon` (необязательно) задаёт значок, отображаемый в UI.
* `description` (необязательно) предоставляет дополнительный контекст о назначении навыка.
</Accordion>
<Accordion title="defineAgent" description="Define AI agents with custom prompts">
<Accordion title="defineAgent" description="Определяйте ИИ-агентов с пользовательскими промптами">
Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt:
Агенты — это ИИ-помощники, работающие в вашем рабочем пространстве. Используйте `defineAgent()` для создания агентов с пользовательским системным промптом:
```ts src/agents/example-agent.ts
import { defineAgent } from 'twenty-sdk/define';
@@ -57,13 +57,13 @@ export default defineAgent({
});
```
Key points:
* `name` is the unique identifier string for the agent (kebab-case recommended).
* `label` is the display name shown in the UI.
* `prompt` is the system prompt that defines the agent's behavior.
* `description` (optional) provides context about what the agent does.
* `icon` (optional) sets the icon displayed in the UI.
* `modelId` (optional) overrides the default AI model used by the agent.
Основные моменты:
* `name` — уникальная строка-идентификатор агента (рекомендуется kebab-case).
* `label` — отображаемое имя, показываемое в UI.
* `prompt` — это системный промпт, определяющий поведение агента.
* `description` (необязательно) предоставляет контекст о том, что делает агент.
* `icon` (необязательно) задаёт значок, отображаемый в UI.
* `modelId` (необязательно) переопределяет модель ИИ по умолчанию, используемую агентом.
</Accordion>
</AccordionGroup>
@@ -1,14 +1,14 @@
---
title: CLI
description: yarn twenty commands for executing functions, streaming logs, managing app installations, and switching remotes.
description: Команды `yarn twenty` для выполнения функций, потоковой передачи логов, управления установками приложений и переключения удалённых репозиториев.
icon: terminal
---
Beyond `dev`, `build`, `add`, and `typecheck`, the `yarn twenty` CLI provides commands for executing functions, viewing logs, and managing app installations.
Помимо `dev`, `build`, `add` и `typecheck`, CLI `yarn twenty` предоставляет команды для выполнения функций, просмотра логов и управления установками приложений.
## Executing functions (`yarn twenty exec`)
## Выполнение функций (`yarn twenty exec`)
Run a logic function manually without triggering it via HTTP, cron, or database event:
Запустите логическую функцию вручную, не вызывая её через HTTP, cron или событие базы данных:
```bash filename="Terminal"
# Execute by function name
@@ -24,9 +24,9 @@ yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
yarn twenty exec --postInstall
```
## Viewing function logs (`yarn twenty logs`)
## Просмотр логов функций (`yarn twenty logs`)
Stream execution logs for your app's logic functions:
Потоковая передача журналов выполнения логических функций вашего приложения:
```bash filename="Terminal"
# Stream all function logs
@@ -40,12 +40,12 @@ yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
<Note>
This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server.
Это отличается от `yarn twenty server logs`, который показывает логи контейнера Docker. `yarn twenty logs` показывает журналы выполнения функций вашего приложения с сервера Twenty.
</Note>
## Uninstalling an app (`yarn twenty uninstall`)
## Удаление приложения (`yarn twenty uninstall`)
Remove your app from the active workspace:
Удалите свое приложение из активного рабочего пространства:
```bash filename="Terminal"
yarn twenty uninstall
@@ -54,9 +54,9 @@ yarn twenty uninstall
yarn twenty uninstall --yes
```
## Managing remotes
## Управление удалёнными серверами
A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time.
**Remote** — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скэффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые удалённые серверы или переключаться между ними.
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
@@ -75,4 +75,4 @@ yarn twenty remote list
yarn twenty remote switch <name>
```
Your credentials are stored in `~/.twenty/config.json`.
Ваши учётные данные хранятся в `~/.twenty/config.json`.
@@ -1,10 +1,10 @@
---
title: Overview
description: Build, test, and ship your app — CLI commands, integration tests, CI, and publishing to a server or to npm.
title: Обзор
description: Собирайте, тестируйте и поставляйте свое приложение — команды CLI, интеграционные тесты, CI и публикация на сервер или в npm.
icon: rocket
---
The **operations layer** is everything you do *to* your app rather than *with* it: invoking CLI commands, running integration tests against a real Twenty server, configuring CI, and shipping releases — either as a tarball deployed to a single server or as an npm package listed in the marketplace.
**Операционный уровень** — это все, что вы делаете *с* вашим приложением, а не *внутри* него: выполнение команд CLI, запуск интеграционных тестов против реального сервера Twenty, настройка CI и поставка релизов — либо в виде tarball, разворачиваемого на одном сервере, либо как пакет npm, публикуемый в маркетплейсе.
```text
develop ─▶ test ─▶ build ─▶ deploy / publish
@@ -14,16 +14,16 @@ The **operations layer** is everything you do *to* your app rather than *with* i
dev build yarn twenty publish (npm → marketplace)
```
## In this section
## В этом разделе
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/ru/developers/extend/apps/operations/cli">
`yarn twenty` reference — exec, logs, uninstall, remotes.
</Card>
<Card title="Testing" icon="flask" href="/l/ru/developers/extend/apps/operations/testing">
Vitest setup, integration tests, type checking, CI workflow.
<Card title="Тестирование" icon="flask" href="/l/ru/developers/extend/apps/operations/testing">
Настройка Vitest, интеграционные тесты, проверка типов, рабочий процесс CI.
</Card>
<Card title="Publishing" icon="upload" href="/l/ru/developers/extend/apps/operations/publishing">
Build, deploy a tarball, publish to npm, install.
<Card title="Публикация" icon="загрузить" href="/l/ru/developers/extend/apps/operations/publishing">
Сборка, развертывание tarball, публикация в npm, установка.
</Card>
</CardGroup>
@@ -1,45 +1,45 @@
---
title: Publishing
icon: upload
description: Distribute your Twenty app to the marketplace or deploy it internally.
title: Публикация
icon: загрузить
description: Распространяйте своё приложение Twenty в маркетплейсе или разверните его для внутреннего использования.
---
## Overview
## Обзор
Once your app is [built and tested locally](/l/ru/developers/extend/apps/getting-started/concepts), you have two paths for distributing it:
После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/getting-started/concepts), у вас есть два пути для его распространения:
* **Deploy a tarball** — upload your app directly to a specific Twenty server for internal or private use.
* **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
* **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования.
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
Both paths start from the same **build** step.
Оба пути начинаются с одного и того же шага **build**.
## Building your app
## Сборка вашего приложения
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
Выполните команду сборки, чтобы скомпилировать приложение и сгенерировать готовый к распространению `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
Это компилирует исходные файлы TypeScript, транспилирует функции логики и фронтенд-компоненты и записывает всё в `.twenty/output/`. Добавьте `--tarball`, чтобы также создать пакет `.tgz` для ручного распространения или для команды deploy.
## Deploying to a server (tarball)
## Развертывание на сервер (tarball)
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
### Prerequisites
### Требования
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
Add a remote:
Добавьте remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Deploying
### Развертывание
Build and upload your app to the server in one step:
Соберите и загрузите ваше приложение на сервер в одном шаге:
```bash filename="Terminal"
yarn twenty deploy
@@ -47,39 +47,39 @@ yarn twenty deploy
# yarn twenty deploy --remote production
```
### Sharing a deployed app
### Общий доступ к развернутому приложению
<Warning>
Sharing private (tarball) apps across workspaces is an **Enterprise** feature. The **Distribution** tab will show an upgrade prompt instead of the share controls until your workspace has a valid Enterprise key. See [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to activate it.
Совместный доступ к частным приложениям (tarball) между рабочими пространствами — функция уровня **Enterprise**. Вкладка **Distribution** будет показывать предложение обновиться вместо элементов управления совместным доступом, пока в вашем рабочем пространстве не будет действительного ключа Enterprise. Перейдите в [Настройки > Панель администратора > Enterprise](/settings/admin-panel#enterprise), чтобы включить её.
</Warning>
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. Once your workspace is on the Enterprise plan, you can share a deployed app like this:
Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Как только ваше рабочее пространство перейдёт на тарифный план Enterprise, вы сможете поделиться развёрнутым приложением следующим образом:
1. Go to **Settings > Applications > Registrations** and open your app
2. In the **Distribution** tab, click **Copy share link**
3. Share this link with users on other workspaces — it takes them directly to the app's install page
1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение
2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа**
3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере.
### Version management
### Управление версиями
When updating an already deployed tarball app, the server requires the `version` in `package.json` to be **strictly higher** (per [semver](https://semver.org) ordering) than the currently deployed version. Re-deploying the same version, or pushing a lower one, is rejected before the tarball is stored — you'll see a `VERSION_ALREADY_EXISTS` error from the CLI.
При обновлении уже развернутого tarball-приложения сервер требует, чтобы значение `version` в `package.json` было **строго выше** (согласно упорядочиванию по [semver](https://semver.org)), чем текущая развернутая версия. Повторное развёртывание той же версии или публикация более низкой версии отклоняются до сохранения tarball — в CLI вы увидите ошибку `VERSION_ALREADY_EXISTS`.
To release an update:
Чтобы выпустить обновление:
1. Bump the `version` field in your `package.json` (e.g. `1.2.3` → `1.2.4`, `1.3.0`, or `2.0.0`)
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Workspaces that have the app installed will see the upgrade available in their settings
1. Увеличьте значение поля `version` в вашем `package.json` (например: `1.2.3` → `1.2.4`, `1.3.0` или `2.0.0`).
2. Выполните `yarn twenty deploy` (или `yarn twenty deploy --remote production`)
3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках
<Note>
Pre-release tags work as expected: bumping `1.0.0-rc.1` → `1.0.0-rc.2` is allowed, and a final release like `1.0.0` is correctly recognized as higher than `1.0.0-rc.5`. The version in `package.json` must itself be a valid semver string.
Пререлизные теги работают как ожидается: повышение версии `1.0.0-rc.1` → `1.0.0-rc.2` допускается, а финальный релиз вроде `1.0.0` корректно распознаётся как более высокий, чем `1.0.0-rc.5`. Версия в `package.json` должна сама по себе быть корректной строкой semver.
</Note>
{/* TODO: add screenshot of the Upgrade button */}
### Server version compatibility
### Совместимость версий сервера
If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`:
Если ваше приложение использует функцию, появившуюся в конкретной версии сервера Twenty (например, провайдеры OAuth, добавленные в v2.3.0), следует объявить минимальную требуемую версию сервера с помощью поля `engines.twenty` в `package.json`:
```json filename="package.json"
{
@@ -92,83 +92,83 @@ If your app uses a feature introduced in a specific Twenty server version (for e
}
```
The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns:
Значение — это стандартный [диапазон semver](https://github.com/npm/node-semver#ranges). Типовые шаблоны:
| Range | Meaning |
| ---------------------------------- | ------------------------------------------ |
| `>=2.3.0` | Any server from 2.3.0 onward |
| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major |
| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` |
| Диапазон | Значение |
| ---------------------------------- | -------------------------------------------------- |
| `>=2.3.0` | Любой сервер версии 2.3.0 и новее |
| `>=2.3.0 \<3.0.0` | 2.3.0 или новее, но ниже следующей мажорной версии |
| `^2.3.0` | То же, что и `>=2.3.0 \<3.0.0` |
**What happens at deploy and install time:**
**Что происходит при развёртывании и установке:**
* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version.
* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps).
* If the server has no `APP_VERSION` configured, the check is skipped.
* Если `engines.twenty` задано и версия целевого сервера не удовлетворяет диапазону, развёртывание (загрузка tarball-архива) или установка отклоняются с ошибкой `SERVER_VERSION_INCOMPATIBLE` и сообщением, указывающим как требуемый диапазон, так и фактическую версию сервера.
* Если `engines.twenty` **не задано**, приложение принимается на сервере любой версии (обратная совместимость с существующими приложениями).
* Если на сервере `APP_VERSION` не задано, проверка пропускается.
<Note>
The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility.
Сервер выполняет окончательную проверку — он проверяет `engines.twenty` как при загрузке tarball-архива, так и при установке в рабочем пространстве. Если вы развёртываете tarball вне стандартного процесса или устанавливаете из маркетплейса, сервер всё равно принудительно проверяет совместимость.
</Note>
## Automated CI/CD (scaffolded workflows)
## Автоматизированный CI/CD (рабочие процессы, сгенерированные шаблоном)
Apps generated with `create-twenty-app` ship with two GitHub Actions workflows out of the box, under `.github/workflows/`. They are ready to run as soon as you push the repo to GitHub — no extra setup is needed for CI, and CD only requires a single secret.
Приложения, созданные с помощью `create-twenty-app`, «из коробки» включают два рабочих процесса GitHub Actions в каталоге `.github/workflows/`. Они готовы к запуску, как только вы запушите репозиторий на GitHub — для CI не требуется дополнительной настройки, а для CD нужен лишь один секрет.
### CI — `ci.yml`
Runs integration tests on every push to `main` and every pull request.
Автоматически запускает интеграционные тесты при каждом пуше в `main` и для каждого pull request.
**What it does:**
**Что делает:**
1. Checks out your app's source.
2. Spawns an isolated Twenty test instance using the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` composite action (the CI equivalent of `yarn twenty server start --test`).
3. Enables Corepack, sets up Node.js from your `.nvmrc`, and installs dependencies with `yarn install --immutable`.
4. Runs `yarn test`, passing `TWENTY_API_URL` and `TWENTY_API_KEY` from the spawned instance so your tests can talk to a real server.
1. Извлекает исходный код вашего приложения.
2. Запускает изолированный тестовый экземпляр Twenty с помощью составного действия `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` (эквивалент для CI `yarn twenty server start --test`).
3. Включает Corepack, настраивает Node.js на основе вашего `.nvmrc` и устанавливает зависимости с помощью `yarn install --immutable`.
4. Запускает `yarn test`, передавая `TWENTY_API_URL` и `TWENTY_API_KEY` из запущенного экземпляра, чтобы ваши тесты могли взаимодействовать с реальным сервером.
**Config knobs:**
**Параметры конфигурации:**
* `TWENTY_VERSION` (env, defaults to `latest`) — pin the Twenty server version used in CI by editing this in `ci.yml`.
* Concurrency is grouped by `github.ref` and cancels in-progress runs on new pushes.
* `TWENTY_VERSION` (переменная окружения, по умолчанию `latest`) — зафиксируйте версию сервера Twenty, используемую в CI, отредактировав это значение в `ci.yml`.
* Параллельные запуски группируются по `github.ref` и отменяют выполняющиеся прогоны при новых пушах.
No secrets are required — the test instance is ephemeral and lives only for the duration of the job.
Секреты не требуются — тестовый экземпляр эфемерен и существует только на время выполнения задания.
### CD — `cd.yml`
Deploys your app to a configured Twenty server on every push to `main`, and optionally from a pull request when the `deploy` label is applied.
Разворачивает ваше приложение на настроенном сервере Twenty при каждом пуше в `main` и, при необходимости, из pull request при наличии метки `deploy`.
**What it does:**
**Что делает:**
1. Checks out the PR head (for labeled PRs) or the pushed commit.
2. Runs `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — the CI equivalent of `yarn twenty deploy`.
3. Runs `twentyhq/twenty/.github/actions/install-twenty-app@main` so the newly deployed version is installed into the target workspace.
1. Извлекает head-коммит PR (для PR с меткой) или запушенный коммит.
2. Запускает `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — эквивалент для CI `yarn twenty deploy`.
3. Запускает `twentyhq/twenty/.github/actions/install-twenty-app@main`, чтобы новая развернутая версия была установлена в целевое рабочее пространство.
**Required configuration:**
**Обязательная конфигурация:**
| Setting | Where | Purpose |
| ----------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `env` in `cd.yml` (defaults to `http://localhost:3000`) | The Twenty server to deploy to. Change this to your real server URL before first use. |
| `TWENTY_DEPLOY_API_KEY` | GitHub repo **Settings → Secrets and variables → Actions** | API key with deploy permission on the target server. |
| Настройка | Где | Назначение |
| ----------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `env` в `cd.yml` (по умолчанию `http://localhost:3000`) | Сервер Twenty, на который выполняется деплой. Перед первым использованием замените это на URL вашего реального сервера. |
| `TWENTY_DEPLOY_API_KEY` | В репозитории GitHub **Settings → Secrets and variables → Actions** | Ключ API с правом деплоя на целевом сервере. |
<Note>
The default `TWENTY_DEPLOY_URL` of `http://localhost:3000` is a placeholder — it will not reach anything from a GitHub-hosted runner. Update it to your server's public URL (or use a self-hosted runner with network access) before enabling CD.
Значение `TWENTY_DEPLOY_URL` по умолчанию — `http://localhost:3000` — это заглушка: с хостируемого GitHub раннера к ней не будет доступа. Перед включением CD замените его на публичный URL вашего сервера (или используйте self-hosted раннер с сетевым доступом).
</Note>
**Triggering a preview deploy from a PR:**
**Запуск предварительного деплоя из PR:**
Add the `deploy` label to a pull request. The `if:` guard in `cd.yml` will run the job for that PR using the PR's head commit, letting you validate a change on the target server before merging.
Добавьте к pull request метку `deploy`. Условие `if:` в `cd.yml` запустит задачу для этого PR, используя его head-коммит, что позволит проверить изменение на целевом сервере до слияния.
### Pinning the reusable actions
### Закрепление версий повторно используемых действий
Both workflows reference reusable actions at `@main`, so action updates in the `twentyhq/twenty` repo are picked up automatically. If you want deterministic builds, replace `@main` with a commit SHA or release tag on each `uses:` line.
Оба рабочих процесса ссылаются на повторно используемые действия с указанием `@main`, поэтому обновления действий в репозитории `twentyhq/twenty` подхватываются автоматически. Если вам нужны детерминированные сборки, замените `@main` на SHA коммита или тег релиза в каждой строке `uses:`.
## Publishing to npm
## Публикация в npm
Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twenty workspace can browse, install, and upgrade marketplace apps directly from the UI.
Публикация в npm делает ваше приложение видимым в маркетплейсе Twenty. Любое рабочее пространство Twenty может просматривать, устанавливать и обновлять приложения из маркетплейса непосредственно из интерфейса.
### Requirements
### Требования
* An [npm](https://www.npmjs.com) account
* The `twenty-app` keyword in your `package.json` `keywords` array (add it manually — it is not included by default in the `create-twenty-app` template)
* Учётная запись [npm](https://www.npmjs.com)
* Ключевое слово `twenty-app` в массиве `keywords` вашего `package.json` (добавьте его вручную — по умолчанию оно не включено в шаблон `create-twenty-app`)
```json filename="package.json"
{
@@ -178,9 +178,9 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
}
```
### Marketplace metadata
### Метаданные маркетплейса
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
Конфигурация `defineApplication()` поддерживает необязательные поля, которые определяют, как ваше приложение отображается в маркетплейсе. Используйте `logoUrl` и `screenshots`, чтобы ссылаться на изображения из папки `public/`:
```ts src/application-config.ts
export default defineApplication({
@@ -196,33 +196,33 @@ export default defineApplication({
});
```
See the [defineApplication accordion](/l/ru/developers/extend/apps/config/application#marketplace-metadata) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
См. [аккордеон defineApplication](/l/ru/developers/extend/apps/config/application#marketplace-metadata) на странице «Создание приложений» для полного списка полей маркетплейса (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` и т. д.).
#### Recommended screenshot dimensions
#### Рекомендуемые размеры скриншотов
The marketplace renders `screenshots` in a fixed `8:5` container (for example, `1600×1000 px`).
Маркетплейс отображает `screenshots` в контейнере с фиксированным соотношением сторон `8:5` (например, `1600×1000 px`).
<Note>
Screenshots of any aspect ratio are displayed in full and are never cropped, but anything significantly taller or narrower than `8:5` will show empty bands on the sides.
Скриншоты с любым соотношением сторон отображаются полностью и никогда не обрезаются, но всё, что значительно выше или уже, чем `8:5`, будет иметь пустые поля по бокам.
</Note>
### Publish
### Публикация
```bash filename="Terminal"
yarn twenty publish
```
To publish under a specific dist-tag (e.g., `beta` or `next`):
Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### How marketplace discovery works
### Как работает обнаружение приложений в маркетплейсе
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**.
You can trigger the sync immediately instead of waiting:
Вы можете запустить синхронизацию немедленно, вместо ожидания:
```bash filename="Terminal"
yarn twenty server catalog-sync
@@ -230,15 +230,15 @@ yarn twenty server catalog-sync
# yarn twenty server catalog-sync --remote production
```
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
Метаданные, отображаемые в маркетплейсе, берутся из конфигурации `defineApplication()` — из таких полей, как `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` и `termsUrl`.
<Note>
If your app does not define an `aboutDescription` in `defineApplication()`, the marketplace will automatically use your package's `README.md` from npm as the about page content. This means you can maintain a single README for both npm and the Twenty marketplace. If you want a different description in the marketplace, explicitly set `aboutDescription`.
Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`.
</Note>
### CI publishing
### Публикация через CI
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
Используйте этот workflow GitHub Actions, чтобы публиковать автоматически при каждом релизе (использует [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -265,31 +265,31 @@ jobs:
working-directory: .twenty/output
```
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `yarn twenty build`, затем `npm publish` из `.twenty/output`.
<Note>
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Note>
## Installing apps
## Установка приложений
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
После публикации приложения (npm) или его развертывания (tarball) рабочие пространства могут установить его через интерфейс.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
Перейдите на страницу **Настройки > Приложения** в Twenty, где можно просматривать и устанавливать как приложения из маркетплейса, так и развернутые через tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
Вы также можете устанавливать приложения из командной строки:
```bash filename="Terminal"
yarn twenty install
```
<Note>
The server enforces semver versioning on install, mirroring the rules on deploy:
Сервер при установке применяет версионирование semver, аналогичное правилам при развёртывании:
* Installing the same version that is already installed in your workspace is rejected with an `APP_ALREADY_INSTALLED` error.
* Installing a lower version than the one currently installed is rejected with a `CANNOT_DOWNGRADE_APPLICATION` error.
* Установка той же версии, которая уже установлена в вашем рабочем пространстве, отклоняется с ошибкой `APP_ALREADY_INSTALLED`.
* Установка версии ниже текущей отклоняется с ошибкой `CANNOT_DOWNGRADE_APPLICATION`.
To install a newer version, deploy or publish it first, then re-run `yarn twenty install`.
Чтобы установить более новую версию, сначала разверните или опубликуйте её, затем снова выполните `yarn twenty install`.
</Note>
@@ -1,22 +1,22 @@
---
title: Testing
description: Vitest setup, integration tests against a real Twenty server, type checking, and CI with GitHub Actions.
title: Тестирование
description: Настройка Vitest, интеграционные тесты с использованием реального сервера Twenty, проверка типов и CI с GitHub Actions.
icon: flask
---
The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server.
SDK предоставляет программные API, которые позволяют собирать, разворачивать, устанавливать и удалять ваше приложение из тестового кода. В сочетании с [Vitest](https://vitest.dev/) и типизированными клиентами API вы можете писать интеграционные тесты, которые проверяют, что ваше приложение работает сквозным образом на реальном сервере Twenty.
## Using npm packages
## Использование пакетов npm
You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime.
Вы можете устанавливать и использовать любые пакеты npm в своём приложении. И логические функции, и компоненты фронтенда собираются с помощью [esbuild](https://esbuild.github.io/), который встраивает все зависимости в выходной файл — каталоги `node_modules` во время выполнения не нужны.
### Installing a package
### Установка пакета
```bash filename="Terminal"
yarn add axios
```
Then import it in your code:
Затем импортируйте его в своём коде:
```ts src/logic-functions/fetch-data.ts
import { defineLogicFunction } from 'twenty-sdk/define';
@@ -37,7 +37,7 @@ export default defineLogicFunction({
});
```
The same works for front components:
То же самое работает для компонентов фронтенда:
```tsx src/front-components/chart.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -54,25 +54,25 @@ export default defineFrontComponent({
});
```
### How bundling works
### Как работает бандлинг
The build step uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle.
Этап сборки использует esbuild для создания одного самодостаточного файла на каждую логическую функцию и на каждый компонент фронтенда. Все импортированные пакеты встроены в бандл.
**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed.
**Логические функции** выполняются в среде Node.js. Встроенные модули Node (`fs`, `path`, `crypto`, `http` и т. д.) доступны и не требуют установки.
**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment.
**Компоненты фронтенда** выполняются в Web Worker. Встроенные модули Node недоступны — доступны только браузерные API и пакеты npm, работающие в браузерной среде.
Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server.
В обеих средах доступны как предварительно предоставленные модули `twenty-client-sdk/core` и `twenty-client-sdk/metadata` — они не включаются в бандл, а подставляются сервером во время выполнения.
## Setup
## Настройка
The scaffolded app already includes Vitest. If you set it up manually, install the dependencies:
Приложение, созданное скэффолдером, уже включает Vitest. Если вы настраиваете его вручную, установите зависимости:
```bash filename="Terminal"
yarn add -D vitest vite-tsconfig-paths
```
Create a `vitest.config.ts` at the root of your app:
Создайте `vitest.config.ts` в корне вашего приложения:
```ts vitest.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';
@@ -98,7 +98,7 @@ export default defineConfig({
});
```
Create a setup file that verifies the server is reachable before tests run:
Создайте файл инициализации, который проверяет доступность сервера перед запуском тестов:
```ts src/__tests__/setup-test.ts
import * as fs from 'fs';
@@ -138,22 +138,22 @@ beforeAll(async () => {
});
```
## Programmatic SDK APIs
## Программные API SDK
The `twenty-sdk/cli` subpath exports functions you can call directly from test code:
Подпуть `twenty-sdk/cli` экспортирует функции, которые можно вызывать напрямую из тестового кода:
| Function | Description |
| -------------- | ------------------------------------------- |
| `appBuild` | Build the app and optionally pack a tarball |
| `appDeploy` | Upload a tarball to the server |
| `appInstall` | Install the app on the active workspace |
| `appUninstall` | Uninstall the app from the active workspace |
| Функция | Описание |
| -------------- | ---------------------------------------------------------- |
| `appBuild` | Собрать приложение и при необходимости упаковать tar-архив |
| `appDeploy` | Загрузить tar-архив на сервер |
| `appInstall` | Установить приложение в активное рабочее пространство |
| `appUninstall` | Удалить приложение из активного рабочего пространства |
Each function returns a result object with `success: boolean` and either `data` or `error`.
Каждая функция возвращает объект результата с `success: boolean` и либо `data`, либо `error`.
## Writing an integration test
## Написание интеграционного теста
Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace:
Полный пример, который собирает, разворачивает и устанавливает приложение, а затем проверяет, что оно появляется в рабочем пространстве:
```ts src/__tests__/app-install.integration-test.ts
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
@@ -216,40 +216,40 @@ describe('App installation', () => {
});
```
## Running tests
## Запуск тестов
Make sure your local Twenty server is running, then:
Убедитесь, что ваш локальный сервер Twenty запущен, затем:
```bash filename="Terminal"
yarn test
```
Or in watch mode during development:
Или в режиме наблюдения во время разработки:
```bash filename="Terminal"
yarn test:watch
```
## Type checking
## Проверка типов
You can also run type checking on your app without running tests:
Вы также можете запустить проверку типов для своего приложения без запуска тестов:
```bash filename="Terminal"
yarn twenty typecheck
```
This runs `tsc --noEmit` and reports any type errors.
Это запускает `tsc --noEmit` и сообщает о любых ошибках типов.
## CI with GitHub Actions
## CI с GitHub Actions
The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests.
Скэффолдер генерирует готовый к использованию рабочий процесс GitHub Actions в `.github/workflows/ci.yml`. Он автоматически запускает ваши интеграционные тесты при каждом пуше в `main` и в pull request'ах.
The workflow:
Рабочий процесс:
1. Checks out your code
2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action
3. Installs dependencies with `yarn install --immutable`
4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs
1. Извлекает ваш код
2. Поднимает временный сервер Twenty с помощью экшена `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Устанавливает зависимости с помощью `yarn install --immutable`
4. Запускает `yarn test` с `TWENTY_API_URL` и `TWENTY_API_KEY`, переданными из выходных данных экшена
```yaml .github/workflows/ci.yml
name: CI
@@ -296,6 +296,6 @@ jobs:
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
```
You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub.
Вам не нужно настраивать секреты — экшен `spawn-twenty-docker-image` запускает эфемерный сервер Twenty прямо в раннере и выводит данные для подключения. Секрет `GITHUB_TOKEN` предоставляется GitHub автоматически.
To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow.
Чтобы закрепить конкретную версию Twenty вместо `latest`, измените переменную окружения `TWENTY_VERSION` в начале рабочего процесса.