snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
---
title: Configuração da aplicação
description: Declare a identidade do seu app, o papel padrão, as variáveis e os metadados de marketplace com `defineApplication`.
icon: rocket
---
Todo app deve ter exatamente uma chamada a `defineApplication`. Ela declara:
* **Identidade** — identificador universal, nome de exibição, descrição.
* **Permissões** — qual papel é usado pelas suas funções de lógica e pelos componentes de front-end.
* **Variáveis** *(opcional)* — pares chave–valor expostos ao seu código como variáveis de ambiente.
* **Hooks de pré-instalação/pós-instalação/desinstalação** *(opcional)* — consulte [Funções de lógica](/l/pt/developers/extend/apps/logic/logic-functions).
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
displayName: 'My Twenty App',
description: 'My first Twenty app',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
});
```
Notas:
* Os campos `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 e componentes de front-end. Em funções lógicas (no lado do servidor), elas ficam disponíveis como `process.env.VARIABLE_NAME`. Em componentes de front-end, use `getApplicationVariable('VARIABLE_NAME')` de `twenty-sdk/front-component`. Variáveis marcadas com `isSecret: true` são injetadas apenas em funções lógicas. Componentes de front-end recebem apenas variáveis não secretas.
* O papel padrão é detectado automaticamente a partir do arquivo de definição de papel marcado com [`defineApplicationRole()`](/l/pt/developers/extend/apps/config/roles) — você não precisa referenciá-lo em `defineApplication()`.
* As funções de pré-instalação, pós-instalação e desinstalação são detectadas automaticamente durante a construção do manifesto — você não precisa referenciá-las em `defineApplication()`.
* Passar `defaultRoleUniversalIdentifier` explicitamente ainda é compatível para retrocompatibilidade, mas foi preterido em favor de `defineApplicationRole()`.
* `serverVariables` são configurações e segredos com escopo de instância (por exemplo, chaves de API). Ao contrário de `applicationVariables`, eles não declaram nenhum valor no manifesto — o operador do workspace os preenche nas configurações do app, e eles são injetados nas funções de lógica somente depois de definidos.
* Ambos os tipos de variável aceitam `isDeprecated: true`. Use isso para aposentar uma variável em vez de excluí-la: manter a chave declarada preserva o valor armazenado (excluí-la destrói o valor inserido pelo operador), e a variável ainda é injetada, para que seu código possa fazer fallback para ela — `process.env.NEW_API_KEY ?? process.env.API_KEY`. Uma variável obsoleta desaparece das configurações do aplicativo assim que não tiver valor e nunca é contabilizada na verificação de configuração do aplicativo, portanto `isDeprecated` prevalece sobre `isRequired`.
* Para renderizar uma interface de configuração personalizada na guia **Configurações** do app (no lugar da seção padrão de configuração de variáveis), declare um componente de front-end com [`defineSettingsFrontComponent()`](/l/pt/developers/extend/apps/layout/front-components#custom-settings-component) em seu próprio arquivo. Só é permitido um por app. Seções gerenciadas pelo sistema (atualização automática, URL do app, conexões) permanecem sempre visíveis.
## Tipos de variáveis
Tanto `applicationVariables` quanto `serverVariables` aceitam um `type` opcional (e, para `SELECT` / `MULTI_SELECT`, uma lista de `options`). Tipos compatíveis: `TEXT` (padrão), `BOOLEAN`, `NUMBER`, `NUMERIC`, `DATE`, `DATE_TIME`, `SELECT`, `MULTI_SELECT`, `ARRAY`, `RAW_JSON`, `RICH_TEXT`.
```ts src/application-config.ts
import { defineApplication, FieldType } from 'twenty-sdk/define';
export default defineApplication({
// ...identity, role...
applicationVariables: {
MAX_POSTCARDS: {
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
description: 'Maximum postcards per batch',
type: FieldType.NUMBER,
value: 10,
},
DEFAULT_REGION: {
universalIdentifier: '76c5c321-b6b6-46eb-b4fc-f9f04bb04227',
description: 'Default shipping region',
type: FieldType.SELECT,
options: [
{ label: 'Europe', value: 'eu' },
{ label: 'United States', value: 'us' },
],
value: 'eu',
},
},
});
```
O `type` afeta apenas a **apresentação e validação** — ele seleciona a entrada correspondente na interface de configurações do workspace (um toggle, campo numérico, dropdown, seletor de data, editor JSON, …) e permite que o build valide a sua configuração (por exemplo, `SELECT` / `MULTI_SELECT` devem declarar `options` não vazias). Ele **não** altera a forma como o valor chega ao seu código.
Os valores são **sempre injetados como strings** — isso é inerente às variáveis de ambiente (`process.env.*` aceita apenas string). Quando a sua função de lógica é executada, o executor serializa cada valor de acordo com o `type` declarado ao construir o `process.env`, para que o formato da string seja consistente, não importa como o valor foi definido (padrão do manifesto, interface de configurações ou uma versão anterior):
| Tipo | string de `process.env` |
| ------------------------------------- | -------------------------------------- |
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | o valor bruto (`"eu"`, `"2026-01-01"`) |
| `BOOLEAN` | `"true"` / `"false"` |
| `NUMBER`, `NUMERIC` | string decimal (`"10"`, `"2.5"`) |
| `MULTI_SELECT`, `ARRAY` | array JSON (`'["email","postcard"]'`) |
| `RAW_JSON`, `RICH_TEXT` | objeto JSON (`'{"retries":3}'`) |
Converta a string de volta para o tipo que você espera:
```ts
const maxCards = Number(process.env.MAX_POSTCARDS); // "10" -> 10
const enabled = process.env.ENABLE_TRACKING === 'true'; // "true" -> true
const channels = JSON.parse(process.env.ENABLED_CHANNELS ?? '[]'); // '["email"]' -> ["email"]
const config = JSON.parse(process.env.PROVIDER_CONFIG ?? '{}'); // '{"retries":3}' -> { retries: 3 }
```
O mesmo se aplica a componentes de front-end que leem valores via `getApplicationVariable('VARIABLE_NAME')` — o valor retornado é uma string; converta conforme necessário.
## Papel de função padrão
O papel declarado com [`defineApplicationRole()`](/l/pt/developers/extend/apps/config/roles) controla o que as funções de lógica e os componentes de front-end do aplicativo podem acessar:
* Os tokens de tempo de execução injetados nas suas funções de lógica são derivados desse papel. Uma chamada que atua como uma pessoa é ainda mais limitada pelo que essa pessoa pode fazer, portanto nunca pode exceder nenhum dos dois. Consulte [Qual acesso uma chamada usa](/l/pt/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses).
* O cliente de API tipado é restrito às permissões concedidas a esse papel.
* Siga o princípio do menor privilégio: declare apenas as permissões de que suas funções precisam.
Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel inicial em `src/roles/default-role.ts`. Consulte [Papéis e permissões](/l/pt/developers/extend/apps/config/roles) para a referência completa.
## Metadados do Marketplace
Se você planeja [publicar seu app](/l/pt/developers/extend/apps/operations/publishing), estes campos opcionais controlam como seu app aparece no marketplace:
| Campo | Descrição |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `author` | Nome do autor ou da empresa |
| `category` | Categoria do app para filtragem no marketplace |
| `logo` | Caminho para o logotipo do seu aplicativo empacotado em `public/` (p. ex., `public/logo.png`) |
| `galleryImages` | Array de caminhos de imagem da galeria empacotado em `public/` (por exemplo, `public/screenshot-1.png`) |
| `aboutDescription` | Descrição em markdown mais longa para a aba "Sobre". Se omitido, o marketplace usa o `README.md` do pacote no npm |
| `websiteUrl` | Link para seu site |
| `termsUrl` | Link para os Termos de Serviço |
| `emailSupport` | Endereço de e-mail de suporte |
| `issueReportUrl` | Link para o rastreador de problemas |
<Note>
`logoUrl` e `screenshots` são aliases depreciadas de `logo` e `galleryImages`. URLs absolutas externas (`http://` ou `https://`) não são suportados para estes campos: eles são descartados com um aviso no momento da compilação. Agrupe as imagens na pasta `public/` do seu aplicativo.
</Note>

View File

@@ -0,0 +1,188 @@
---
title: Hooks de instalação
description: Execute lógica durante o ciclo de vida de instalação, atualização ou desinstalação — popule dados iniciais, faça backup de registros, valide a atualização, limpe recursos externos.
icon: wrench
---
Hooks de instalação são funções de lógica especiais que são executadas durante o ciclo de vida de instalação, atualização ou desinstalação. Elas compartilham o mesmo runtime de handler que as [logic functions](/l/pt/developers/extend/apps/logic/logic-functions) normais, mas são declaradas com suas próprias funções de definição e ficam fora do modelo de gatilhos normal (HTTP, cron, eventos de banco de dados). Hooks de instalação recebem um `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` é `undefined` em uma instalação nova); o hook de desinstalação recebe um `UninstallPayload` (`{ version?: string }` — a versão que está sendo removida).
Cada app pode definir **no máximo um** de cada hook (pre-instalação, pós-instalação, desinstalação). A geração do manifesto apresentará erro se mais de um de qualquer tipo for detectado.
```
┌─────────────────────────────────────────────────────────────┐
│ install flow │
│ │
│ upload package → [pre-install] → metadata migration → │
│ generate SDK → [post-install] │
│ │
│ old schema visible new schema visible │
└─────────────────────────────────────────────────────────────┘
```
## Visão geral
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Execuções | Antes da migração de metadados — o esquema e os dados **anteriores** ainda estão intactos | Após a migração e a geração do SDK — o **novo** esquema está em vigor |
| Execução | Sempre síncrona; bloqueia a instalação | Assíncrona por padrão (em fila, 3 novas tentativas); modo síncrono por opt-in via `shouldRunSynchronously: true` |
| Em caso de falha | A instalação é **abortada** antes de qualquer alteração de esquema | Assíncrono: novas tentativas até 3 vezes. Síncrono: o chamador recebe `POST_INSTALL_ERROR` (as alterações de esquema **não** são revertidas) |
| Uso típico | Fazer backup ou corrigir dados que uma migração poderia perder; recusar uma atualização arriscada lançando uma exceção | Popular dados padrão, configurar o workspace, registrar recursos externos |
**Regra geral:** use post-install como padrão. Recurra à pré-instalação somente quando a própria migração for destrutiva e você precisar interceptar o estado anterior antes que ele desapareça.
| Você quer... | Usar |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Popular dados, configurar o workspace, registrar recursos externos | `post-install` |
| Trabalho de longa duração que não deve bloquear a resposta da instalação | `post-install` (modo assíncrono padrão, com novas tentativas do worker) |
| Configuração rápida da qual o chamador depende imediatamente após o retorno da instalação | `post-install` com `shouldRunSynchronously: true` |
| Ler ou fazer backup de dados que a próxima migração perderia | `pre-install` |
| Rejeitar uma atualização que corromperia dados existentes | `pre-install` (lançar uma exceção no manipulador) |
| Reconciliação em cada atualização | Qualquer um dos hooks com `shouldRunOnVersionUpgrade: true` |
## Comportamento compartilhado por ambos os hooks
* A configuração é uma config de `defineLogicFunction` menos as configurações de gatilho, mais `shouldRunOnVersionUpgrade`.
* **Quando é executado**: apenas em instalações novas, por padrão. Defina `shouldRunOnVersionUpgrade: true` para também executar em atualizações. Use `previousVersion` / `newVersion` para ramificar com base no caminho de atualização.
* **Idempotência é importante**: o post-install assíncrono pode ser executado novamente, e qualquer um dos hooks é reexecutado em atualizações quando `shouldRunOnVersionUpgrade` está ativado.
* O ambiente usual de logic-function (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) é injetado, para que você possa chamar a Twenty API com o token do seu app.
* O hook é anexado automaticamente ao manifesto da aplicação em tempo de build (`preInstallLogicFunction` / `postInstallLogicFunction`) — nada para referenciar em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
* O `timeoutSeconds` padrão é 300 para permitir tarefas de configuração mais longas, como o pré-carregamento de dados.
* **Não é executado em modo de desenvolvimento**: `yarn twenty dev` ignora o fluxo de instalação e sincroniza os arquivos diretamente, portanto os hooks nunca são executados ali. Em vez disso, acione-os manualmente:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="É executada depois que a migração de metadados do workspace é aplicada">
É executado depois que seu app termina de ser instalado: metadados sincronizados, cliente SDK gerado, novo esquema disponível para consulta. Exemplo — popular um registro padrão em instalações novas:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
A flag `shouldRunSynchronously` controla o modelo de execução:
* `false` *(padrão)* — colocado em fila na message queue (`retryLimit: 3`) e executado por um worker. A resposta da instalação retorna assim que o job é colocado na fila. **Use para trabalhos de longa duração** — popular grandes conjuntos de dados, APIs lentas de terceiros.
* `true` — executado inline durante o fluxo de instalação. A requisição de instalação fica bloqueada até que o handler termine; um erro lançado aparece como `POST_INSTALL_ERROR` para o chamador (sem novas tentativas). **Use para trabalhos rápidos que precisam ser concluídos antes da resposta.** A migração já foi aplicada neste ponto, portanto uma falha não reverte as alterações de esquema — ela apenas expõe o erro.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="É executada antes que a migração de metadados do workspace seja aplicada">
É executado antes da migração de metadados, contra o esquema **anterior** — o lugar certo para fazer backup de dados que uma migração poderia perder ou para recusar uma atualização arriscada. Antes de executar, o servidor realiza uma "sincronização simplificada" puramente aditiva que registra apenas a função de pré-instalação da nova versão; todo o resto — objetos, campos e dados da versão anterior — permanece intocado quando seu handler é executado.
A pré-instalação é sempre **síncrona** e bloqueia a instalação. Se o handler lançar uma exceção, a instalação é abortada antes de qualquer alteração de esquema — o workspace permanece na versão anterior em um estado consistente. Isto é intencional: a pré-instalação é sua última chance de recusar uma atualização arriscada.
Exemplo — copiar os valores de um campo legado antes que a migração o remova:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
return;
}
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Backs up legacy notes into description before the v2 migration.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
</Accordion>
</AccordionGroup>
## Hook de desinstalação
`defineUninstallLogicFunction` declara um hook que é executado quando um usuário desinstala seu app. Ele é executado **antes** que os metadados, dados e código do app sejam removidos — depois que a migration de exclusão é executada, não sobra nada para executar — portanto, seu handler ainda pode consultar os objetos e registros do app. Use-o para limpar recursos externos: desprovisionar recursos de API, excluir bots remanescentes, revogar webhooks.
Notas:
* O hook é de melhor esforço: ele é executado de forma síncrona, mas uma falha é registrada em log e **nunca bloqueia a desinstalação** — a limpeza não deve tornar impossível remover um app.
* Ele recebe `UninstallPayload` (`{ version?: string }` — a versão que está sendo removida).
* Ele **não** é executado quando uma instalação nova com falha é revertida — o app nunca chegou a ser totalmente instalado.
* O hook não pode ser executado depois que o app foi removido, então a limpeza externa que depende de dados do app (por exemplo, IDs de bots armazenados em registros) deve ser feita aqui, não em um job externo agendado.
* Assim como os hooks de instalação, ele **não é executado no modo de desenvolvimento** — em vez disso, acione-o manualmente:
```bash filename="Terminal"
yarn twenty dev:function:exec --uninstall
```
```ts src/logic-functions/uninstall.ts
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (_payload: UninstallPayload): Promise<void> => {
const client = new CoreApiClient();
const { meetingBots } = await client.query({
meetingBots: { edges: { node: { id: true, externalBotId: true } } },
});
// Delete the provider-side bots so nothing keeps recording after uninstall.
for (const { node } of meetingBots.edges) {
await fetch(`https://api.recorder.example/bots/${node.externalBotId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.RECORDER_API_KEY}` },
});
}
};
export default defineUninstallLogicFunction({
universalIdentifier: 'b2c3d4e5-6789-01bc-def0-234567890abc',
name: 'uninstall',
description: 'Deletes remaining recorder bots when the app is uninstalled.',
timeoutSeconds: 300,
handler,
});
```

View File

@@ -0,0 +1,56 @@
---
title: Visão Geral
description: Configure a própria aplicação — a sua identidade, permissões predefinidas e o que é executado no momento da instalação.
icon: screwdriver-wrench
---
A **camada de configuração** de uma aplicação Twenty é o que descreve a aplicação *para a plataforma* — a sua identidade, as permissões que detém e o código que é executado durante a instalação ou atualização. Estas declarações não adicionam novos formatos de dados nem comportamento em tempo de execução; dizem à Twenty *quem é a aplicação* e *como configurá-la*.
```text
┌────────────────────────────────────────────────────────┐
│ Application — identity, default role, variables, │
│ marketplace metadata │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Role — what the app's logic functions can read │ │
│ │ and write (referenced by Application) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ (at install / upgrade time)
┌──────────────────────────────────┐
│ Pre-install hook │ before metadata migration
└──────────────────────────────────┘
┌──────────────────────────────────┐
│ Post-install hook │ after metadata migration
└──────────────────────────────────┘
│
▼ (at uninstall time)
┌──────────────────────────────────┐
│ Uninstall hook │ before app removal
└──────────────────────────────────┘
```
## Nesta secção
<CardGroup cols={2}>
<Card title="Configuração da aplicação" icon="rocket" href="/l/pt/developers/extend/apps/config/application">
`defineApplication` — identidade, função predefinida, variáveis, metadados do marketplace.
</Card>
<Card title="Funções e Permissões" icon="shield-halved" href="/l/pt/developers/extend/apps/config/roles">
`defineRole` — declara o que as funções de lógica da sua aplicação podem ler e escrever.
</Card>
<Card title="Hooks de instalação" icon="wrench" href="/l/pt/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction`, `definePostInstallLogicFunction` e `defineUninstallLogicFunction` — criam cópias de segurança dos dados, inicializam valores predefinidos, validam atualizações e fazem a limpeza na remoção.
</Card>
</CardGroup>
## Como as peças se relacionam
* A **Aplicação** é o ponto de entrada. Cada aplicação tem exatamente uma chamada `defineApplication()`, e esta aponta para uma **Função** como predefinida.
* A **Função** controla o que as funções de lógica e os componentes de front-end da aplicação podem ler e escrever. Siga o princípio do menor privilégio: conceda apenas as permissões de que o seu código realmente necessita.
* Os **Hooks de instalação** são executados durante a instalação ou atualização — a pré-instalação antes da migração de metadados (para que possa recusar uma atualização arriscada) e a pós-instalação após a migração (para que possa inicializar dados predefinidos com base no novo esquema). O hook de desinstalação é executado imediatamente antes de o app ser removido, para que ele possa limpar recursos externos enquanto os dados do app ainda são legíveis.
<Note>
Os hooks de instalação partilham o ambiente de execução de [função de lógica](/l/pt/developers/extend/apps/logic/logic-functions) — a mesma assinatura de handler, as mesmas variáveis de ambiente, o mesmo cliente de API tipado — mas são declarados com as suas próprias funções "define" e vivem fora do modelo de disparo normal (HTTP, cron, eventos de base de dados).
</Note>

