Files
twenty/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx
T
cc7131b0b5 i18n - docs translations (#18941)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-25 04:18:13 +01:00

1428 lines
69 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Aplicații Twenty
description: Construiți și gestionați personalizările Twenty sub formă de cod.
---
<Warning>
Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare.
</Warning>
## Ce sunt aplicațiile?
Aplicațiile vă permit să construiți și să gestionați personalizările Twenty **sub formă de cod**. În loc să configurați totul prin interfața de utilizator (UI), vă definiți modelul de date și funcțiile de logică în cod — făcând mai rapidă construirea, mentenanța și implementarea în mai multe spații de lucru.
**Ce puteți face astăzi:**
* Definiți obiecte și câmpuri personalizate sub formă de cod (model de date gestionat)
* Creați funcții de logică cu declanșatoare personalizate
* Definiți abilități și agenți pentru AI
* Implementați aceeași aplicație în mai multe spații de lucru
## Cerințe
* Node.js 24+ și Yarn 4
* Docker (pentru serverul local de dezvoltare Twenty)
## Începeți
Creează o aplicație nouă folosind generatorul oficial. Poate porni automat o instanță Twenty locală pentru tine:
```bash filename="Terminal"
# Creează scheletul unei aplicații noi — CLI-ul îți va oferi opțiunea de a porni un server Twenty local
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Pornește modul de dezvoltare: sincronizează automat modificările locale cu spațiul tău de lucru
yarn twenty dev
```
### Gestionarea serverului local
SDK-ul include comenzi pentru a gestiona un server local de dezvoltare Twenty (imagine Docker all-in-one cu PostgreSQL, Redis, server și worker):
```bash filename="Terminal"
# Pornește serverul local (descarcă imaginea dacă este necesar)
yarn twenty server start
# Verifică starea serverului
yarn twenty server status
# Afișează în timp real jurnalele serverului
yarn twenty server logs
# Oprește serverul
yarn twenty server stop
# Resetează toate datele și pornește de la zero
yarn twenty server reset
```
Serverul local vine preconfigurat cu un spațiu de lucru și un utilizator (`tim@apple.dev` / `tim@apple.dev`), astfel încât să poți începe să dezvolți imediat, fără nicio configurare manuală.
### Autentificare
Conectează-ți aplicația la serverul local folosind OAuth:
```bash filename="Terminal"
# Autentifică-te prin OAuth (se deschide browserul)
yarn twenty remote add --local
```
Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse:
```bash filename="Terminal"
# Implicit (exhaustiv): toate exemplele (obiect, câmp, funcție logică, componentă de interfață, vizualizare, element de meniu de navigare, abilitate, agent)
npx create-twenty-app@latest my-app
# Minimal: doar fișierele de bază (application-config.ts și default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
### Cum să folosești o instanță Twenty locală
Dacă rulezi deja o instanță Twenty locală, te poți conecta la ea în loc să folosești Docker. Specifică portul pe care ascultă serverul tău local (implicit: `3000`):
```bash filename="Terminal"
# During scaffolding
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding
yarn twenty remote add --local --port 3000
```
De aici puteți:
```bash filename="Terminal"
# Adaugă o entitate nouă în aplicația ta (ghidat)
yarn twenty entity:add
# Urmărește jurnalele funcțiilor aplicației tale
yarn twenty function:logs
# Execută o funcție după nume
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
# Execută funcția de pre-instalare
yarn twenty function:execute --preInstall
# Execută funcția post-instalare
yarn twenty function:execute --postInstall
# Construiește aplicația pentru distribuire
yarn twenty build
# Publică aplicația pe npm sau pe un server Twenty
yarn twenty publish
# Dezinstalează aplicația din spațiul de lucru curent
yarn twenty uninstall
# Afișează ajutorul pentru comenzi
yarn twenty help
```
Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
## Structura proiectului (generată)
Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
* Copiază o aplicație de bază minimală în `my-twenty-app/`
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului.
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
```text filename="my-twenty-app/"
my-twenty-app/
package.json
yarn.lock
.gitignore
.nvmrc
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
tsconfig.json
README.md
public/ # Director pentru resurse publice (imagini, fonturi etc.)
src/
├── application-config.ts # Obligatoriu - configurația principală a aplicației
├── roles/
│ └── default-role.ts # Rol implicit pentru funcțiile logice
├── objects/
│ └── example-object.ts # Exemplu de definiție a unui obiect personalizat
├── fields/
│ └── example-field.ts # Exemplu de definiție de câmp independent
├── logic-functions/
│ ├── hello-world.ts # Exemplu de funcție logică
│ ├── pre-install.ts # Funcție logică de pre-instalare
│ └── post-install.ts # Funcție logică post-instalare
├── front-components/
│ └── hello-world.tsx # Exemplu de componentă de interfață
├── views/
│ └── example-view.ts # Exemplu de definiție a unei vizualizări salvate
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Exemplu de link de navigare în bara laterală
├── skills/
│ └── example-skill.ts # Exemplu de definiție a unei abilități a agentului AI
└── agents/
└── example-agent.ts # Exemplu de definiție a unui agent AI
```
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`).
Pe scurt:
* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulează `yarn twenty help` pentru a lista toate comenzile disponibile.
* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `generated/` (client tipizat), `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect.
* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect.
* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază.
* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicația ta. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare.
* **src/**: Locul principal unde vă definiți aplicația sub formă de cod
### Detectarea entităților
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
| Funcție ajutătoare | Tipul entității |
| ---------------------------------- | -------------------------------------------------------------- |
| `defineObject()` | Definiții de obiecte personalizate |
| `defineLogicFunction()` | Definiții de funcții de logică |
| `definePreInstallLogicFunction()` | Funcție logică de pre-instalare (rulează înainte de instalare) |
| `definePostInstallLogicFunction()` | Funcție logică post-instalare (rulează după instalare) |
| `defineFrontComponent()` | Definiții ale componentelor de interfață |
| `defineRole()` | Definiții de rol |
| `defineField()` | Extensii de câmp pentru obiectele existente |
| `defineView()` | Definiții pentru vizualizări salvate |
| `defineNavigationMenuItem()` | Definiții pentru elemente de meniu de navigare |
| `defineSkill()` | Definiții ale abilităților agentului AI |
| `defineAgent()` | Definiții ale agenților AI |
<Note>
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
</Note>
Exemplu de entitate detectată:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Comenzile ulterioare vor adăuga mai multe fișiere și foldere:
* `yarn twenty dev` va genera automat doi clienți API tipizați în `node_modules/twenty-sdk/clients`: `CoreApiClient` (pentru datele spațiului de lucru prin `/graphql`) și `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`).
* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end, rolurile, abilitățile și altele.
## Autentificare
Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita:
* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent)
* Cheie API
Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele.
### Gestionarea spațiilor de lucru
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace <name>`.
## Utilizați resursele SDK (tipuri și configurare)
Biblioteca twenty-sdk oferă blocuri de bază tipizate și funcții ajutătoare pe care le utilizați în aplicația dvs. Mai jos sunt elementele cheie cu care veți interacționa cel mai des.
### Funcții ajutătoare
SDK-ul oferă funcții ajutătoare pentru definirea entităților aplicației. După cum este descris în [Detectarea entităților](#entity-detection), trebuie să folosiți `export default define<Entity>({...})` pentru ca entitățile să fie detectate:
| Funcție | Scop |
| ---------------------------------- | ---------------------------------------------------------------------- |
| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) |
| `defineObject()` | Definiți obiecte personalizate cu câmpuri |
| `defineLogicFunction()` | Definiți funcții de logică cu handleri |
| `definePreInstallLogicFunction()` | Definește o funcție logică de pre-instalare (una per aplicație) |
| `definePostInstallLogicFunction()` | Definește o funcție logică post-instalare (una per aplicație) |
| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată |
| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte |
| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare |
| `defineView()` | Definește vizualizări salvate pentru obiecte |
| `defineNavigationMenuItem()` | Definește linkuri de navigare în bara laterală |
| `defineSkill()` | Definiți abilități pentru agentul AI |
| `defineAgent()` | Definiți agenți AI cu prompturi de sistem |
Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor.
### Definirea obiectelor
Obiectele personalizate descriu atât schema, cât și comportamentul înregistrărilor din spațiul dvs. de lucru. Utilizați `defineObject()` pentru a defini obiecte cu validare încorporată:
```typescript
// src/app/postCard.object.ts
import { defineObject, FieldType } from 'twenty-sdk';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
Puncte cheie:
* Folosiți `defineObject()` pentru validare încorporată și suport mai bun în IDE.
* `universalIdentifier` trebuie să fie unic și stabil între implementări.
* Fiecare câmp necesită un `name`, un `type`, un `label` și propriul `universalIdentifier` stabil.
* Matricea `fields` este opțională — puteți defini obiecte fără câmpuri personalizate.
* Puteți genera obiecte noi folosind `yarn twenty entity:add`, care vă ghidează prin denumire, câmpuri și relații.
<Note>
**Câmpurile de bază sunt create automat.** Când definiți un obiect personalizat, Twenty adaugă automat câmpuri standard
precum `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` și `deletedAt`.
Nu trebuie să le definiți în tabloul `fields` — adăugați doar câmpurile personalizate proprii.
Puteți suprascrie câmpurile implicite definind un câmp cu același nume în tabloul `fields`,
dar acest lucru nu este recomandat.
</Note>
### Definirea câmpurilor pe obiecte existente
Folosiți `defineField()` pentru a adăuga câmpuri personalizate la obiectele existente — atât obiecte standard (precum `company`, `person`, `opportunity`), cât și obiecte personalizate definite de alte aplicații. Fiecare câmp se află în propriul fișier și face referire la obiectul țintă prin `universalIdentifier`.
Pentru a face referire la obiectele standard, importați `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` din `twenty-sdk`. Această constantă furnizează identificatori stabili pentru toate obiectele integrate și câmpurile lor:
```typescript
// src/fields/apollo-total-funding.field.ts
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
export default defineField({
universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
type: FieldType.CURRENCY,
name: 'apolloTotalFunding',
label: 'Total Funding',
description: 'Total funding raised by the company',
icon: 'IconCash',
});
```
Puncte cheie:
* `objectUniversalIdentifier` îi indică lui Twenty la care obiect să atașeze câmpul. Folosiți `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<objectName>.universalIdentifier` pentru obiectele standard.
* Fiecare câmp necesită propriul `universalIdentifier` stabil, un `name`, `type`, `label` și `objectUniversalIdentifier` țintă.
* Puteți genera câmpuri noi folosind `yarn twenty entity:add` și alegând opțiunea pentru câmp.
* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` este exportată și ca `STANDARD_OBJECT` pentru comoditate — ambele se referă la aceeași constantă.
Obiectele standard disponibile includ: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` și `workspaceMember`.
Fiecare obiect standard expune, de asemenea, identificatorii câmpurilor sale. De exemplu, pentru a face referire la un câmp specific pe un obiect standard în permisiunile de rol:
```typescript
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier
```
#### Câmpuri de relație pe obiecte existente
Puteți defini, de asemenea, câmpuri de relație care leagă obiectele existente de obiectele dvs. personalizate:
```typescript
// src/fields/people-on-call-recording.field.ts
import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk';
import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording';
import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field';
export default defineField({
universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
objectUniversalIdentifier:
CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
CALL_RECORDING_ON_PERSON_ID,
relationType: RelationType.MANY_TO_ONE,
});
```
### Configurația aplicației (application-config.ts)
Fiecare aplicație are un singur fișier `application-config.ts` care descrie:
* **Cine este aplicația**: identificatori, nume de afișare și descriere.
* **Cum rulează funcțiile**: ce rol folosesc pentru permisiuni.
* **(Opțional) variabile**: perechi cheievaloare expuse funcțiilor ca variabile de mediu.
* **(Opțional) funcție de pre-instalare**: o funcție logică care rulează înainte ca aplicația să fie instalată.
* **(Opțional) funcție post-instalare**: o funcție logică care rulează după instalarea aplicației.
Folosiți `defineApplication()` pentru a defini configurația aplicației:
```typescript
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
icon: 'IconWorld',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
Notițe:
* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți; generați-le o singură dată și păstrați-le stabile între sincronizări.
* `applicationVariables` devin variabile de mediu pentru funcțiile dvs. (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` trebuie să corespundă fișierului de rol (vedeți mai jos).
* Funcțiile de pre-instalare și post-instalare sunt detectate automat în timpul construirii manifestului. Vezi [Funcții de pre-instalare](#pre-install-functions) și [Funcții post-instalare](#post-install-functions).
#### Roluri și permisiuni
Aplicațiile pot defini roluri care încapsulează permisiuni asupra obiectelor și acțiunilor din spațiul dvs. de lucru. Câmpul `defaultRoleUniversalIdentifier` din `application-config.ts` desemnează rolul implicit utilizat de funcțiile de logică ale aplicației.
* Cheia API de runtime injectată ca `TWENTY_API_KEY` este derivată din acest rol implicit pentru funcții.
* Clientul tipizat va fi restricționat la permisiunile acordate acelui rol.
* Respectați principiul celui mai mic privilegiu: creați un rol dedicat doar cu permisiunile de care au nevoie funcțiile, apoi referiți identificatorul său universal.
##### Rol implicit pentru funcții (\*.role.ts)
Când generați o aplicație nouă, CLI creează și un fișier de rol implicit. Folosiți `defineRole()` pentru a defini roluri cu validare încorporată:
```typescript
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
fieldPermissions: [
{
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlags: [PermissionFlag.APPLICATIONS],
});
```
`universalIdentifier` al acestui rol este apoi referențiat în `application-config.ts` ca `defaultRoleUniversalIdentifier`. Cu alte cuvinte:
* **\*.role.ts** definește ce poate face rolul implicit pentru funcții.
* **application-config.ts** indică acel rol, astfel încât funcțiile moștenesc permisiunile lui.
Notițe:
* Porniți de la rolul generat, apoi restrângeți-l progresiv urmând principiul celui mai mic privilegiu.
* Înlocuiți `objectPermissions` și `fieldPermissions` cu obiectele/câmpurile de care au nevoie funcțiile.
* `permissionFlags` controlează accesul la capabilități la nivelul platformei. Mențineți-le la minimum; adăugați doar ceea ce aveți nevoie.
* Vedeți un exemplu funcțional în aplicația Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Configurația funcției de logică și punctul de intrare
Fiecare fișier de funcție folosește `defineLogicFunction()` pentru a exporta o configurație cu un handler și declanșatoare opționale.
```typescript
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import { CoreApiClient, type Person } from 'twenty-client-sdk/core';
const handler = async (params: RoutePayload) => {
const client = new CoreApiClient();
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
: 'Hello world';
const result = await client.mutation({
createPostCard: {
__args: { data: { name } },
id: true,
name: true,
},
});
return result;
};
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
handler,
triggers: [
// Public HTTP route trigger '/s/post-card/create'
{
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/post-card/create',
httpMethod: 'GET',
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});},{
```
Tipuri comune de declanșatoare:
* **route**: Expune funcția pe o rută și metodă HTTP **sub endpoint-ul `/s/`**:
> de ex. `path: '/post-card/create',` -> apel pe `<APP_URL>/s/post-card/create`
* **cron**: Rulează funcția pe un program folosind o expresie CRON.
* **databaseEvent**: Rulează la evenimentele ciclului de viață ale obiectelor din spațiul de lucru. Când operațiunea evenimentului este `updated`, câmpurile specifice de urmărit pot fi specificate în array-ul `updatedFields`. Dacă este lăsat nedefinit sau gol, orice actualizare va declanșa funcția.
> de ex. `person.updated`
Notițe:
* Matricea `triggers` este opțională. Funcțiile fără declanșatoare pot fi folosite ca funcții utilitare apelate de alte funcții.
* Puteți combina mai multe tipuri de declanșatoare într-o singură funcție.
### Funcții de pre-instalare
O funcție de pre-instalare este o funcție logică ce rulează automat înainte ca aplicația ta să fie instalată într-un spațiu de lucru. Aceasta este utilă pentru sarcini de validare, verificări ale condițiilor prealabile sau pregătirea stării spațiului de lucru înainte ca instalarea principală să continue.
Când creezi scheletul unei aplicații noi cu `create-twenty-app`, ți se generează o funcție de pre-instalare la `src/logic-functions/pre-install.ts`:
```typescript
// src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
handler,
});
```
Poți, de asemenea, să execuți manual funcția de pre-instalare oricând folosind CLI:
```bash filename="Terminal"
yarn twenty function:execute --preInstall
```
Puncte cheie:
* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi).
* Este permisă o singură funcție de pre-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una.
* Proprietatea `universalIdentifier` a funcției este setată automat ca `preInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`.
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de pregătire mai lungi.
* Funcțiile de pre-instalare nu au nevoie de declanșatoare — sunt invocate de platformă înainte de instalare sau manual prin `function:execute --preInstall`.
### Funcții post-instalare
O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru.
Când creezi scheletul unei aplicații noi cu `create-twenty-app`, este generată o funcție post-instalare la `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
export default definePostInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Puncte cheie:
* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi).
* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una.
* Proprietatea `universalIdentifier` a funcției este setată automat ca `postInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`.
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor.
* Funcțiile post-instalare nu au nevoie de declanșatoare — sunt invocate de platformă în timpul instalării sau manual prin `function:execute --postInstall`.
### Payload-ul declanșatorului de rută
<Warning>
**Modificare incompatibilă (v1.16, ianuarie 2026):** Formatul payload-ului declanșatorului de rută s-a schimbat. Înainte de v1.16, parametrii de interogare (query), parametrii de cale și corpul erau trimiși direct ca payload. Începând cu v1.16, acestea sunt incluse într-un obiect structurat `RoutePayload`.
**Înainte de v1.16:**
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
**După v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**Pentru a migra funcțiile existente:** Actualizează handler-ul pentru a extrage câmpurile din `event.body`, `event.queryStringParameters` sau `event.pathParameters` în loc să le iei direct din obiectul params.
</Warning>
Când un declanșator de rută apelează funcția dvs. de logică, aceasta primește un obiect `RoutePayload` care urmează formatul AWS HTTP API v2. Importă tipul din `twenty-sdk`:
```typescript
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
const { headers, queryStringParameters, pathParameters, body } = event;
// HTTP method and path are available in requestContext
const { method, path } = event.requestContext.http;
return { message: 'Success' };
};
```
Tipul `RoutePayload` are următoarea structură:
| Proprietate | Tip | Descriere |
| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ |
| `headers` | `Record<string, string \| undefined>` | Anteturi HTTP (doar cele listate în `forwardedRequestHeaders`) |
| `queryStringParameters` | `Record<string, string \| undefined>` | Parametri query string (valorile multiple unite cu virgule) |
| `pathParameters` | `Record<string, string \| undefined>` | Parametri de cale extrași din modelul rutei (de ex., `/users/:id` → `{ id: '123' }`) |
| `corp` | `object \| null` | Corpul cererii analizat (JSON) |
| `isBase64Encoded` | `boolean` | Indică dacă corpul este codificat în base64 |
| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) |
| `requestContext.http.path` | `string` | Calea brută a cererii |
### Transmiterea anteturilor HTTP
În mod implicit, anteturile HTTP din cererile de intrare **nu** sunt transmise funcției dvs. de logică din motive de securitate. Pentru a accesa anumite anteturi, listează-le explicit în array-ul `forwardedRequestHeaders`:
```typescript
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
triggers: [
{
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/webhook',
httpMethod: 'POST',
isAuthRequired: false,
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
},
],
});
```
În handler, poți apoi accesa aceste anteturi:
```typescript
const handler = async (event: RoutePayload) => {
const signature = event.headers['x-webhook-signature'];
const contentType = event.headers['content-type'];
// Validate webhook signature...
return { received: true };
};
```
<Note>
Numele anteturilor sunt normalizate la litere mici. Accesează-le folosind chei cu litere mici (de exemplu, `event.headers['content-type']`).
</Note>
Puteți crea funcții noi în două moduri:
* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o funcție logică nouă. Aceasta generează un fișier inițial cu un handler și o configurație.
* **Manual**: Creați un fișier nou `*.logic-function.ts` și folosiți `defineLogicFunction()`, urmând același model.
### Marcarea unei funcții logice drept instrument
Funcțiile logice pot fi expuse ca **instrumente** pentru agenți de IA și fluxuri de lucru. Când o funcție este marcată ca instrument, poate fi descoperită de funcționalitățile de IA ale Twenty și poate fi selectată ca pas în automatizări ale fluxurilor de lucru.
Pentru a marca o funcție logică drept instrument, setați `isTool: true` și furnizați un `toolInputSchema` care descrie parametrii de intrare așteptați folosind [JSON Schema](https://json-schema.org/):
```typescript
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (params: { companyName: string; domain?: string }) => {
const client = new CoreApiClient();
const result = await client.mutation({
createTask: {
__args: {
data: {
title: `Enrich data for ${params.companyName}`,
body: `Domain: ${params.domain ?? 'unknown'}`,
},
},
id: true,
},
});
return { taskId: result.createTask.id };
};
export default defineLogicFunction({
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
name: 'enrich-company',
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
isTool: true,
toolInputSchema: {
type: 'object',
properties: {
companyName: {
type: 'string',
description: 'The name of the company to enrich',
},
domain: {
type: 'string',
description: 'The company website domain (optional)',
},
},
required: ['companyName'],
},
});
```
Puncte cheie:
* **`isTool`** (`boolean`, implicit: `false`): Când este setat la `true`, funcția este înregistrată ca instrument și devine disponibilă pentru agenții AI și automatizările de fluxuri de lucru.
* **`toolInputSchema`** (`object`, opțional): Un obiect JSON Schema care descrie parametrii pe care îi acceptă funcția dvs. Agenții AI folosesc această schemă pentru a înțelege ce intrări așteaptă instrumentul și pentru a valida apelurile. Dacă este omisă, schema are implicit valoarea `{ type: 'object', properties: {} }` (fără parametri).
* Funcțiile cu `isTool: false` (sau nedefinit) **nu** sunt expuse ca instrumente. Pot totuși fi executate direct sau apelate de alte funcții, dar nu vor apărea în descoperirea instrumentelor.
* **Denumierea instrumentelor**: Când este expusă ca instrument, denumirea funcției este normalizată automat la `logic_function_<name>` (convertită la litere mici, iar caracterele non-alfanumerice sunt înlocuite cu caractere de subliniere). De exemplu, `enrich-company` devine `logic_function_enrich_company`.
* Puteți combina `isTool` cu declanșatoare — o funcție poate fi atât un instrument (apelabilă de agenții AI), cât și declanșată de evenimente (cron, evenimente de bază de date, rute) în același timp.
<Note>
**Scrieți o `description` bună.** Agenții AI se bazează pe câmpul `description` al funcției pentru a decide când să folosească instrumentul. Fiți specifici cu privire la ceea ce face instrumentul și când ar trebui apelat.
</Note>
### Componente Front
Componentele Front vă permit să construiți componente React personalizate care sunt randate în interfața Twenty. Utilizați `defineFrontComponent()` pentru a defini componente cu validare încorporată:
```typescript
// src/front-components/my-widget.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>My Custom Widget</h1>
<p>This is a custom front component for Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-widget',
description: 'A custom widget component',
component: MyWidget,
});
```
Puncte cheie:
* Componentele Front sunt componente React care sunt randate în contexte izolate în cadrul Twenty.
* Câmpul `component` face referire la componenta React.
* Componentele sunt construite și sincronizate automat în timpul `yarn twenty dev`.
Puteți crea componente Front noi în două moduri:
* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o componentă frontend nouă.
* **Manual**: Creați un fișier nou `.tsx` și folosiți `defineFrontComponent()`, urmând același model.
#### Where front components can be used
Front components can render in two locations within 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. When configuring a dashboard or a record page layout, users can add a front component widget.
#### Headless vs non-headless
Front components come in two rendering modes controlled by the `isHeadless` option:
**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.
**Headless** — 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.
```typescript
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-action',
description: 'Runs an action without opening the side panel',
component: MyAction,
isHeadless: true,
command: {
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901',
label: 'Run my action',
},
});
```
#### Adding command menu items
To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click.
The `command` object accepts the following fields:
| Câmp | Tip | Descriere |
| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `universalIdentifier` | `string` (required) | Unique ID for the command menu item |
| `etichetă` | `string` (required) | Display label shown in the command menu |
| `pictogramă` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) |
| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu |
| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts |
| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) |
Here is an example from the call-recording app that adds a command scoped to Person records:
```typescript
import { defineFrontComponent } from 'twenty-sdk';
export default defineFrontComponent({
universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012',
name: 'Summarize Person Call Recordings',
description: 'Generates a summary of call recordings for a person',
component: SummarizePersonRecordings,
command: {
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123',
label: 'Summarize call recordings',
icon: 'IconSparkles',
isPinned: false,
availabilityType: 'RECORD_SELECTION',
availabilityObjectUniversalIdentifier:
'20202020-e674-48e5-a542-72570eee7213',
},
});
```
When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic.
#### SDK Command components
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.
Import them from `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`.
Here is a full example of a headless front component using `Command` to run an action from the command menu:
```typescript
// src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
command: {
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
},
});
```
And an example using `CommandModal` to ask for confirmation before executing:
```typescript
// src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk';
import { CommandModal } from 'twenty-sdk/command';
const DeleteDraft = () => {
const execute = async () => {
// perform the deletion
};
return (
<CommandModal
title="Delete draft?"
subtitle="This action cannot be undone."
execute={execute}
confirmButtonText="Delete"
confirmButtonAccent="danger"
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
name: 'delete-draft',
description: 'Deletes a draft with confirmation',
component: DeleteDraft,
isHeadless: true,
command: {
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
label: 'Delete draft',
icon: 'IconTrash',
},
});
```
#### Execution context
Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`:
| Hook | Return type | Descriere |
| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `useFrontComponentId()` | `string` | The unique ID of the current front component instance |
| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. |
| `useUserId()` | `string \| null` | The ID of the current user |
```typescript
import { useRecordId, useUserId } from 'twenty-sdk';
const MyWidget = () => {
const recordId = useRecordId();
const userId = useUserId();
return (
<div>
<p>Record: {recordId ?? 'none'}</p>
<p>User: {userId ?? 'anonymous'}</p>
</div>
);
};
```
The context is reactive — if the surrounding record changes, hooks automatically return the updated values.
#### Host API functions
Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`:
```typescript
import {
navigate,
closeSidePanel,
enqueueSnackbar,
unmountFrontComponent,
openSidePanelPage,
openCommandConfirmationModal,
} from 'twenty-sdk';
```
| Funcție | Signature | Descriere |
| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `navigate` | `(to, params?, queryParams?, options?) => Promise<void>` | Navigate to a typed app path within Twenty |
| `closeSidePanel` | `() => Promise<void>` | Close the side panel |
| `enqueueSnackbar` | `(params) => Promise<void>` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` |
| `unmountFrontComponent` | `() => Promise<void>` | Unmount the current front component (used by headless components to clean up after execution) |
| `openSidePanelPage` | `(params) => Promise<void>` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` |
| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) |
Here is an example that uses the host API to show a snackbar and close the side panel after an action completes:
```typescript
import { defineFrontComponent, useRecordId } from 'twenty-sdk';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk';
import { CoreApiClient } from 'twenty-sdk/clients';
const ArchiveRecord = () => {
const recordId = useRecordId();
const handleArchive = async () => {
const client = new CoreApiClient();
await client.mutation({
updateTask: {
__args: { id: recordId, data: { status: 'ARCHIVED' } },
id: true,
},
});
await enqueueSnackbar({
message: 'Record archived',
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Archive this record?</p>
<button onClick={handleArchive}>Archive</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
name: 'archive-record',
description: 'Archives the current record',
component: ArchiveRecord,
});
```
### Abilități
Abilitățile definesc instrucțiuni și capabilități reutilizabile pe care agenții AI le pot folosi în spațiul dvs. de lucru. Folosiți `defineSkill()` pentru a defini abilități cu validare încorporată:
```typescript
// src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk';
export default defineSkill({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'sales-outreach',
label: 'Abordare de vânzări',
description: 'Ghidează agentul AI printr-un proces structurat de abordare de vânzări',
icon: 'IconBrain',
content: `Ești un asistent pentru abordare de vânzări. Când contactezi un potențial client:
1. Cercetează compania și noutățile recente
2. Identifică rolul potențialului client și probabilele puncte sensibile
3. Redactează un mesaj personalizat care face referire la detalii specifice
4. Păstrează un ton profesionist, dar conversațional`,
});
```
Puncte cheie:
* `name` este un șir identificator unic pentru abilitate (se recomandă kebab-case).
* `label` este numele lizibil afișat în interfața cu utilizatorul (UI).
* `content` conține instrucțiunile abilității — acesta este textul pe care agentul AI îl folosește.
* `icon` (opțional) setează pictograma afișată în UI.
* `description` (opțional) oferă context suplimentar despre scopul abilității.
Puteți crea abilități noi în două moduri:
* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o abilitate nouă.
* **Manual**: Creați un fișier nou și folosiți `defineSkill()`, urmând același model.
### Agenți
Agenții sunt agenți AI definiți cu prompturi de sistem, care pot funcționa în spațiul dvs. de lucru. Utilizați `defineAgent()` pentru a defini agenți cu validare încorporată:
```typescript
// src/agents/example-agent.ts
import { defineAgent } from 'twenty-sdk';
export default defineAgent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'sales-assistant',
label: 'Sales Assistant',
description: 'An AI agent that helps with sales tasks',
icon: 'IconRobot',
prompt: `You are a sales assistant. Help users with:
1. Researching prospects and companies
2. Drafting personalized outreach messages
3. Tracking follow-ups and next steps
4. Analyzing deal pipeline and suggesting actions`,
});
```
Puncte cheie:
* `name` este un șir identificator unic pentru agent (se recomandă kebab-case).
* `label` este numele lizibil afișat în interfața cu utilizatorul (UI).
* `prompt` conține promptul de sistem — acesta este textul de instrucțiuni care definește comportamentul agentului.
* `icon` (opțional) setează pictograma afișată în UI.
* `description` (opțional) oferă context suplimentar despre scopul agentului.
Puteți crea agenți noi în două moduri:
* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga un agent nou.
* **Manual**: Creați un fișier nou și folosiți `defineAgent()`, urmând același model.
### Generated typed clients
Doi clienți tipizați sunt generați automat de `yarn twenty dev` și stocați în `node_modules/twenty-sdk/clients`, pe baza schemei spațiului tău de lucru:
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
* **`MetadataApiClient`** — interoghează endpointul `/metadata` pentru configurarea spațiului de lucru și încărcarea fișierelor
```typescript
import { CoreApiClient } from 'twenty-client-sdk/core';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
const client = new CoreApiClient();
const { me } = await client.query({ me: { id: true, displayName: true } });
const metadataClient = new MetadataApiClient();
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
```
`CoreApiClient` este regenerat automat de `yarn twenty dev` ori de câte ori obiectele sau câmpurile tale se schimbă. `MetadataApiClient` este livrat preconstruit împreună cu SDK-ul.
#### Acreditări la runtime în funcțiile de logică
Când funcția rulează pe Twenty, platforma injectează acreditări ca variabile de mediu înainte de execuția codului:
* `TWENTY_API_URL`: URL-ul de bază al API-ului Twenty către care țintește aplicația.
* `TWENTY_API_KEY`: Cheie cu durată scurtă, limitată la rolul implicit de funcție al aplicației.
Notițe:
* Nu trebuie să transmiteți URL-ul sau cheia API către clientul generat. Acesta citește `TWENTY_API_URL` și `TWENTY_API_KEY` din process.env la runtime.
* Permisiunile cheii API sunt determinate de rolul referențiat în `application-config.ts` prin `defaultRoleUniversalIdentifier`. Acesta este rolul implicit folosit de funcțiile de logică ale aplicației.
* Aplicațiile pot defini roluri pentru a urma principiul celui mai mic privilegiu. Acordați doar permisiunile de care au nevoie funcțiile, apoi setați `defaultRoleUniversalIdentifier` la identificatorul universal al acelui rol.
#### Încărcarea fișierelor
`MetadataApiClient` include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier ale obiectelor din spațiul tău de lucru. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec).
```typescript
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import * as fs from 'fs';
const metadataClient = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
const uploadedFile = await metadataClient.uploadFile(
fileBuffer, // file contents as a Buffer
'invoice.pdf', // filename
'application/pdf', // MIME type (defaults to 'application/octet-stream')
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier
);
console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
```
Semnătura metodei:
```typescript
uploadFile(
fileBuffer: Buffer,
filename: string,
contentType: string,
fieldMetadataUniversalIdentifier: string,
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
```
| Parametru | Tip | Descriere |
| ---------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `fileBuffer` | `Buffer` | Conținutul brut al fișierului |
| `filename` | `string` | Numele fișierului (folosit pentru stocare și afișare) |
| `contentType` | `string` | Tipul MIME al fișierului (are valoarea implicită `application/octet-stream` dacă este omis) |
| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` al câmpului de tip fișier de pe obiectul tău |
Puncte cheie:
* Metoda `uploadFile` este disponibilă pe `MetadataApiClient` deoarece mutația de încărcare este rezolvată de endpointul `/metadata`.
* Folosește `universalIdentifier` al câmpului (nu ID-ul specific spațiului de lucru), astfel încât codul tău de încărcare funcționează în orice spațiu de lucru în care aplicația ta este instalată — în concordanță cu modul în care aplicațiile fac referire la câmpuri în rest.
* `url` returnat este un URL semnat pe care îl poți folosi pentru a accesa fișierul încărcat.
### Exemplu Hello World
Explorați un exemplu minim, cap la cap, care demonstrează obiecte, funcții de logică, componente Front și declanșatoare multiple [aici](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Building your app
Once you've developed your app with `app:dev`, use `app:build` to compile it into a distributable package.
```bash filename="Terminal"
# Construiește aplicația (rezultatul este plasat în .twenty/output/)
yarn twenty build
# Construiește și creează un pachet tar (.tgz) pentru distribuire
yarn twenty build --tarball
```
The build process:
1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure.
2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild.
3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generează clientul API tipizat** — examinează schema GraphQL și generează clienți tipizați `CoreApiClient` și `MetadataApiClient`.
5. **Rulează o verificare a tipurilor TypeScript** — rulează `tsc --noEmit` pentru a detecta erorile de tip înainte de publicare.
6. **Reconstruiește cu clientul generat** — efectuează o a doua trecere de compilare astfel încât tipurile clientului generat să fie incluse.
7. **Creează opțional un tarball** — dacă se trece `--tarball`, rulează `npm pack` pentru a crea un fișier `.tgz` gata pentru distribuire.
Rezultatul build-ului din `.twenty/output/` conține:
```text
.twenty/output/
├── manifest.json # Manifest cu sume de control pentru toate fișierele generate
├── package.json # Copiat din rădăcina aplicației
├── yarn.lock # Copiat din rădăcina aplicației
├── src/
│ ├── logic-functions/ # Fișiere .mjs de funcții de logică compilate
│ └── front-components/ # Fișiere .mjs de componente front-end compilate
├── public/ # Resurse statice (dacă există)
└── my-app-1.0.0.tgz # Doar cu opțiunea --tarball
```
| Opțiune | Descriere |
| ----------- | -------------------------------------------------------------- |
| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) |
| `--tarball` | De asemenea, împachetează rezultatul într-un tarball `.tgz` |
## Publicarea aplicației
Folosește `app:publish` pentru a distribui aplicația — fie în registrul npm, fie direct pe un server Twenty.
### Publicare pe npm (implicit)
```bash filename="Terminal"
# Publică pe npm (necesită autentificare npm)
yarn twenty publish
# Publică cu un dist-tag (de ex. beta, next)
yarn twenty publish --tag beta
```
Aceasta construiește aplicația și rulează `npm publish` din directorul `.twenty/output/`. Pachetul publicat poate fi apoi instalat din marketplace-ul Twenty de către orice spațiu de lucru.
### Publicare pe un server Twenty
```bash filename="Terminal"
# Publică direct pe un server Twenty
yarn twenty publish --server https://app.twenty.com
```
Aceasta construiește aplicația cu un tarball, o încarcă pe server prin mutația GraphQL `uploadAppTarball` și declanșează instalarea într-un singur pas. Acest lucru este util pentru implementări private sau pentru testare pe un server specific.
| Opțiune | Descriere |
| ----------------- | -------------------------------------------------------------------- |
| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) |
| `--server <url>` | Publică pe un server Twenty în loc de npm |
| `--token <token>` | Jeton de autentificare pentru serverul țintă |
| `--tag <tag>` | npm dist-tag (de ex. `beta`, `next`) — doar pentru publicarea pe npm |
## Înregistrarea aplicației
Înainte ca o aplicație să poată fi instalată într-un spațiu de lucru, aceasta trebuie să fie **înregistrată**. O înregistrare este o înregistrare de metadate care descrie de unde provine aplicația și cum se autentifică. Acest lucru este gestionat automat de CLI în cele mai multe cazuri.
### Tipuri de sursă
Fiecare înregistrare are un **tip de sursă** care determină modul în care fișierele aplicației sunt preluate în timpul instalării:
| Tip de sursă | Cum sunt preluate fișierele | Caz de utilizare tipic |
| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------- |
| `LOCAL` | Fișierele sunt sincronizate în timp real de către watcher-ul CLI — instalarea este omisă | Dezvoltare cu `app:dev` |
| `NPM` | Obținute din registrul npm prin câmpul `sourcePackage` | Aplicații publicate pe npm |
| `TARBALL` | Extrase dintr-un fișier `.tgz` încărcat, stocat pe server | Aplicații private publicate cu `--server` |
### Cum are loc înregistrarea
* **`app:dev`** — creează automat o înregistrare `LOCAL` prima dată când rulezi modul de dezvoltare pentru un spațiu de lucru.
* **`app:publish --server`** — încarcă un tarball și creează (sau actualizează) o înregistrare `TARBALL`, apoi instalează aplicația.
* **marketplace-ul npm** — înregistrările `NPM` sunt create când aplicațiile sunt sincronizate din registrul npm în catalogul marketplace-ului Twenty.
* **API GraphQL** — poți de asemenea să creezi înregistrări programatic prin mutația `createApplicationRegistration`.
### Înregistrare vs instalare
**Înregistrarea** și **instalarea** sunt concepte separate:
* O **înregistrare** (`ApplicationRegistration`) este o înregistrare globală de metadate care descrie aplicația: numele ei, tipul de sursă, acreditările OAuth și statutul listării în marketplace. Există independent de orice spațiu de lucru.
* O **instalare** (`Application`) este o instanță per spațiu de lucru. Când un utilizator instalează o aplicație, Twenty rezolvă pachetul din sursa înregistrării, scrie fișierele compilate în stocare și sincronizează manifestul (creând obiecte, câmpuri, funcții de logică etc.) în acel spațiu de lucru.
O singură înregistrare poate fi instalată în multe spații de lucru. Fiecare spațiu de lucru primește propria copie a fișierelor și a modelului de date al aplicației.
### Acreditări OAuth
Fiecare înregistrare include acreditări OAuth (`oAuthClientId` și `oAuthClientSecret`) generate la momentul creării. Acestea sunt folosite de aplicație pentru a autentifica cererile API în numele utilizatorilor. Secretul clientului este returnat **o singură dată** la creare — păstrează-l în siguranță. Îl poți roti ulterior prin mutația `rotateApplicationRegistrationClientSecret`.
## Configurare manuală (fără generator)
Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
Apoi adăugați un script `twenty`:
```json filename="package.json"
{
"scripts": {
"twenty": "twenty"
}
}
```
Acum poți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty dev`, `yarn twenty help`, etc.
## Depanare
* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare.
* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil.
* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat.
* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău.
Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322