Files
twenty/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx
T
a6cecdbd49 i18n - docs translations (#19137)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 20:38:05 +02:00

1428 lines
70 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: App di Twenty
description: Crea e gestisci le personalizzazioni di Twenty come codice.
---
<Warning>
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
</Warning>
## Cosa sono le app?
Le app ti consentono di creare e gestire le personalizzazioni di Twenty **come codice**. Invece di configurare tutto tramite l'interfaccia utente, definisci in codice il modello dati e le funzioni logiche — rendendo più veloce creare, mantenere e distribuire su più spazi di lavoro.
**Cosa puoi fare oggi:**
* Definisci oggetti e campi personalizzati come codice (modello dati gestito)
* Crea funzioni logiche con trigger personalizzati
* Definire skill e agenti per l'IA
* Distribuisci la stessa app su più spazi di lavoro
## Prerequisiti
* Node.js 24+ e Yarn 4
* Docker (per il server di sviluppo locale di Twenty)
## Per iniziare
Crea una nuova app utilizzando lo scaffolder ufficiale. Può avviare automaticamente un'istanza locale di Twenty per te:
```bash filename="Terminal"
# Crea lo scaffold di una nuova app — la CLI offrirà di avviare un server locale di Twenty
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Avvia la modalità di sviluppo: sincronizza automaticamente le modifiche locali con il tuo workspace
yarn twenty dev
```
### Gestione del server locale
L'SDK include comandi per gestire un server di sviluppo locale di Twenty (immagine Docker all-in-one con PostgreSQL, Redis, server e worker sulla porta 2020). Questi comandi si applicano solo al server di sviluppo basato su Docker — non gestiscono un'istanza di Twenty avviata dal sorgente (ad es. `npx nx start twenty-server` sulla porta 3000):
```bash filename="Terminal"
# Avvia il server locale (scarica l'immagine se necessario)
yarn twenty server start
# Verifica lo stato del server
yarn twenty server status
# Segui i log del server
yarn twenty server logs
# Arresta il server
yarn twenty server stop
# Reimposta tutti i dati e riparti da zero
yarn twenty server reset
```
Il server locale è preconfigurato con uno spazio di lavoro e un utente (`tim@apple.dev` / `tim@apple.dev`), così puoi iniziare a sviluppare immediatamente senza alcuna configurazione manuale.
### Autenticazione
Collega la tua app al server locale tramite OAuth:
```bash filename="Terminal"
# Autenticati tramite OAuth (apre il browser)
yarn twenty remote add --local
```
Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi:
```bash filename="Terminal"
# Predefinita (esaustiva): tutti gli esempi (oggetto, campo, funzione logica, componente front-end, vista, voce del menu di navigazione, skill, agente)
npx create-twenty-app@latest my-app
# Minimale: solo i file principali (application-config.ts e default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
### Come utilizzare un'istanza locale di Twenty
Se stai già eseguendo un'istanza locale di Twenty, puoi connetterti ad essa invece di usare Docker. Indica la porta su cui il tuo server locale è in ascolto (predefinita: `3000`):
```bash filename="Terminal"
# Durante lo scaffolding
npx create-twenty-app@latest my-app --port 3000
# Oppure dopo lo scaffolding
yarn twenty remote add --local --port 3000
```
Da qui puoi:
```bash filename="Terminal"
# Aggiungi una nuova entità alla tua applicazione (guidata)
yarn twenty entity:add
# Monitora i log delle funzioni della tua applicazione
yarn twenty function:logs
# Esegui una funzione per nome
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Esegui la funzione di pre-installazione
yarn twenty function:execute --preInstall
# Esegui la funzione post-installazione
yarn twenty function:execute --postInstall
# Compila l'app per la distribuzione
yarn twenty build
# Pubblica l'app su npm o su un server Twenty
yarn twenty publish
# Disinstalla l'applicazione dallo spazio di lavoro corrente
yarn twenty uninstall
# Mostra l'aiuto dei comandi
yarn twenty help
```
Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
## Struttura del progetto (generata dallo scaffolder)
Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
* Copia un'applicazione base minimale in `my-twenty-app/`
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
* Crea file di configurazione e script collegati alla CLI `twenty`
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
```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/ # Cartella delle risorse pubbliche (immagini, font, ecc.)
src/
├── application-config.ts # Obbligatorio - configurazione principale dell'applicazione
├── roles/
│ └── default-role.ts # Ruolo predefinito per le funzioni logiche
├── objects/
│ └── example-object.ts # Definizione di oggetto personalizzato di esempio
├── fields/
│ └── example-field.ts # Definizione di campo autonomo di esempio
├── logic-functions/
│ ├── hello-world.ts # Funzione logica di esempio
│ ├── pre-install.ts # Funzione logica di pre-installazione
│ └── post-install.ts # Funzione logica di post-installazione
├── front-components/
│ └── hello-world.tsx # Componente front-end di esempio
├── views/
│ └── example-view.ts # Definizione di vista salvata di esempio
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Link di navigazione della barra laterale di esempio
├── skills/
│ └── example-skill.ts # Definizione di skill per agente IA di esempio
└── agents/
└── example-agent.ts # Definizione di agente IA di esempio
```
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
A livello generale:
* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili.
* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `generated/` (client tipizzato), `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto.
* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto.
* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
* **README.md**: Un breve README nella radice dell'app con istruzioni di base.
* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione.
* **src/**: Il luogo principale in cui definisci la tua applicazione come codice
### Rilevamento delle entità
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
| Funzione helper | Tipo di entità |
| ---------------------------------- | ------------------------------------------------------------------------------ |
| `defineObject()` | Definizioni di oggetti personalizzati |
| `defineLogicFunction()` | Definizioni di funzioni logiche |
| `definePreInstallLogicFunction()` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) |
| `definePostInstallLogicFunction()` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) |
| `defineFrontComponent()` | Definizioni dei componenti front-end |
| `defineRole()` | Definizioni di ruoli |
| `defineField()` | Estensioni di campo per oggetti esistenti |
| `defineView()` | Definizioni di viste salvate |
| `defineNavigationMenuItem()` | Definizioni delle voci del menu di navigazione |
| `defineSkill()` | AI agent skill definitions |
| `defineAgent()` | Definizioni di agenti IA |
<Note>
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
</Note>
Esempio di entità rilevata:
```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
});
```
Comandi successivi aggiungeranno altri file e cartelle:
* `yarn twenty dev` genererà automaticamente due client API tipizzati in `node_modules/twenty-sdk/clients`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`).
* `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
## Autenticazione
La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti:
* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente)
* Chiave API
Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro.
### Gestione delle aree di lavoro
```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
```
Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace <name>`.
## Usa le risorse dell'SDK (tipi e configurazione)
Il pacchetto twenty-sdk fornisce blocchi tipizzati e funzioni helper da usare nella tua app. Di seguito gli elementi principali con cui interagirai più spesso.
### Funzioni helper
L'SDK fornisce funzioni helper per definire le entità della tua app. Come descritto in [Rilevamento delle entità](#entity-detection), devi usare `export default define<Entity>({...})` affinché le tue entità vengano rilevate:
| Funzione | Scopo |
| ---------------------------------- | -------------------------------------------------------------------------- |
| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) |
| `defineObject()` | Definisci oggetti personalizzati con campi |
| `defineLogicFunction()` | Definisci funzioni logiche con handler |
| `definePreInstallLogicFunction()` | Definisce una funzione logica di pre-installazione (una per applicazione) |
| `definePostInstallLogicFunction()` | Definisce una funzione logica di post-installazione (una per applicazione) |
| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata |
| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti |
| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi |
| `defineView()` | Definisce viste salvate per gli oggetti |
| `defineNavigationMenuItem()` | Definisce i link di navigazione della barra laterale |
| `defineSkill()` | Define AI agent skills |
| `defineAgent()` | Definisci agenti IA con prompt di sistema |
Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi.
### Definizione degli oggetti
Gli oggetti personalizzati descrivono sia lo schema sia il comportamento per i record nel tuo spazio di lavoro. Usa `defineObject()` per definire oggetti con convalida integrata:
```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,
},
],
});
```
Punti chiave:
* Usa `defineObject()` per una convalida integrata e un migliore supporto IDE.
* Il `universalIdentifier` deve essere univoco e stabile tra i deployment.
* Ogni campo richiede un `name`, `type`, `label` e il proprio `universalIdentifier` stabile.
* L'array `fields` è facoltativo: puoi definire oggetti senza campi personalizzati.
* Puoi generare nuovi oggetti con `yarn twenty entity:add`, che ti guida nella denominazione, nei campi e nelle relazioni.
<Note>
**I campi base vengono creati automaticamente.** Quando definisci un oggetto personalizzato, Twenty aggiunge automaticamente i campi standard
come `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`.
Non è necessario definirli nel tuo array `fields` — aggiungi solo i tuoi campi personalizzati.
Puoi sovrascrivere i campi predefiniti definendo un campo con lo stesso nome nel tuo array `fields`,
ma non è consigliato.
</Note>
### Definire campi sugli oggetti esistenti
Usa `defineField()` per aggiungere campi personalizzati agli oggetti esistenti — sia agli oggetti standard (come `company`, `person`, `opportunity`) sia agli oggetti personalizzati definiti da altre app. Ogni campo risiede nel proprio file e fa riferimento all'oggetto di destinazione tramite il suo `universalIdentifier`.
Per fare riferimento agli oggetti standard, importa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` da `twenty-sdk`. Questa costante fornisce identificatori stabili per tutti gli oggetti integrati e per i relativi campi:
```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',
});
```
Punti chiave:
* `objectUniversalIdentifier` indica a Twenty a quale oggetto associare il campo. Usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<objectName>.universalIdentifier` per gli oggetti standard.
* Ogni campo richiede un proprio `universalIdentifier` stabile, un `name`, `type`, `label` e l'`objectUniversalIdentifier` di destinazione.
* Puoi generare nuovi campi usando `yarn twenty entity:add` e scegliendo l'opzione campo.
* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` è anche esportato come `STANDARD_OBJECT` per comodità — entrambi si riferiscono alla stessa costante.
Gli oggetti standard disponibili includono: `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` e `workspaceMember`.
Ogni oggetto standard espone anche gli identificatori dei propri campi. Ad esempio, per fare riferimento a un campo specifico su un oggetto standard nelle autorizzazioni dei ruoli:
```typescript
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier
```
#### Campi di relazione su oggetti esistenti
Puoi anche definire campi di relazione che collegano oggetti esistenti ai tuoi oggetti personalizzati:
```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,
});
```
### Configurazione dell'applicazione (application-config.ts)
Ogni app ha un singolo file `application-config.ts` che descrive:
* **Identità dell'app**: identificatori, nome visualizzato e descrizione.
* **Come vengono eseguite le sue funzioni**: quale ruolo usano per i permessi.
* **Variabili (opzionali)**: coppie chiavevalore esposte alle funzioni come variabili d'ambiente.
* **(Opzionale) funzione di pre-installazione**: una funzione logica che viene eseguita prima che l'app venga installata.
* **(Opzionale) funzione post-installazione**: una funzione logica che viene eseguita dopo l'installazione dell'app.
Usa `defineApplication()` per definire la configurazione della tua applicazione:
```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,
});
```
Note:
* I campi `universalIdentifier` sono ID deterministici sotto il tuo controllo; generali una volta e mantienili stabili tra le sincronizzazioni.
* `applicationVariables` diventano variabili d'ambiente per le tue funzioni (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` deve corrispondere al file del ruolo (vedi sotto).
* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante la build del manifesto. Vedi [Funzioni di pre-installazione](#pre-install-functions) e [Funzioni di post-installazione](#post-install-functions).
#### Ruoli e permessi
Le applicazioni possono definire ruoli che incapsulano i permessi sugli oggetti e sulle azioni del tuo spazio di lavoro. Il campo `defaultRoleUniversalIdentifier` in `application-config.ts` indica il ruolo predefinito utilizzato dalle funzioni logiche della tua app.
* La chiave API di runtime iniettata come `TWENTY_API_KEY` è derivata da questo ruolo funzione predefinito.
* Il client tipizzato sarà limitato ai permessi concessi a quel ruolo.
* Segui il principio del privilegio minimo: crea un ruolo dedicato con solo i permessi necessari alle tue funzioni, quindi fai riferimento al suo identificatore universale.
##### Ruolo funzione predefinito (\*.role.ts)
Quando generi una nuova app con lo scaffolder, la CLI crea anche un file di ruolo predefinito. Usa `defineRole()` per definire ruoli con convalida integrata:
```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],
});
```
L'`universalIdentifier` di questo ruolo viene quindi referenziato in `application-config.ts` come `defaultRoleUniversalIdentifier`. In altre parole:
* **\*.role.ts** definisce ciò che il ruolo funzione predefinito può fare.
* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi.
Note:
* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo.
* Sostituisci `objectPermissions` e `fieldPermissions` con gli oggetti/campi di cui le tue funzioni hanno bisogno.
* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Mantienili al minimo; aggiungi solo ciò che ti serve.
* Vedi un esempio funzionante nell'app 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).
### Configurazione e punto di ingresso della funzione logica
Ogni file di funzione usa `defineLogicFunction()` per esportare una configurazione con un handler e trigger opzionali.
```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: [
// Trigger di route HTTP pubblica '/s/post-card/create'
{
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/post-card/create',
httpMethod: 'GET',
isAuthRequired: false,
},
// Trigger Cron (pattern CRON)
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Trigger di evento del database
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
Tipi di trigger comuni:
* **route**: Espone la funzione su un percorso e metodo HTTP **sotto l'endpoint `/s/`**:
> es. `path: '/post-card/create',` -> chiamata su `<APP_URL>/s/post-card/create`
* **cron**: Esegue la tua funzione secondo una pianificazione utilizzando un'espressione CRON.
* **databaseEvent**: Viene eseguito sugli eventi del ciclo di vita degli oggetti dello spazio di lavoro. Quando l'operazione dell'evento è `updated`, è possibile specificare campi specifici da monitorare nell'array `updatedFields`. Se lasciato non definito o vuoto, qualsiasi aggiornamento attiverà la funzione.
> es. `person.updated`
Note:
* L'array `triggers` è facoltativo. Le funzioni senza trigger possono essere utilizzate come funzioni di utilità richiamate da altre funzioni.
* Puoi combinare più tipi di trigger in un'unica funzione.
### Funzioni di pre-installazione
Una funzione di pre-installazione è una funzione logica che viene eseguita automaticamente prima che la tua app venga installata in uno spazio di lavoro. È utile per attività di convalida, controlli dei prerequisiti o per preparare lo stato dello spazio di lavoro prima che proceda l'installazione principale.
Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata una funzione di pre-installazione in `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,
});
```
Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI:
```bash filename="Terminal"
yarn twenty function:execute --preInstall
```
Punti chiave:
* Le funzioni di pre-installazione utilizzano `definePreInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni).
* È consentita una sola funzione di pre-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una.
* L'`universalIdentifier` della funzione viene impostato automaticamente come `preInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`.
* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di preparazione più lunghe.
* Le funzioni di pre-installazione non necessitano di trigger — vengono invocate dalla piattaforma prima dell'installazione o manualmente tramite `function:execute --preInstall`.
### Funzioni post-installazione
Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro.
Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata automaticamente una funzione di post-installazione in `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,
});
```
Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Punti chiave:
* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni).
* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una.
* L'`universalIdentifier` della funzione viene impostato automaticamente come `postInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`.
* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati.
* Le funzioni di post-installazione non necessitano di trigger — vengono invocate dalla piattaforma durante l'installazione o manualmente tramite `function:execute --postInstall`.
### Payload del trigger di route
<Warning>
**Modifica non retrocompatibile (v1.16, gennaio 2026):** Il formato del payload del trigger di route è cambiato. Prima della v1.16, i parametri di query, i parametri di percorso e il corpo venivano inviati direttamente come payload. A partire dalla v1.16, sono annidati all'interno di un oggetto `RoutePayload` strutturato.
**Prima della v1.16:**
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
**Dopo la v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**Per migrare le funzioni esistenti:** Aggiorna l'handler per estrarre i dati da `event.body`, `event.queryStringParameters` o `event.pathParameters` invece che direttamente dall'oggetto `params`.
</Warning>
Quando un trigger di route invoca la tua funzione logica, questa riceve un oggetto `RoutePayload` che segue il formato AWS HTTP API v2. Importa il tipo da `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' };
};
```
Il tipo `RoutePayload` ha la seguente struttura:
| Proprietà | Tipo | Descrizione |
| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `headers` | `Record<string, string \| undefined>` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) |
| `queryStringParameters` | `Record<string, string \| undefined>` | Parametri della query string (valori multipli uniti da virgole) |
| `pathParameters` | `Record<string, string \| undefined>` | Parametri di percorso estratti dal pattern della route (ad es., `/users/:id` → `{ id: '123' }`) |
| `corpo` | `object \| null` | Corpo della richiesta analizzato (JSON) |
| `isBase64Encoded` | `booleano` | Indica se il corpo è codificato in base64 |
| `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) |
| `requestContext.http.path` | `string` | Percorso della richiesta non elaborato |
### Inoltro delle intestazioni HTTP
Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. Per accedere a intestazioni specifiche, elencale esplicitamente nell'array `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'],
},
],
});
```
Nel tuo handler, puoi quindi accedere a queste intestazioni:
```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>
I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad esempio, `event.headers['content-type']`).
</Note>
Puoi creare nuove funzioni in due modi:
* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere una nuova funzione logica. Questo genera un file iniziale con un handler e una configurazione.
* **Manuale**: Crea un nuovo file `*.logic-function.ts` e usa `defineLogicFunction()`, seguendo lo stesso schema.
### Contrassegnare una funzione logica come strumento
Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. Quando una funzione è contrassegnata come strumento, diventa individuabile dalle funzionalità di IA di Twenty e può essere selezionata come passaggio nelle automazioni dei flussi di lavoro.
Per contrassegnare una funzione logica come strumento, imposta `isTool: true` e fornisci un `toolInputSchema` che descriva i parametri di input attesi utilizzando [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: `Arricchisci i dati per ${params.companyName}`,
body: `Dominio: ${params.domain ?? 'sconosciuto'}`,
},
},
id: true,
},
});
return { taskId: result.createTask.id };
};
export default defineLogicFunction({
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
name: 'enrich-company',
description: 'Arricchisci un record aziendale con dati esterni',
timeoutSeconds: 10,
handler,
isTool: true,
toolInputSchema: {
type: 'object',
properties: {
companyName: {
type: 'string',
description: 'Il nome dell\'azienda da arricchire',
},
domain: {
type: 'string',
description: 'Il dominio del sito web dell\'azienda (facoltativo)',
},
},
required: ['companyName'],
},
});
```
Punti chiave:
* **`isTool`** (`boolean`, predefinito: `false`): Quando impostato su `true`, la funzione viene registrata come strumento e diventa disponibile per gli agenti IA e le automazioni dei flussi di lavoro.
* **`toolInputSchema`** (`object`, opzionale): Un oggetto JSON Schema che descrive i parametri accettati dalla funzione. Gli agenti IA utilizzano questo schema per capire quali input si aspetta lo strumento e per convalidare le chiamate. Se omesso, lo schema assume il valore predefinito `{ type: 'object', properties: {} }` (nessun parametro).
* Le funzioni con `isTool: false` (o non impostato) **non** vengono esposte come strumenti. Possono comunque essere eseguite direttamente o chiamate da altre funzioni, ma non compariranno nell'individuazione degli strumenti.
* **Denominazione dello strumento**: Quando esposta come strumento, il nome della funzione viene normalizzato automaticamente in `logic_function_<name>` (in minuscolo, i caratteri non alfanumerici vengono sostituiti da trattini bassi). Ad esempio, `enrich-company` diventa `logic_function_enrich_company`.
* È possibile combinare `isTool` con i trigger — una funzione può essere sia uno strumento (invocabile dagli agenti IA) sia attivata da eventi (cron, eventi del database, routes) contemporaneamente.
<Note>
**Scrivi una buona `description`.** Gli agenti IA fanno affidamento sul campo `description` della funzione per decidere quando usare lo strumento. Sii specifico su cosa fa lo strumento e quando dovrebbe essere invocato.
</Note>
### Componenti front-end
I componenti front-end ti consentono di creare componenti React personalizzati che vengono renderizzati all'interno dell'interfaccia di Twenty. Usa `defineFrontComponent()` per definire componenti con convalida integrata:
```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,
});
```
Punti chiave:
* I componenti front-end sono componenti React che eseguono il rendering in contesti isolati all'interno di Twenty.
* Il campo `component` fa riferimento al tuo componente React.
* I componenti vengono compilati e sincronizzati automaticamente durante `yarn twenty dev`.
Puoi creare nuovi componenti front-end in due modi:
* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo componente front-end.
* **Manuale**: Crea un nuovo file `.tsx` e usa `defineFrontComponent()`, seguendo lo stesso schema.
#### Dove possono essere utilizzati i componenti front.
I componenti front possono essere renderizzati in due posizioni all'interno di Twenty:
* **Pannello laterale** — I componenti front non headless si aprono nel pannello laterale destro. Questo è il comportamento predefinito quando un componente front viene avviato dal menu comandi.
* **Widget (dashboard e pagine dei record)** — I componenti front possono essere incorporati come widget all'interno dei layout di pagina. Quando si configura una dashboard o il layout di una pagina record, gli utenti possono aggiungere un widget del componente front.
#### Headless vs non headless
I componenti front prevedono due modalità di rendering controllate dall'opzione `isHeadless`:
**Non headless (predefinito)** — Il componente renderizza un'interfaccia utente visibile. Quando viene avviato dal menu comandi, si apre nel pannello laterale. Questo è il comportamento predefinito quando `isHeadless` è `false` o omesso.
**Headless** — Il componente viene montato in modo invisibile in background. Non apre il pannello laterale. I componenti headless sono pensati per azioni che eseguono una logica e poi si smontano — ad esempio, eseguire un'attività asincrona, navigare a una pagina o mostrare una finestra modale di conferma. Si abbinano naturalmente ai componenti Command dell'SDK descritti di seguito.
```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',
},
});
```
#### Aggiungere voci al menu comandi
Per far comparire un componente front come voce nel menu comandi di Twenty, aggiungi la proprietà `command` a `defineFrontComponent()`. Quando gli utenti aprono il menu comandi (Cmd+K / Ctrl+K), la voce viene mostrata e al clic attiva il componente front.
L'oggetto `command` accetta i seguenti campi:
| Campo | Tipo | Descrizione |
| --------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `universalIdentifier` | `string` (obbligatorio) | ID univoco per la voce del menu comandi |
| `etichetta` | `string` (obbligatorio) | Etichetta visualizzata nel menu comandi |
| `icona` | `string` (facoltativo) | Nome dell'icona (ad es., `'IconSparkles'`) |
| `isPinned` | `boolean` (facoltativo) | Indica se il comando è fissato in alto nel menu |
| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (facoltativo) | `GLOBAL` mostra il comando ovunque; `RECORD_SELECTION` lo mostra solo nei contesti dei record |
| `availabilityObjectUniversalIdentifier` | `string` (facoltativo) | Limita il comando a uno specifico tipo di oggetto (ad es., Person) |
Ecco un esempio dall'app di registrazione delle chiamate che aggiunge un comando limitato ai record Person:
```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',
},
});
```
Quando il comando viene sincronizzato, appare nel menu comandi. Se il componente front è non headless, si apre il pannello laterale con il componente renderizzato al suo interno. Se è headless, il componente viene montato in background ed esegue la propria logica.
#### Componenti Command dell'SDK
Il pacchetto `twenty-sdk` fornisce quattro componenti di supporto Command progettati per i componenti front headless. Ogni componente esegue un'azione al montaggio, gestisce gli errori mostrando una notifica snackbar e smonta automaticamente il componente front al termine.
Importali da `twenty-sdk/command`:
* **`Command`** — Esegue una callback asincrona tramite la prop `execute`.
* **`CommandLink`** — Naviga verso un percorso dell'app. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Apre una finestra modale di conferma. Se l'utente conferma, esegue la callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Apre una specifica pagina del pannello laterale. Props: `page`, `pageTitle`, `pageIcon`.
Ecco un esempio completo di componente front headless che usa `Command` per eseguire un'azione dal menu comandi:
```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',
},
});
```
E un esempio che usa `CommandModal` per chiedere conferma prima di eseguire:
```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',
},
});
```
#### Contesto di esecuzione
Ogni componente front riceve un contesto di esecuzione che fornisce informazioni su dove e come sta funzionando. Accedi ai valori del contesto usando gli hook di `twenty-sdk`:
| Hook | Tipo restituito | Descrizione |
| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `useFrontComponentId()` | `string` | L'ID univoco dell'istanza corrente del componente front |
| `useRecordId()` | `string \| null` | L'ID del record corrente, quando il componente viene eseguito in un contesto di record (ad es., un widget di pagina record o un comando con ambito a un record). In caso contrario, restituisce `null`. |
| `useUserId()` | `string \| null` | L'ID dell'utente corrente |
```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>
);
};
```
Il contesto è reattivo — se il record circostante cambia, gli hook restituiscono automaticamente i valori aggiornati.
#### Funzioni dell'API host
I componenti front vengono eseguiti in una sandbox isolata ma possono interagire con l'interfaccia di Twenty tramite un insieme di funzioni fornite dall'host. Importale direttamente da `twenty-sdk`:
```typescript
import {
navigate,
closeSidePanel,
enqueueSnackbar,
unmountFrontComponent,
openSidePanelPage,
openCommandConfirmationModal,
} from 'twenty-sdk';
```
| Funzione | Firma | Descrizione |
| ------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `naviga` | `(to, params?, queryParams?, options?) => Promise<void>` | Naviga verso un percorso tipizzato dell'app all'interno di Twenty |
| `closeSidePanel` | `() => Promise<void>` | Chiudi il pannello laterale |
| `enqueueSnackbar` | `(params) => Promise<void>` | Mostra una notifica snackbar. Parametri: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), `duration` opzionale, `detailedMessage`, `dedupeKey` |
| `unmountFrontComponent` | `() => Promise<void>` | Smonta il componente front corrente (usato dai componenti headless per pulire dopo l'esecuzione) |
| `openSidePanelPage` | `(params) => Promise<void>` | Apri una pagina nel pannello laterale. Parametri: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` |
| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Mostra una finestra modale di conferma e attende la risposta dell'utente. Parametri: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) |
Ecco un esempio che usa l'API host per mostrare una snackbar e chiudere il pannello laterale dopo il completamento di un'azione:
```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à
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
```typescript
// src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk';
export default defineSkill({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'sales-outreach',
label: 'Sales Outreach',
description: 'Guides the AI agent through a structured sales outreach process',
icon: 'IconBrain',
content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
```
Punti chiave:
* `name` is a unique identifier string for the skill (kebab-case recommended).
* `label` is the human-readable display name shown in the UI.
* `content` contains the skill instructions — this is the text the AI agent uses.
* `icon` (optional) sets the icon displayed in the UI.
* `description` (optional) provides additional context about the skill's purpose.
You can create new skills in two ways:
* **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill.
* **Manual**: Create a new file and use `defineSkill()`, following the same pattern.
### Agenti
Gli Agenti definiscono agenti IA con prompt di sistema che possono operare all'interno del tuo spazio di lavoro. Usa `defineAgent()` per definire agenti con convalida integrata:
```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`,
});
```
Punti chiave:
* `name` è una stringa identificativa univoca per l'agente (kebab-case consigliato).
* `label` è il nome di visualizzazione leggibile mostrato nell'UI.
* `prompt` contiene il prompt di sistema — è il testo di istruzioni che definisce il comportamento dell'agente.
* `icon` (opzionale) imposta l'icona visualizzata nell'UI.
* `description` (opzionale) fornisce contesto aggiuntivo sullo scopo dell'agente.
Puoi creare nuovi agenti in due modi:
* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo agente.
* **Manuale**: Crea un nuovo file e usa `defineAgent()`, seguendo lo stesso schema.
### Client tipizzati generati
Due client tipizzati sono generati automaticamente da `yarn twenty dev` e salvati in `node_modules/twenty-sdk/clients` in base allo schema della tua area di lavoro:
* **`CoreApiClient`** — interroga l'endpoint `/graphql` per i dati dell'area di lavoro
* **`MetadataApiClient`** — interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro e il caricamento dei file
```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` viene rigenerato automaticamente da `yarn twenty dev` ogni volta che i tuoi oggetti o campi cambiano. `MetadataApiClient` è fornito pronto all'uso con l'SDK.
#### Credenziali di runtime nelle funzioni logiche
Quando la tua funzione viene eseguita su Twenty, la piattaforma inietta le credenziali come variabili d'ambiente prima dell'esecuzione del tuo codice:
* `TWENTY_API_URL`: URL di base dell'API Twenty a cui punta la tua app.
* `TWENTY_API_KEY`: Chiave a breve durata con ambito al ruolo funzione predefinito della tua applicazione.
Note:
* Non è necessario passare URL o chiave API al client generato. Legge `TWENTY_API_URL` e `TWENTY_API_KEY` da process.env in fase di esecuzione.
* I permessi della chiave API sono determinati dal ruolo referenziato nel tuo `application-config.ts` tramite `defaultRoleUniversalIdentifier`. Questo è il ruolo predefinito utilizzato dalle funzioni logiche della tua applicazione.
* Le applicazioni possono definire ruoli per seguire il principio del privilegio minimo. Concedi solo i permessi necessari alle tue funzioni, quindi punta `defaultRoleUniversalIdentifier` all'identificatore universale di quel ruolo.
#### Caricamento dei file
Il `MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file sugli oggetti del tuo spazio di lavoro. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte.
```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, // contenuto del file come Buffer
'invoice.pdf', // nome del file
'application/pdf', // Tipo MIME (predefinito: 'application/octet-stream')
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // identificatore universale del campo
);
console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
```
La firma del metodo:
```typescript
uploadFile(
fileBuffer: Buffer,
filename: string,
contentType: string,
fieldMetadataUniversalIdentifier: string,
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
```
| Parametro | Tipo | Descrizione |
| ---------------------------------- | -------- | ------------------------------------------------------------------------ |
| `fileBuffer` | `Buffer` | Il contenuto grezzo del file |
| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) |
| `contentType` | `string` | Tipo MIME del file (predefinito su `application/octet-stream` se omesso) |
| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto |
Punti chiave:
* Il metodo `uploadFile` è disponibile su `MetadataApiClient` perché la mutazione di upload viene risolta dall'endpoint `/metadata`.
* Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata — coerentemente con il modo in cui le app fanno riferimento ai campi altrove.
* L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato.
### Esempio Hello World
Esplora un esempio minimale end-to-end che dimostra oggetti, funzioni logiche, componenti front-end e trigger multipli [qui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Compilazione della tua app
Una volta che hai sviluppato la tua app con `app:dev`, usa `app:build` per compilarla in un pacchetto distribuibile.
```bash filename="Terminal"
# Compila l'app (l'output va in .twenty/output/)
yarn twenty build
# Compila e crea un tarball (.tgz) per la distribuzione
yarn twenty build --tarball
```
Il processo di compilazione:
1. **Analizza e convalida il manifest** — legge tutte le entità `defineX()` dai tuoi file sorgente e convalida la struttura del manifest.
2. **Compila le funzioni di logica e i componenti front-end** — raggruppa i sorgenti TypeScript in file ESM `.mjs` usando esbuild.
3. **Genera i checksum** — calcola gli hash MD5 per ogni file compilato, memorizzati nel manifest come `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Genera il client API tipizzato** — esegue l'analisi dello schema GraphQL e genera i client tipizzati `CoreApiClient` e `MetadataApiClient`.
5. **Esegue un controllo dei tipi di TypeScript** — esegue `tsc --noEmit` per intercettare gli errori di tipo prima della pubblicazione.
6. **Ricompila con il client generato** — esegue una seconda passata di compilazione in modo da includere i tipi del client generato.
7. **Crea facoltativamente un tarball** — se viene passato `--tarball`, esegue `npm pack` per creare un file `.tgz` pronto per la distribuzione.
L'output della build in `.twenty/output/` contiene:
```text
.twenty/output/
├── manifest.json # Manifest con checksum per tutti i file compilati
├── package.json # Copiato dalla radice dell'app
├── yarn.lock # Copiato dalla radice dell'app
├── src/
│ ├── logic-functions/ # File .mjs compilati delle funzioni logiche
│ └── front-components/ # File .mjs compilati dei componenti front-end
├── public/ # Asset statici (se presenti)
└── my-app-1.0.0.tgz # Solo con il flag --tarball
```
| Opzione | Descrizione |
| ----------- | ------------------------------------------------------------------- |
| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) |
| `--tarball` | Imballa anche l'output in un tarball `.tgz` |
## Pubblicazione della tua app
Usa `app:publish` per distribuire la tua app — al registro npm oppure direttamente a un server Twenty.
### Pubblica su npm (predefinito)
```bash filename="Terminal"
# Pubblica su npm (richiede l'accesso a npm)
yarn twenty publish
# Pubblica con un dist-tag (ad es. beta, next)
yarn twenty publish --tag beta
```
Questo compila l'app ed esegue `npm publish` dalla directory `.twenty/output/`. Il pacchetto pubblicato può quindi essere installato dal marketplace di Twenty da qualsiasi area di lavoro.
### Pubblica su un server Twenty
```bash filename="Terminal"
# Pubblica direttamente su un server Twenty
yarn twenty publish --server https://app.twenty.com
```
Questo compila l'app con un tarball, lo carica sul server tramite la mutation GraphQL `uploadAppTarball` e avvia l'installazione in un unico passaggio. Questo è utile per distribuzioni private o per effettuare test su un server specifico.
| Opzione | Descrizione |
| ----------------- | -------------------------------------------------------------------------- |
| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) |
| `--server <url>` | Pubblica su un server Twenty invece di npm |
| `--token <token>` | Token di autenticazione per il server di destinazione |
| `--tag <tag>` | dist-tag di npm (ad es. `beta`, `next`) — solo per la pubblicazione su npm |
## Registrazione dell'applicazione
Prima che un'app possa essere installata in un'area di lavoro, deve essere **registrata**. Una registrazione è un record di metadati che descrive l'origine dell'app e come autenticarla. Nella maggior parte dei casi questo è gestito automaticamente dalla CLI.
### Tipi di origine
Ogni registrazione ha un **tipo di origine** che determina come vengono risolti i file dell'app durante l'installazione:
| Tipo di origine | Come vengono risolti i file | Caso d'uso tipico |
| --------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------- |
| `LOCAL` | I file sono sincronizzati in tempo reale dal watcher della CLI — l'installazione viene saltata | Sviluppo con `app:dev` |
| `NPM` | Recuperati dal registro npm tramite il campo `sourcePackage` | App pubblicate su npm |
| `TARBALL` | Estratti da un file `.tgz` caricato e archiviato sul server | App private pubblicate con `--server` |
### Come avviene la registrazione
* **`app:dev`** — crea automaticamente una registrazione `LOCAL` la prima volta che esegui la modalità di sviluppo su un'area di lavoro.
* **`app:publish --server`** — carica un tarball e crea (o aggiorna) una registrazione `TARBALL`, quindi installa l'app.
* **Marketplace npm** — le registrazioni `NPM` vengono create quando le app vengono sincronizzate dal registro npm nel catalogo del marketplace di Twenty.
* **GraphQL API** — puoi anche creare registrazioni in modo programmatico tramite la mutation `createApplicationRegistration`.
### Registrazione vs installazione
**Registrazione** e **installazione** sono concetti distinti:
* Una **registrazione** (`ApplicationRegistration`) è un record di metadati globale che descrive l'app: il suo nome, il tipo di origine, le credenziali OAuth e lo stato di pubblicazione nel marketplace. Esiste indipendentemente da qualsiasi area di lavoro.
* Un'**installazione** (`Application`) è un'istanza per area di lavoro. Quando un utente installa un'app, Twenty risolve il pacchetto dalla sorgente della registrazione, scrive i file compilati nell'archiviazione e sincronizza il manifest (creando oggetti, campi, funzioni logiche, ecc.) in quell'area di lavoro.
Una registrazione può essere installata in molte aree di lavoro. Ogni area di lavoro ottiene la propria copia dei file dell'app e del modello di dati.
### Credenziali OAuth
Ogni registrazione include credenziali OAuth (`oAuthClientId` e `oAuthClientSecret`) generate al momento della creazione. Queste vengono utilizzate dall'app per autenticare le richieste API per conto degli utenti. Il client secret viene restituito **una sola volta** alla creazione — conservalo in modo sicuro. Puoi ruotarlo in seguito tramite la mutation `rotateApplicationRegistrationClientSecret`.
## Configurazione manuale (senza lo scaffolder)
Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
Quindi aggiungi uno script `twenty`:
```json filename="package.json"
{
"scripts": {
"twenty": "twenty"
}
}
```
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty dev`, `yarn twenty help`, ecc.
## Risoluzione dei problemi
* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti.
* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile.
* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato.
* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente.
Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322