View File

@@ -0,0 +1,67 @@
---
title: Recursos públicos
description: Envie arquivos estáticos — imagens, ícones, fontes — junto com seu app por meio da pasta public/.
icon: folder-open
---
A pasta `public/` na raiz do seu app contém arquivos estáticos — imagens, ícones, fontes ou quaisquer outros recursos de que seu app precisa em tempo de execução. Esses arquivos são incluídos automaticamente nas compilações, sincronizados durante o modo de desenvolvimento e enviados para o servidor.
Arquivos colocados em `public/` são:
* **Publicamente acessíveis** — depois de sincronizados com o servidor, os recursos são servidos em uma URL pública. Não é necessária autenticação para acessá-los.
* **Disponíveis em componentes de front-end** — use URLs de recursos para exibir imagens, ícones ou qualquer mídia dentro de seus componentes React.
* **Disponíveis em funções lógicas** — referencie URLs de recursos em e-mails, respostas de API ou qualquer lógica no lado do servidor.
* **Usado para metadados de marketplace** — os campos `logo` e `galleryImages` no arquivo de referência `defineApplication()` desta pasta (por exemplo, `public/logo.png`). Eles são exibidos no marketplace quando seu app é publicado. URLs absolutas externas são ignoradas para esses campos — agrupe as imagens em `public/`. Cada imagem não pode exceder 10 MB.
* **Sincronizados automaticamente no modo de desenvolvimento** — quando você adiciona, atualiza ou exclui um arquivo em `public/`, ele é sincronizado automaticamente com o servidor. Não é necessário reiniciar.
* **Incluídos nas compilações** — `yarn twenty dev:build` agrupa todos os recursos públicos na saída de distribuição.
## Acessando recursos públicos com `getPublicAssetUrl`
Use o helper `getPublicAssetUrl` de `twenty-sdk` para obter a URL completa de um arquivo no seu diretório `public/`. Funciona tanto em **funções lógicas** quanto em **componentes de front-end**.
**Em uma função lógica:**
```ts src/logic-functions/send-invoice.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const handler = async (): Promise<any> => {
const logoUrl = getPublicAssetUrl('logo.png');
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
// Fetch the file content (no auth required — public endpoint)
const response = await fetch(invoiceUrl);
const buffer = await response.arrayBuffer();
return { logoUrl, size: buffer.byteLength };
};
export default defineLogicFunction({
universalIdentifier: 'a1b2c3d4-...',
name: 'send-invoice',
description: 'Sends an invoice with the app logo',
timeoutSeconds: 10,
handler,
});
```
**Em um componente de front-end:**
```tsx src/front-components/company-card.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const CompanyCard = () => {
const logoUrl = getPublicAssetUrl('logo.png');
return <img src={logoUrl} alt="App logo" />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'company-card',
component: CompanyCard,
});
```
O argumento `path` é relativo à pasta `public/` do seu app. Tanto `getPublicAssetUrl('logo.png')` quanto `getPublicAssetUrl('public/logo.png')` resolvem para a mesma URL — o prefixo `public/` é removido automaticamente, se presente.

