Vai al contenuto principale
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.

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 le abilità per gli agenti IA
  • Distribuisci la stessa app su più spazi di lavoro

Prerequisiti

Per iniziare

Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare:
# Crea lo scaffold di una nuova app (include tutti gli esempi per impostazione predefinita)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app

# Se non usi yarn@4
corepack enable
yarn install

# Autenticati usando la tua API key (ti verrà richiesto)
yarn twenty auth:login

# Avvia la modalità di sviluppo: sincronizza automaticamente le modifiche locali con il tuo workspace
yarn twenty app:dev
Lo strumento di scaffolding supporta tre modalità per controllare quali file di esempio vengono inclusi:
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app

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

# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
Da qui puoi:
# 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 post-installazione
yarn twenty function:execute --postInstall

# Disinstalla l'applicazione dallo spazio di lavoro corrente
yarn twenty app:uninstall

# Mostra l'aiuto dei comandi
yarn twenty help
Vedi anche: le pagine di riferimento della CLI per create-twenty-app e twenty-sdk CLI.

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, funzione di 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ì:
my-twenty-app/
  package.json
  yarn.lock
  .gitignore
  .nvmrc
  .yarnrc.yml
  .yarn/
    install-state.gz
  eslint.config.mjs
  tsconfig.json
  README.md
  public/                           # Public assets folder (images, fonts, etc.)
  src/
  ├── application-config.ts           # Required - main application configuration
  ├── roles/
  │   └── default-role.ts               # Default role for logic functions
  ├── objects/
  │   └── example-object.ts             # Example custom object definition
  ├── fields/
  │   └── example-field.ts              # Example standalone field definition
  ├── logic-functions/
  │   ├── hello-world.ts                # Example logic function
  │   └── post-install.ts               # Post-install logic function
  ├── front-components/
  │   └── hello-world.tsx               # Example front component
  ├── views/
  │   └── example-view.ts                # Example saved view definition
  ├── navigation-menu-items/
  │   └── example-navigation-menu-item.ts # Example sidebar navigation link
  └── skills/
      └── example-skill.ts                # Example AI agent skill definition
Con --minimal, vengono creati solo i file principali (application-config.ts, roles/default-role.ts e logic-functions/post-install.ts). Con --interactive, scegli quali file di esempio includere. 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.
  • eslint.config.mjs e tsconfig.json: Forniscono linting e configurazione TypeScript per i sorgenti TypeScript della tua app.
  • 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 helperTipo di entità
defineObject()Definizioni di oggetti personalizzati
defineLogicFunction()Definizioni di funzioni logiche
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
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.
Esempio di entità rilevata:
// 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 app:dev genererà automaticamente un client API tipizzato in node_modules/twenty-sdk/generated (client Twenty tipizzato + tipi dell’area di lavoro).
  • 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

# 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à, devi usare export default define<Entity>({...}) affinché le tue entità vengano rilevate:
FunzioneScopo
defineApplication()Configura i metadati dell’applicazione (obbligatorio, uno per app)
defineObject()Definisci oggetti personalizzati con campi
defineLogicFunction()Definisci funzioni logiche con handler
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
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:
// 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.
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.

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 chiave–valore esposte alle funzioni come variabili d’ambiente.
  • (Opzionale) funzione post-installazione: una funzione logica che viene eseguita dopo l’installazione dell’app.
Usa defineApplication() per definire la configurazione della tua applicazione:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';

export default defineApplication({
  universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
  displayName: 'My Twenty App',
  description: 'My first Twenty app',
  icon: 'IconWorld',
  applicationVariables: {
    DEFAULT_RECIPIENT_NAME: {
      universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
      description: 'Default recipient name for postcards',
      value: 'Jane Doe',
      isSecret: false,
    },
  },
  defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
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).
  • postInstallLogicFunctionUniversalIdentifier (opzionale) fa riferimento a una funzione logica che viene eseguita automaticamente dopo l’installazione dell’app. Vedi Funzioni post-installazione.

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:
// 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.

Configurazione e punto di ingresso della funzione logica

Ogni file di funzione usa defineLogicFunction() per esportare una configurazione con un handler e trigger opzionali.
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';

const handler = async (params: RoutePayload) => {
  const client = new Twenty(); // generated typed client
  const name = 'name' in params.queryStringParameters
    ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
    : 'Hello world';

  const result = await client.mutation({
    createPostCard: {
      __args: { data: { name } },
      id: true,
      name: true,
    },
  });
  return result;
};

export default defineLogicFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  name: 'create-new-post-card',
  timeoutSeconds: 2,
  handler,
  triggers: [
    // Public HTTP route trigger '/s/post-card/create'
    {
      universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
      type: 'route',
      path: '/post-card/create',
      httpMethod: 'GET',
      isAuthRequired: false,
    },
    // Cron trigger (CRON pattern)
    // {
    //   universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
    //   type: 'cron',
    //   pattern: '0 0 1 1 *',
    // },
    // Database event trigger
    // {
    //   universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
    //   type: 'databaseEvent',
    //   eventName: 'person.updated',
    //   updatedFields: ['name'],
    // },
  ],
});
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 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:
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';

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

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

