---
title: Aplicativos Twenty
description: Crie e gerencie personalizações do Twenty como código.
---

<Warning>
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
</Warning>

## O que são aplicativos?

Os aplicativos permitem criar e gerenciar personalizações do Twenty **como código**. Em vez de configurar tudo pela UI, você define seu modelo de dados e funções de lógica em código — tornando mais rápido criar, manter e distribuir para vários workspaces.

**O que você pode fazer hoje:**

* Defina objetos e campos personalizados como código (modelo de dados gerenciado)
* Crie funções de lógica com gatilhos personalizados
* Defina habilidades e agentes de IA
* Implemente o mesmo aplicativo em vários espaços de trabalho

## Pré-requisitos

* Node.js 24+ e Yarn 4
* Um espaço de trabalho do Twenty e uma chave de API (crie uma em https://app.twenty.com/settings/api-webhooks)

## Primeiros passos

Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver:

```bash filename="Terminal"
# Criar a estrutura de um novo app (inclui todos os exemplos por padrão)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app

# Iniciar modo de desenvolvimento: sincroniza automaticamente as alterações locais com seu workspace
yarn twenty app:dev
```

O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos:

```bash filename="Terminal"
# Padrão (exhaustivo): todos os exemplos (objeto, campo, função de lógica, componente de front-end, visualização, item do menu de navegação, habilidade, agente)
npx create-twenty-app@latest my-app

# Mínimo: apenas arquivos principais (application-config.ts e default-role.ts)
npx create-twenty-app@latest my-app --minimal
```

A partir daqui você pode:

```bash filename="Terminal"
# Adicionar uma nova entidade à sua aplicação (assistido)
yarn twenty entity:add

# Acompanhar os logs das funções da sua aplicação
yarn twenty function:logs

# Executar uma função pelo nome
yarn twenty function:execute -n my-function -p '{"name": "test"}'

# Executar a função de pré-instalação
yarn twenty function:execute --preInstall

# Executar a função de pós-instalação
yarn twenty function:execute --postInstall

# Compilar a aplicação para distribuição
yarn twenty app:build

# Publicar a aplicação no npm ou em um servidor Twenty
yarn twenty app:publish

# Desinstalar a aplicação do espaço de trabalho atual
yarn twenty app:uninstall

# Exibir a ajuda dos comandos
yarn twenty help
```

Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).

## Estrutura do projeto (com scaffold)

Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:

* Copia um aplicativo base mínimo para `my-twenty-app/`
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
* Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode

Um app recém-criado com o modo padrão `--exhaustive` fica assim:

```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/                           # 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
  │   ├── pre-install.ts                # Pre-install 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
  └── agents/
      └── example-agent.ts                # Example AI agent definition
```

With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).

Em alto nível:

* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis.
* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto.
* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto.
* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo.
* **README.md**: Um README curto na raiz do aplicativo com instruções básicas.
* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução.
* **src/**: O local principal onde você define seu aplicativo como código

### Detecção de entidades

O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:

| Função utilitária                  | Tipo de entidade                                      |
| ---------------------------------- | ----------------------------------------------------- |
| `defineObject()`                   | Definições de objetos personalizados                  |
| `defineLogicFunction()`            | Definições de funções de lógica                       |
| `definePreInstallLogicFunction()`  | Pre-install logic function (runs before installation) |
| `definePostInstallLogicFunction()` | Post-install logic function (runs after installation) |
| `defineFrontComponent()`           | Definições de componentes de front-end                |
| `defineRole()`                     | Definições de papéis                                  |
| `defineField()`                    | Extensões de campos para objetos existentes           |
| `defineView()`                     | Definições de visualizações salvas                    |
| `defineNavigationMenuItem()`       | Definições de itens do menu de navegação              |
| `defineSkill()`                    | Definições de habilidades de agente de IA             |
| `defineAgent()`                    | Definições de agentes de IA                           |

<Note>
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
</Note>

Exemplo de uma entidade detectada:

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

Comandos posteriores adicionarão mais arquivos e pastas:

* `yarn twenty app:dev` irá gerar automaticamente dois clientes de API tipados em `node_modules/twenty-sdk/clients`: `CoreApiClient` (para dados do espaço de trabalho via `/graphql`) e `MetadataApiClient` (para configuração do espaço de trabalho e envio de ficheiros via `/metadata`).
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros.

## Autenticação

Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte:

* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual)
* Chave de API

Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles.

### Gerenciando espaços de trabalho

```bash filename="Terminal"
# Fazer login interativamente (recomendado)
yarn twenty auth:login

# Fazer login em um perfil de espaço de trabalho específico
yarn twenty auth:login --workspace my-custom-workspace

# Listar todos os espaços de trabalho configurados
yarn twenty auth:list

# Alterar o espaço de trabalho padrão (interativo)
yarn twenty auth:switch

# Alternar para um espaço de trabalho específico
yarn twenty auth:switch production

# Verificar o status atual da autenticação
yarn twenty auth:status
```

Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace <name>`.

## Use os recursos do SDK (tipos e configuração)

O twenty-sdk fornece blocos de construção tipados e funções utilitárias que você usa dentro do seu aplicativo. A seguir estão as partes principais que você usará com mais frequência.

### Funções utilitárias

O SDK fornece funções utilitárias para definir as entidades do seu app. Conforme descrito em [Detecção de entidades](#entity-detection), você deve usar `export default define<Entity>({...})` para que suas entidades sejam detectadas:

| Função                             | Finalidade                                                   |
| ---------------------------------- | ------------------------------------------------------------ |
| `defineApplication()`              | Configurar metadados do aplicativo (obrigatório, um por app) |
| `defineObject()`                   | Define objetos personalizados com campos                     |
| `defineLogicFunction()`            | Defina funções de lógica com handlers                        |
| `definePreInstallLogicFunction()`  | Define a pre-install logic function (one per app)            |
| `definePostInstallLogicFunction()` | Define a post-install logic function (one per app)           |
| `defineFrontComponent()`           | Definir componentes de front-end para UI personalizada       |
| `defineRole()`                     | Configura permissões de papéis e acesso a objetos            |
| `defineField()`                    | Estender objetos existentes com campos adicionais            |
| `defineView()`                     | Define visualizações salvas para objetos                     |
| `defineNavigationMenuItem()`       | Define links de navegação da barra lateral                   |
| `defineSkill()`                    | Define habilidades de agente de IA                           |
| `defineAgent()`                    | Defina agentes de IA com prompts do sistema                  |

Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos.

### Definindo objetos

Objetos personalizados descrevem tanto o esquema quanto o comportamento de registros no seu espaço de trabalho. Use `defineObject()` para definir objetos com validação integrada:

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

Pontos-chave:

* Use `defineObject()` para validação integrada e melhor suporte na IDE.
* O `universalIdentifier` deve ser exclusivo e estável entre implantações.
* Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável.
* O array `fields` é opcional — você pode definir objetos sem campos personalizados.
* Você pode criar novos objetos usando `yarn twenty entity:add`, que orienta você sobre nomeação, campos e relacionamentos.

<Note>
**Os campos base são criados automaticamente.** Quando você define um objeto personalizado, o Twenty adiciona automaticamente campos padrão
como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`.
Você não precisa definir esses no seu array `fields` — adicione apenas seus campos personalizados.
Você pode substituir os campos padrão definindo um campo com o mesmo nome no seu array `fields`,
mas isso não é recomendado.
</Note>

### Definindo campos em objetos existentes

Use `defineField()` para adicionar campos personalizados a objetos existentes — tanto objetos padrão (como `company`, `person`, `opportunity`) quanto objetos personalizados definidos por outros aplicativos. Cada campo fica em seu próprio arquivo e faz referência ao objeto de destino por seu `universalIdentifier`.

Para fazer referência a objetos padrão, importe `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` de `twenty-sdk`. Essa constante fornece identificadores estáveis para todos os objetos integrados e seus campos:

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

Pontos-chave:

* `objectUniversalIdentifier` indica ao Twenty a qual objeto anexar o campo. Use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<objectName.universalIdentifier` para objetos padrão.
* Cada campo requer seu próprio `universalIdentifier` estável, um `name`, `type`, `label` e o `objectUniversalIdentifier` de destino.
* Você pode criar novos campos usando `yarn twenty entity:add` e escolhendo a opção de campo.
* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` também é exportado como `STANDARD_OBJECT` por conveniência — ambos se referem à mesma constante.

Os objetos padrão disponíveis incluem: `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`.

Cada objeto padrão também expõe os identificadores de seus campos. Por exemplo, para referenciar um campo específico em um objeto padrão nas permissões de função:

```typescript
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier
```

#### Campos de relação em objetos existentes

Você também pode definir campos de relação que vinculam objetos existentes aos seus objetos personalizados:

```typescript
// src/fields/people-on-call-recording.field.ts
import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk';
import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording';
import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field';

export default defineField({
  universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
  objectUniversalIdentifier:
    CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
  type: FieldType.RELATION,
  name: 'person',
  label: 'Person',
  relationTargetObjectMetadataUniversalIdentifier:
    STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
  relationTargetFieldMetadataUniversalIdentifier:
    CALL_RECORDING_ON_PERSON_ID,
  relationType: RelationType.MANY_TO_ONE,
});
```

### Configuração do aplicativo (application-config.ts)

Todo aplicativo tem um único arquivo `application-config.ts` que descreve:

* **O que é o aplicativo**: identificadores, nome de exibição e descrição.
* **Como suas funções são executadas**: qual papel usam para permissões.
* **Variáveis (opcional)**: pares chave–valor expostos às suas funções como variáveis de ambiente.
* **(Optional) pre-install function**: a logic function that runs before the app is installed.
* **(Opcional) função de pós-instalação**: uma função de lógica que é executada após a instalação da aplicação.

Use `defineApplication()` to define your application configuration:

```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: 'Meu aplicativo Twenty',
  description: 'Meu primeiro aplicativo Twenty',
  icon: 'IconWorld',
  applicationVariables: {
    DEFAULT_RECIPIENT_NAME: {
      universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
      description: 'Nome padrão do destinatário para cartões-postais',
      value: 'Jane Doe',
      isSecret: false,
    },
  },
  defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```

Notas:

* `universalIdentifier` são IDs determinísticos que você controla; gere-os uma vez e mantenha-os estáveis entre sincronizações.
* `applicationVariables` tornam-se variáveis de ambiente para suas funções (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` deve corresponder ao arquivo do papel (veja abaixo).
* Pre-install and post-install functions are automatically detected during the manifest build. See [Pre-install functions](#pre-install-functions) and [Post-install functions](#post-install-functions).

#### Papéis e permissões

Os aplicativos podem definir papéis que encapsulam permissões sobre os objetos e ações do seu espaço de trabalho. O campo `defaultRoleUniversalIdentifier` em `application-config.ts` designa o papel padrão usado pelas funções de lógica do seu app.

* A chave de API em tempo de execução, injetada como `TWENTY_API_KEY`, é derivada desse papel padrão de função.
* O cliente tipado ficará restrito às permissões concedidas a esse papel.
* Siga o princípio do menor privilégio: crie um papel dedicado com apenas as permissões de que suas funções precisam e, em seguida, faça referência ao seu identificador universal.

##### Papel de função padrão (\*.role.ts)

Ao criar um novo aplicativo com o scaffold, a CLI também cria um arquivo de papel padrão. Use `defineRole()` para definir papéis com validação integrada:

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

O `universalIdentifier` desse papel é então referenciado em `application-config.ts` como `defaultRoleUniversalIdentifier`. Em outras palavras:

* **\*.role.ts** define o que o papel de função padrão pode fazer.
* **application-config.ts** aponta para esse papel para que suas funções herdem suas permissões.

Notas:

* Comece pelo papel gerado pelo scaffold e depois restrinja-o progressivamente seguindo o princípio do menor privilégio.
* Substitua `objectPermissions` e `fieldPermissions` pelos objetos/campos de que suas funções precisam.
* `permissionFlags` controlam o acesso a recursos em nível de plataforma. Mantenha-os mínimos; adicione apenas o que for necessário.
* Veja um exemplo funcional no 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).

### Configuração de função de lógica e ponto de entrada

Cada arquivo de função usa `defineLogicFunction()` para exportar uma configuração com um handler e gatilhos opcionais.

```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-sdk/clients';

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

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

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

Tipos de gatilho comuns:

* **route**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**:

> por exemplo, `path: '/post-card/create',` -> chamar em `<APP_URL>/s/post-card/create`

* **cron**: Executa sua função em um agendamento usando uma expressão CRON.
* **databaseEvent**: Executa em eventos do ciclo de vida de objetos do espaço de trabalho. Quando a operação do evento é `updated`, campos específicos a serem observados podem ser especificados no array `updatedFields`. Se deixar indefinido ou vazio, qualquer atualização acionará a função.

> por exemplo, `person.updated`

Notas:

* O array `triggers` é opcional. Funções sem gatilhos podem ser usadas como funções utilitárias chamadas por outras funções.
* Você pode misturar vários tipos de gatilho em uma única função.

### Pre-install functions

A pre-install function is a logic function that runs automatically before your app is installed on a workspace. This is useful for validation tasks, prerequisite checks, or preparing workspace state before the main installation proceeds.

When you scaffold a new app with `create-twenty-app`, a pre-install function is generated for you at `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,
});
```

You can also manually execute the pre-install function at any time using the CLI:

```bash filename="Terminal"
yarn twenty function:execute --preInstall
```

Pontos-chave:

* Pre-install functions use `definePreInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
* Only one pre-install function is allowed per application. The manifest build will error if more than one is detected.
* The function's `universalIdentifier` is automatically set as `preInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks.
* Pre-install functions do not need triggers — they are invoked by the platform before installation or manually via `function:execute --preInstall`.

### Funções de pós-instalação

Uma função de pós-instalação é uma função de lógica que é executada automaticamente após a sua aplicação ser instalada em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho.

Ao criar a estrutura de um novo app com `create-twenty-app`, uma função de pós-instalação é gerada para você em `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,
});
```

Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI:

```bash filename="Terminal"
yarn twenty function:execute --postInstall
```

Pontos-chave:

* Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
* Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
* The function's `universalIdentifier` is automatically set as `postInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados.
* As funções de pós-instalação não precisam de gatilhos — elas são invocadas pela plataforma durante a instalação ou manualmente via `function:execute --postInstall`.

### Payload de gatilho de rota

<Warning>
**Alteração incompatível (v1.16, janeiro de 2026):** O formato do payload de gatilho de rota mudou. Antes da v1.16, os parâmetros de consulta, parâmetros de caminho e corpo eram enviados diretamente como o payload. A partir da v1.16, eles ficam aninhados dentro de um objeto estruturado `RoutePayload`.

**Antes da v1.16:**
```typescript
const handler = async (params) => {
  const { param1, param2 } = params; // Direct access
};
```

**Depois da v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
  const { param1, param2 } = event.body; // Access via .body
  const { queryParam } = event.queryStringParameters;
  const { id } = event.pathParameters;
};
```

**Para migrar funções existentes:** Atualize seu handler para desestruturar de `event.body`, `event.queryStringParameters` ou `event.pathParameters` em vez de diretamente do objeto de parâmetros.
</Warning>

Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto `RoutePayload` que segue o formato do AWS HTTP API v2. Importe o tipo de `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' };
};
```

O tipo `RoutePayload` tem a seguinte estrutura:

| Propriedade                  | Tipo                                  | Descrição                                                                                       |
| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `headers`                    | `Record<string, string \| undefined>` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`)                          |
| `queryStringParameters`      | `Record<string, string \| undefined>` | Parâmetros de query string (valores múltiplos unidos por vírgulas)                              |
| `pathParameters`             | `Record<string, string \| undefined>` | Parâmetros de caminho extraídos do padrão de rota (por exemplo, `/users/:id` → `{ id: '123' }`) |
| `corpo`                      | `object \| null`                      | Corpo da requisição analisado (JSON)                                                            |
| `isBase64Encoded`            | `booleano`                            | Se o corpo está codificado em base64                                                            |
| `requestContext.http.method` | `string`                              | Método HTTP (GET, POST, PUT, PATCH, DELETE)                                                     |
| `requestContext.http.path`   | `string`                              | Caminho bruto da requisição                                                                     |

### Encaminhamento de cabeçalhos HTTP

Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. Para acessar cabeçalhos específicos, liste-os explicitamente no 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'],
    },
  ],
});
```

No seu handler, você pode então acessar esses cabeçalhos:

```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>
  Os nomes dos cabeçalhos são normalizados para minúsculas. Acesse-os usando chaves em minúsculas (por exemplo, `event.headers['content-type']`).
</Note>

Você pode criar novas funções de duas formas:

* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova função de lógica. Isso gera um arquivo inicial com um handler e configuração.
* **Manual**: Crie um novo arquivo `*.logic-function.ts` e use `defineLogicFunction()`, seguindo o mesmo padrão.

### Marcar uma função lógica como ferramenta

Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. Quando uma função é marcada como ferramenta, ela fica disponível para os recursos de IA do Twenty e pode ser selecionada como uma etapa em automações de fluxos de trabalho.

Para marcar uma função lógica como ferramenta, defina `isTool: true` e forneça um `toolInputSchema` descrevendo os parâmetros de entrada esperados usando [JSON Schema](https://json-schema.org/):

```typescript
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import { CoreApiClient } from 'twenty-sdk/clients';

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

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

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

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

Pontos-chave:

* **`isTool`** (`boolean`, padrão: `false`): Quando definido como `true`, a função é registrada como uma ferramenta e fica disponível para agentes de IA e automações de fluxos de trabalho.
* **`toolInputSchema`** (`object`, opcional): Um objeto JSON Schema que descreve os parâmetros que sua função aceita. Os agentes de IA usam esse esquema para entender quais entradas a ferramenta espera e para validar as chamadas. Se omitido, o esquema tem como padrão `{ type: 'object', properties: {} }` (sem parâmetros).
* Funções com `isTool: false` (ou não definido) **não** são expostas como ferramentas. Elas ainda podem ser executadas diretamente ou chamadas por outras funções, mas não aparecerão na descoberta de ferramentas.
* **Nomenclatura de ferramentas**: Quando exposta como uma ferramenta, o nome da função é automaticamente normalizado para `logic_function_<name>` (em minúsculas, caracteres não alfanuméricos substituídos por sublinhados). Por exemplo, `enrich-company` torna-se `logic_function_enrich_company`.
* Você pode combinar `isTool` com gatilhos — uma função pode ser ao mesmo tempo uma ferramenta (chamável por agentes de IA) e acionada por eventos (cron, eventos de banco de dados, rotas) simultaneamente.

<Note>
**Escreva uma boa `description`.** Os agentes de IA dependem do campo `description` da função para decidir quando usar a ferramenta. Seja específico sobre o que a ferramenta faz e quando ela deve ser chamada.
</Note>

### Componentes de front-end

Componentes de front-end permitem criar componentes React personalizados que são renderizados na UI do Twenty. Use `defineFrontComponent()` para definir componentes com validação integrada:

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

```

Pontos-chave:

* Componentes de front-end são componentes React que renderizam em contextos isolados dentro do Twenty.
* O campo `component` faz referência ao seu componente React.
* Os componentes são compilados e sincronizados automaticamente durante `yarn twenty app:dev`.

Você pode criar novos componentes de front-end de duas formas:

* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo componente de front-end.
* **Manual**: Crie um novo ficheiro `.tsx` e use `defineFrontComponent()`, seguindo o mesmo padrão.

#### Onde os componentes de front-end podem ser usados

Os componentes de front-end podem ser renderizados em dois locais dentro do Twenty:

* **Painel lateral** — Componentes de front-end não headless abrem no painel lateral direito. Este é o comportamento padrão quando um componente de front-end é acionado pelo menu de comandos.
* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets nos layouts de página. Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end.

#### Headless vs não headless

Os componentes de front-end têm dois modos de renderização controlados pela opção `isHeadless`:

**Não headless (padrão)** — O componente renderiza uma interface visível. Quando acionado pelo menu de comandos, ele é aberto no painel lateral. Este é o comportamento padrão quando `isHeadless` é `false` ou omitido.

**Headless** — O componente é montado de forma invisível em segundo plano. Ele não abre o painel lateral. Componentes headless são projetados para ações que executam lógica e, em seguida, se desmontam — por exemplo, executar uma tarefa assíncrona, navegar para uma página ou exibir um modal de confirmação. Eles se combinam naturalmente com os componentes Command do SDK descritos abaixo.

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

#### Adicionando itens ao menu de comandos

Para que um componente de front-end apareça como um item no menu de comandos do Twenty, adicione a propriedade `command` a `defineFrontComponent()`. Quando os usuários abrem o menu de comandos (Cmd+K / Ctrl+K), o item aparece e aciona o componente de front-end ao clicar.

O objeto `command` aceita os seguintes campos:

| Campo                                   | Tipo                                        | Descrição                                                                                                  |
| --------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `universalIdentifier`                   | `string` (obrigatório)                      | ID exclusivo para o item do menu de comandos                                                               |
| `etiqueta`                              | `string` (obrigatório)                      | Rótulo exibido no menu de comandos                                                                         |
| `ícone`                                 | `string` (opcional)                         | Nome do ícone (por exemplo, `'IconSparkles'`)                                                              |
| `isPinned`                              | `boolean` (opcional)                        | Se o comando fica fixado no topo do menu                                                                   |
| `availabilityType`                      | `'GLOBAL' \| 'RECORD_SELECTION'` (opcional) | `GLOBAL` mostra o comando em todos os lugares; `RECORD_SELECTION` o mostra apenas em contextos de registro |
| `availabilityObjectUniversalIdentifier` | `string` (opcional)                         | Restringe o comando a um tipo específico de objeto (por exemplo, Person)                                   |

Aqui está um exemplo do app de gravação de chamadas que adiciona um comando com escopo para registros de 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 o comando é sincronizado, ele aparece no menu de comandos. Se o componente de front-end não for headless, o painel lateral é aberto com o componente renderizado dentro. Se for headless, o componente é montado em segundo plano e executa sua lógica.

#### Componentes Command do SDK

O pacote `twenty-sdk` fornece quatro componentes auxiliares Command projetados para componentes de front-end headless. Cada componente executa uma ação ao montar, trata erros exibindo uma notificação de snackbar e desmonta automaticamente o componente de front-end ao concluir.

Importe-os de `twenty-sdk/command`:

* **`Command`** — Executa um callback assíncrono via a prop `execute`.
* **`CommandLink`** — Navega para um caminho do app. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Abre um modal de confirmação. Se o usuário confirmar, executa o callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Abre uma página específica do painel lateral. Props: `page`, `pageTitle`, `pageIcon`.

Aqui está um exemplo completo de um componente de front-end headless usando `Command` para executar uma ação a partir do menu de comandos:

```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 um exemplo usando `CommandModal` para solicitar confirmação antes de executar:

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

#### Contexto de execução

Todo componente de front-end recebe um contexto de execução que fornece informações sobre onde e como está sendo executado. Acesse os valores do contexto usando hooks do `twenty-sdk`:

| Hook                    | Tipo de retorno  | Descrição                                                                                                                                                                                                   |
| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `useFrontComponentId()` | `string`         | O ID exclusivo da instância atual do componente de front-end                                                                                                                                                |
| `useRecordId()`         | `string \| null` | O ID do registro atual, quando o componente é executado em um contexto de registro (por exemplo, um widget de página de registro ou um comando com escopo para um registro). Retorna `null` caso contrário. |
| `useUserId()`           | `string \| null` | O ID do usuário atual                                                                                                                                                                                       |

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

O contexto é reativo — se o registro de contexto mudar, os hooks retornam automaticamente os valores atualizados.

#### Funções da API do host

Os componentes de front-end são executados em um sandbox isolado, mas podem interagir com a UI do Twenty por meio de um conjunto de funções fornecidas pelo host. Importe-as diretamente de `twenty-sdk`:

```typescript
import {
  navigate,
  closeSidePanel,
  enqueueSnackbar,
  unmountFrontComponent,
  openSidePanelPage,
  openCommandConfirmationModal,
} from 'twenty-sdk';
```

| Função                         | Assinatura                                               | Descrição                                                                                                                                                                      |
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `navegar`                      | `(to, params?, queryParams?, options?) => Promise<void>` | Navega para um caminho tipado do app dentro do Twenty                                                                                                                          |
| `closeSidePanel`               | `() => Promise<void>`                                    | Fecha o painel lateral                                                                                                                                                         |
| `enqueueSnackbar`              | `(params) => Promise<void>`                              | Exibe uma notificação de snackbar. Parâmetros: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), `duration` opcional, `detailedMessage`, `dedupeKey`       |
| `unmountFrontComponent`        | `() => Promise<void>`                                    | Desmonta o componente de front-end atual (usado por componentes headless para limpar após a execução)                                                                          |
| `openSidePanelPage`            | `(params) => Promise<void>`                              | Abre uma página no painel lateral. Parâmetros: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState`                                                                       |
| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>`             | Exibe um modal de confirmação e aguarda a resposta do usuário. Parâmetros: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) |

Aqui está um exemplo que usa a API do host para exibir um snackbar e fechar o painel lateral após a conclusão de uma ação:

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

### Habilidades

As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada:

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

Pontos-chave:

* `name` é uma string de identificador exclusivo para a habilidade (recomenda-se kebab-case).
* `label` é o nome de exibição legível por humanos mostrado na UI.
* `content` contém as instruções da habilidade — este é o texto que o agente de IA usa.
* `icon` (opcional) define o ícone exibido na UI.
* `description` (opcional) fornece contexto adicional sobre a finalidade da habilidade.

Você pode criar novas habilidades de duas formas:

* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova habilidade.
* **Manual**: Crie um novo arquivo e use `defineSkill()`, seguindo o mesmo padrão.

### Agentes

Agentes definem agentes de IA com prompts do sistema que podem operar no seu espaço de trabalho. Use `defineAgent()` para definir agentes com validação integrada:

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

Pontos-chave:

* `name` é uma string de identificador exclusivo para o agente (recomenda-se kebab-case).
* `label` é o nome de exibição legível por humanos mostrado na UI.
* `prompt` contém o prompt do sistema — este é o texto de instruções que define o comportamento do agente.
* `icon` (opcional) define o ícone exibido na UI.
* `description` (opcional) fornece contexto adicional sobre a finalidade do agente.

Você pode criar novos agentes de duas formas:

* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo agente.
* **Manual**: Crie um novo arquivo e use `defineAgent()`, seguindo o mesmo padrão.

### Clientes tipados gerados

Dois clientes tipados são gerados automaticamente pelo `yarn twenty app:dev` e armazenados em `node_modules/twenty-sdk/clients` com base no esquema do seu espaço de trabalho:

* **`CoreApiClient`** — consulta o endpoint `/graphql` para dados do espaço de trabalho
* **`MetadataApiClient`** — consulta o endpoint `/metadata` para obter a configuração do espaço de trabalho e o carregamento de ficheiros

```typescript
import { CoreApiClient } from 'twenty-sdk/clients';
import { MetadataApiClient } from 'twenty-sdk/clients';

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` é regenerado automaticamente pelo `yarn twenty app:dev` sempre que os seus objetos ou campos forem alterados. `MetadataApiClient` é fornecido pré-compilado com o SDK.

#### Credenciais em tempo de execução em funções de lógica

Quando sua função é executada no Twenty, a plataforma injeta credenciais como variáveis de ambiente antes da execução do seu código:

* `TWENTY_API_URL`: URL base da API do Twenty que seu aplicativo usa como alvo.
* `TWENTY_API_KEY`: Chave de curta duração com escopo para o papel de função padrão do seu aplicativo.

Notas:

* Você não precisa passar a URL ou a chave de API para o cliente gerado. Ele lê `TWENTY_API_URL` e `TWENTY_API_KEY` de process.env em tempo de execução.
* As permissões da chave de API são determinadas pelo papel referenciado no seu `application-config.ts` via `defaultRoleUniversalIdentifier`. Este é o papel padrão usado pelas funções de lógica do seu app.
* Os aplicativos podem definir papéis para seguir o princípio do menor privilégio. Conceda apenas as permissões de que suas funções precisam e, em seguida, aponte `defaultRoleUniversalIdentifier` para o identificador universal desse papel.

#### Carregamento de ficheiros

`MetadataApiClient` inclui um método `uploadFile` para anexar ficheiros a campos do tipo ficheiro nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).

```typescript
import { MetadataApiClient } from 'twenty-sdk/clients';
import * as fs from 'fs';

const metadataClient = new MetadataApiClient();

const fileBuffer = fs.readFileSync('./invoice.pdf');

const uploadedFile = await metadataClient.uploadFile(
  fileBuffer,                                         // file contents as a Buffer
  'invoice.pdf',                                      // filename
  'application/pdf',                                  // MIME type (defaults to 'application/octet-stream')
  '58a0a314-d7ea-4865-9850-7fb84e72f30b',            // field universal identifier
);

console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
```

A assinatura do método:

```typescript
uploadFile(
  fileBuffer: Buffer,
  filename: string,
  contentType: string,
  fieldMetadataUniversalIdentifier: string,
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
```

| Parâmetro                          | Tipo     | Descrição                                                                |
| ---------------------------------- | -------- | ------------------------------------------------------------------------ |
| `fileBuffer`                       | `Buffer` | O conteúdo bruto do arquivo                                              |
| `filename`                         | `string` | O nome do arquivo (usado para armazenamento e exibição)                  |
| `contentType`                      | `string` | Tipo MIME do arquivo (padrão para `application/octet-stream` se omitido) |
| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo arquivo no seu objeto           |

Pontos-chave:

* O método `uploadFile` está disponível no `MetadataApiClient` porque a mutação de upload é resolvida pelo endpoint `/metadata`.
* Ele usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado — consistente com a forma como os apps referenciam campos em qualquer outro lugar.
* A `url` retornada é um URL assinado que você pode usar para acessar o arquivo enviado.

### Exemplo Hello World

Explore um exemplo mínimo de ponta a ponta que demonstra objetos, funções de lógica, componentes de front-end e vários gatilhos [aqui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):

## Compilando seu app

Depois de desenvolver seu app com `app:dev`, use `app:build` para compilá-lo em um pacote distribuível.

```bash filename="Terminal"
# Compilar o app (a saída vai para .twenty/output/)
yarn twenty app:build

# Compilar e criar um tarball (.tgz) para distribuição
yarn twenty app:build --tarball
```

O processo de build:

1. **Analisa e valida o manifesto** — lê todas as entidades `defineX()` dos seus arquivos de código-fonte e valida a estrutura do manifesto.
2. **Compila funções de lógica e componentes de front-end** — empacota o código-fonte TypeScript em arquivos ESM `.mjs` usando o esbuild.
3. **Gera checksums** — calcula hashes MD5 para cada arquivo gerado, armazenados no manifesto como `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Gera o cliente de API tipado** — inspeciona o esquema GraphQL e gera clientes tipados `CoreApiClient` e `MetadataApiClient`.
5. **Executa uma verificação de tipos do TypeScript** — executa `tsc --noEmit` para detectar erros de tipo antes da publicação.
6. **Reconstrói com o cliente gerado** — realiza uma segunda passagem de compilação para que os tipos do cliente gerado sejam incluídos.
7. **Opcionalmente cria um tarball** — se `--tarball` for passado, executa `npm pack` para criar um arquivo `.tgz` pronto para distribuição.

A saída da compilação em `.twenty/output/` contém:

```text
.twenty/output/
├── manifest.json              # Manifesto com somas de verificação para todos os arquivos compilados
├── package.json               # Copiado da raiz do aplicativo
├── yarn.lock                  # Copiado da raiz do aplicativo
├── src/
│   ├── logic-functions/       # Arquivos .mjs compilados de funções de lógica
│   └── front-components/      # Arquivos .mjs compilados de componentes de front-end
├── public/                    # Recursos estáticos (se houver)
└── my-app-1.0.0.tgz          # Apenas com a opção --tarball
```

| Opção       | Descrição                                                 |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Caminho para o diretório do app (padrão: diretório atual) |
| `--tarball` | Também empacota a saída em um tarball `.tgz`              |

## Publicando seu app

Use `app:publish` para distribuir seu app — ou para o registro do npm ou diretamente para um servidor Twenty.

### Publicar no npm (padrão)

```bash filename="Terminal"
# Publish to npm (requires npm login)
yarn twenty app:publish

# Publish with a dist-tag (e.g. beta, next)
yarn twenty app:publish --tag beta
```

Isso compila o app e executa `npm publish` a partir do diretório `.twenty/output/`. O pacote publicado pode então ser instalado no marketplace da Twenty por qualquer espaço de trabalho.

### Publicar em um servidor Twenty

```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```

Isso compila o app com um tarball, faz o upload para o servidor via a mutação GraphQL `uploadAppTarball` e aciona a instalação em uma única etapa. Isso é útil para implantações privadas ou para testar em um servidor específico.

| Opção             | Descrição                                                             |
| ----------------- | --------------------------------------------------------------------- |
| `[appPath]`       | Caminho para o diretório do app (padrão: diretório atual)             |
| `--server <url>`  | Publicar em um servidor Twenty em vez de no npm                       |
| `--token <token>` | Token de autenticação para o servidor de destino                      |
| `--tag <tag>`     | dist-tag do npm (ex.: `beta`, `next`) — apenas para publicação no npm |

## Registro de aplicação

Antes que um app possa ser instalado em um espaço de trabalho, ele precisa ser **registrado**. Um registro é um registro de metadados que descreve de onde o app vem e como autenticá-lo. Isso é tratado automaticamente pela CLI na maioria dos casos.

### Tipos de origem

Cada registro tem um **tipo de origem** que determina como os arquivos do app são resolvidos durante a instalação:

| Tipo de origem | Como os arquivos são resolvidos                                                             | Caso de uso típico                      |
| -------------- | ------------------------------------------------------------------------------------------- | --------------------------------------- |
| `LOCAL`        | Os arquivos são sincronizados em tempo real pelo observador da CLI — a instalação é omitida | Desenvolvimento com `app:dev`           |
| `NPM`          | Obtidos do registro npm por meio do campo `sourcePackage`                                   | Apps publicados no npm                  |
| `TARBALL`      | Extraídos de um arquivo `.tgz` enviado e armazenado no servidor                             | Apps privados publicados com `--server` |

### Como o registro acontece

* **`app:dev`** — cria automaticamente um registro `LOCAL` na primeira vez que você executa o modo de desenvolvimento em um espaço de trabalho.
* **`app:publish --server`** — faz o upload de um tarball e cria (ou atualiza) um registro `TARBALL`, e em seguida instala o app.
* **marketplace do npm** — registros `NPM` são criados quando apps são sincronizados do registro npm para o catálogo do marketplace da Twenty.
* **API GraphQL** — você também pode criar registros programaticamente por meio da mutação `createApplicationRegistration`.

### Registro vs instalação

**Registro** e **instalação** são conceitos distintos:

* Um **registro** (`ApplicationRegistration`) é um registro global de metadados que descreve o app: seu nome, tipo de origem, credenciais OAuth e status de listagem no marketplace. Ele existe independentemente de qualquer espaço de trabalho.
* Uma **instalação** (`Application`) é uma instância por espaço de trabalho. Quando um usuário instala um app, a Twenty resolve o pacote a partir da origem do registro, grava os arquivos compilados no armazenamento e sincroniza o manifesto (criando objetos, campos, funções de lógica etc.). naquele espaço de trabalho.

Um registro pode ser instalado em muitos espaços de trabalho. Cada espaço de trabalho recebe sua própria cópia dos arquivos e do modelo de dados do app.

### Credenciais OAuth

Cada registro inclui credenciais OAuth (`oAuthClientId` e `oAuthClientSecret`) geradas no momento da criação. Elas são usadas pelo app para autenticar requisições de API em nome dos usuários. O segredo do cliente é retornado **uma única vez** na criação — armazene-o com segurança. Você pode rotacioná-lo posteriormente por meio da mutação `rotateApplicationRegistrationClientSecret`.

## Configuração manual (sem o gerador)

Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json:

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

Em seguida, adicione um script `twenty`:

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

Agora você pode executar todos os comandos via `yarn twenty <command>`, por exemplo, `yarn twenty app:dev`, `yarn twenty help`, etc.

## Resolução de Problemas

* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias.
* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível.
* Tipos ou cliente ausentes/desatualizados: reinicie `yarn twenty app:dev` — ele gera automaticamente o cliente tipado.
* Modo de desenvolvimento não sincronizando: certifique-se de que `yarn twenty app:dev` esteja em execução e de que as alterações não estejam sendo ignoradas pelo seu ambiente.

Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