View File

@@ -0,0 +1,207 @@
---
title: Funções e Permissões
description: Declare quais objetos e campos as funções de lógica e os componentes de front-end do seu app podem ler e gravar.
icon: shield-halved
---
Um **papel** é um conjunto de permissões: quais objetos um app pode ler ou gravar, quais campos ele pode ver e quais recursos em nível de plataforma ele pode usar. Todas as funções de lógica e os componentes de front-end de cada app herdam as permissões do papel marcado com `defineApplicationRole()` (consulte [O papel padrão da função](#the-default-function-role) abaixo).
```ts src/roles/restricted-company-role.ts
import {
defineRole,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
SystemPermissionFlag,
} from 'twenty-sdk/define';
export default defineRole({
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
label: 'My new role',
description: 'A role that can be used in your workspace',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
fieldPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS],
});
```
## Segurança ao nível da linha
As permissões de objeto e de campo determinam *quais objetos e campos* uma função pode acessar. **Predicados de permissão ao nível da linha** vão além e determinam *quais registros* uma função pode ver e atuar sobre — por exemplo, uma função de autoatendimento em que cada usuário externo vê apenas seus próprios registros.
Declare predicados com `rowLevelPermissionPredicates` na função. Como o restante do manifesto, cada predicado carrega seu próprio `universalIdentifier` e faz referência a um objeto e a um campo por seus respectivos `universalIdentifier`, a um `operand` e, opcionalmente, a um campo de workspaceMember cujo valor é injetado no momento da consulta — assim, você pode expressar: "a relação de proprietário do registro **é** o membro de workspace atual":
```ts src/roles/partner-role.ts
import {
defineRole,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/account-owner.field';
export default defineRole({
universalIdentifier: 'c3c1dc2e-1a08-4de5-abb7-2139b3d99343',
label: 'Partner',
description: 'External partner — sees only its own records',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd0f0c1a2-3b4c-4d5e-8f60-111111111111',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier: ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier,
},
],
});
```
Como os predicados são enviados no manifesto, eles são criados, atualizados e removidos junto com a
função em cada instalação e atualização — não há uma etapa pós-instalação separada para manter em sincronia.
### Combinando predicados com grupos
Por padrão, os predicados de uma função são combinados com `AND`. Para combinar alguns deles com `OR` (ou para
aninhar a lógica), declare uma entrada `rowLevelPermissionPredicateGroups` e faça cada predicado apontar para ela por meio de
`predicateGroupUniversalIdentifier`. Essa função permite que um parceiro veja uma Oportunidade da qual ele **é proprietário**
ou pela qual **é o ponto de contato**:
```ts src/roles/partner-opportunities-role.ts
import {
defineRole,
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
const OPPORTUNITY = STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity;
const CURRENT_MEMBER =
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier;
export default defineRole({
universalIdentifier: 'b2a1c0d9-8e7f-4a6b-9c5d-222222222222',
label: 'Partner (opportunities)',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
canReadObjectRecords: true,
},
],
rowLevelPermissionPredicateGroups: [
{
universalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd4c3b2a1-0e9f-4c8b-9e7d-444444444444',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier: OPPORTUNITY.fields.owner.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
{
universalIdentifier: 'e5d4c3b2-1f0e-4d9c-8f8e-555555555555',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier:
OPPORTUNITY.fields.pointOfContact.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
],
});
```
Notas:
* Dê a cada predicado e grupo um `universalIdentifier` estável (qualquer uuid) — ele identifica a entidade
entre atualizações, e os predicados referenciam os grupos por meio dele.
* Os predicados podem referenciar objetos e campos pertencentes ao seu app ou aos objetos padrão da Twenty.
* A segurança ao nível da linha é aplicada para os workspaces em planos que a incluem; os predicados ainda são sincronizados
em outros planos; eles simplesmente não são aplicados.
## Papel de função padrão
Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel padrão declarado com `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineApplicationRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagUniversalIdentifiers: [],
});
```
`defineApplicationRole()` é um wrapper leve em torno de `defineRole()` que sinaliza **o** papel usado como padrão do seu app no momento da instalação. A validação é idêntica à de `defineRole`, mas o pipeline de build conecta automaticamente seu `universalIdentifier` ao `defaultRoleUniversalIdentifier` do manifesto do app — assim, você não precisa referenciá-lo em [`defineApplication`](/l/pt/developers/extend/apps/config/application).
Notas:
* Exatamente **um** `defineApplicationRole(...)` é permitido por app — o build do manifesto falhará se encontrar mais de um.
* Use `defineRole()` (não `defineApplicationRole()`) para quaisquer papéis **adicionais** que o seu app forneça.
* Definir `defaultRoleUniversalIdentifier` explicitamente em `defineApplication()` ainda é compatível para retrocompatibilidade, mas foi preterido em favor de `defineApplicationRole()`.
## Melhores Práticas
* Comece a partir do papel gerado pelo scaffold e, em seguida, restrinja-o progressivamente — o padrão concede amplo acesso de leitura, o que raramente é o que você quer em produção.
* Substitua `objectPermissions` e `fieldPermissions` pelos objetos e campos de que suas funções realmente precisam.
* `permissionFlagUniversalIdentifiers` controlam o acesso a recursos em nível de plataforma. Mantenha-os no mínimo necessário.
* Veja um exemplo de trabalho: [`exemplos/hello-world/src/roles/default-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/hello-world/src/roles/default-role.ts).