export default defineLogicFunction({
  universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
  name: 'post-install',
  description: 'Runs after installation to set up the application.',
  timeoutSeconds: 300,
  handler,
});
La funzione viene collegata alla tua app facendo riferimento al suo identificatore universale in application-config.ts:
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';

export default defineApplication({
  // ...
  postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI:
yarn twenty function:execute --postInstall
Punti chiave:
  • Le funzioni di post-installazione sono funzioni logiche standard — usano defineLogicFunction() come qualsiasi altra funzione.
  • Il campo postInstallLogicFunctionUniversalIdentifier in defineApplication() è facoltativo. Se omesso, nessuna funzione viene eseguita dopo l’installazione.
  • 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

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:
const handler = async (params) => {
  const { param1, param2 } = params; // Direct access
};
Dopo la v1.16:
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.
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:
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àTipoDescrizione
headersRecord<string, string | undefined>Intestazioni HTTP (solo quelle elencate in forwardedRequestHeaders)
queryStringParametersRecord<string, string | undefined>Parametri della query string (valori multipli uniti da virgole)
pathParametersRecord<string, string | undefined>Parametri di percorso estratti dal pattern della route (ad es., /users/:id{ id: '123' })
corpoobject | nullCorpo della richiesta analizzato (JSON)
isBase64EncodedbooleanoIndica se il corpo è codificato in base64
requestContext.http.methodstringMetodo HTTP (GET, POST, PUT, PATCH, DELETE)
requestContext.http.pathstringPercorso 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:
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:
const handler = async (event: RoutePayload) => {
  const signature = event.headers['x-webhook-signature'];
  const contentType = event.headers['content-type'];

  // Validate webhook signature...
  return { received: true };
};
I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad esempio, event.headers['content-type']).
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:
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import Twenty from '~/generated';

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

  const result = await client.mutation({
    createTask: {
      __args: {
        data: {
          title: `Enrich data for ${params.companyName}`,
          body: `Domain: ${params.domain ?? 'unknown'}`,
        },
      },
      id: true,
    },
  });

  return { taskId: result.createTask.id };
};

export default defineLogicFunction({
  universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
  name: 'enrich-company',
  description: 'Enrich a company record with external data',
  timeoutSeconds: 10,
  handler,
  isTool: true,
  toolInputSchema: {
    type: 'object',
    properties: {
      companyName: {
        type: 'string',
        description: 'The name of the company to enrich',
      },
      domain: {
        type: 'string',
        description: 'The company website domain (optional)',
      },
    },
    required: ['companyName'],
  },
});
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.
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.

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:
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';

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

export default defineFrontComponent({
  universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  name: 'my-widget',
  description: 'A custom widget component',
  component: MyWidget,
});
Punti chiave:
  • I componenti front-end sono componenti React che eseguono il rendering in contesti isolati all’interno di Twenty.
  • Usa il suffisso di file *.front-component.tsx per il rilevamento automatico.
  • Il campo component fa riferimento al tuo componente React.
  • I componenti vengono compilati e sincronizzati automaticamente durante yarn twenty app: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 *.front-component.tsx e usa defineFrontComponent().

Abilità

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

export default defineSkill({
  universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  name: 'sales-outreach',
  label: 'Sales Outreach',
  description: 'Guides the AI agent through a structured sales outreach process',
  icon: 'IconBrain',
  content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
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.

Client tipizzato generato

Il client tipizzato è generato automaticamente da yarn twenty app:dev e salvato in node_modules/twenty-sdk/generated in base allo schema della tua area di lavoro. Usalo nelle tue funzioni:
import Twenty from '~/generated';

const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
Il client viene rigenerato automaticamente da yarn twenty app:dev ogni volta che i tuoi oggetti o campi cambiano.

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.

Esempio Hello World

Esplora un esempio minimale end-to-end che dimostra oggetti, funzioni logiche, componenti front-end e trigger multipli qui:

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:
yarn add -D twenty-sdk
Quindi aggiungi uno script twenty:
{
  "scripts": {
    "twenty": "twenty"
  }
}
Ora puoi eseguire tutti i comandi tramite yarn twenty <command>, ad es. yarn twenty app: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.
  • Types or client missing/outdated: restart yarn twenty app:dev — it auto-generates the typed client.
  • Modalità di sviluppo non sincronizzata: assicurati che yarn twenty app: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