---
title: Componentes de front-end
description: Crie componentes React que renderizam dentro da UI do Twenty com isolamento em sandbox.
icon: window-maximize
---
Componentes de front-end são componentes React que renderizam diretamente dentro da UI do Twenty. Eles são executados em um **Web Worker isolado** usando Remote DOM — seu código é executado dentro de um iframe de origem opaca e em sandbox, mas sua interface ainda é renderizada de forma nativa na página em vez de ficar confinada a esse iframe.
Os componentes da frente ainda estão em desenvolvimento activo. Seu código roda contra um DOM parcial, não uma página real do navegador, então usos avançados podem falhar, geralmente silenciosamente. Veja [Limitação atual](#current-limitations).
## Onde os componentes de front-end podem ser usados
Os componentes de front-end podem ser renderizados em três 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 dentro de [layouts de página](/l/pt/developers/extend/apps/layout/page-layouts). 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.
* **Configurações do aplicativo** — Definido com [`defineSettingsFrontComponent()`](#custom-settings-component), o componente de front-end é renderizado como uma seção dentro da aba **Settings** do aplicativo, no lugar da interface padrão de configuração de variáveis.
Um componente de front-end por si só não é acessível pela UI — é preciso *exibi-lo*. As três maneiras de fazer isso são:
* **Associe-o a um [item do menu de comandos](/l/pt/developers/extend/apps/layout/command-menu-items)** — registra-o no menu de comandos (Cmd+K) e, opcionalmente, como uma ação rápida fixada.
* **Incorpore-o como um widget em um [layout de página](/l/pt/developers/extend/apps/layout/page-layouts)** — posiciona-o na página de detalhes de um registro ou em um painel.
* **Definindo-o com [`defineSettingsFrontComponent()`](#custom-settings-component)** — o componente é renderizado como uma seção dentro da aba **Settings** do aplicativo, no lugar da interface padrão de configuração de variáveis.
## Exemplo básico
A maneira mais rápida de ver um componente de front-end em ação é associá-lo a um [`defineCommandMenuItem`](/l/pt/developers/extend/apps/layout/command-menu-items), para que ele apareça como um botão de ação rápida no canto superior direito da página:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
const HelloWorld = () => {
return (
Hello from my app!
This component renders inside Twenty.
);
};
export default defineFrontComponent({
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
name: 'hello-world',
description: 'A simple front component',
component: HelloWorld,
});
```
```ts src/command-menu-items/hello-world.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Após sincronizar com `yarn twenty dev` (ou executando uma única vez o `yarn twenty apply`), a ação rápida aparece no canto superior direito da página:
Clique nele para renderizar o componente inline.
## Campos de configuração
| Campo | Obrigatório | Descrição |
| --------------------- | ----------- | ---------------------------------------------------------------------------- |
| `universalIdentifier` | sim | ID único e estável para este componente |
| `component` | Sim | Uma função de componente React |
| `name` | Não | Nome de Exibição |
| `description` | Não | Descrição do que o componente faz |
| `isHeadless` | Não | Defina como `true` se o componente não tiver interface visível (veja abaixo) |
## Colocando um componente de front-end em uma página
Além de comandos, você pode incorporar um componente de front-end diretamente em uma página de registro adicionando-o como um widget em um **layout de página**. Veja [Layouts de página](/l/pt/developers/extend/apps/layout/page-layouts) para detalhes.
## Componente de configurações personalizadas
Para substituir a interface de configuração de variáveis gerada automaticamente na aba **Settings** do seu aplicativo pelo seu próprio componente, defina-o com `defineSettingsFrontComponent` em vez de `defineFrontComponent`. Ele usa os mesmos [campos de configuração](#configuration-fields) (exceto `isHeadless`, que não é aceito, já que um componente de configurações sempre renderiza uma interface visível) e, adicionalmente, marca o componente como a interface de configurações do app.
O componente é renderizado como uma seção **dentro** da aba Settings, e não como uma substituição de toda a aba. As seções gerenciadas pelo sistema do Twenty — atualização automática, App URL e conexões — são sempre renderizadas acima dela e não podem ser substituídas pelo app.
```tsx src/front-components/app-settings.tsx
import { defineSettingsFrontComponent } from 'twenty-sdk/define';
const AppSettings = () => {
return (
My app settings
{/* render your own configuration UI here */}
);
};
export default defineSettingsFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'app-settings',
description: "Custom UI for the app's Settings tab",
component: AppSettings,
});
```
Apenas um componente de configurações de front-end é permitido por app; declarar mais de um faz com que a build falhe. Quando presente, a aba **Settings** do app renderiza este componente no lugar da interface padrão de configuração de variáveis.
## 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 (`isHeadless: true`)** — 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.
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
}, [recordId]);
return null;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-tracker',
description: 'Tracks record views silently',
isHeadless: true,
component: SyncTracker,
});
```
Como o componente retorna `null`, o Twenty ignora renderizar um contêiner para ele — nenhum espaço vazio aparece no layout. O componente ainda tem acesso a todos os hooks e à API de comunicação do host.
## 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/front-component`:
* **`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 do painel lateral. As props dependem de `page` — por exemplo, `ViewRecord` recebe `recordId` + `objectNameSingular` (além de um id de `tab` opcional para abrir o registro em uma guia específica), outras páginas recebem `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:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return ;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
E um exemplo usando `CommandModal` para solicitar confirmação antes de executar:
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
// perform the deletion
};
return (
);
};
export default defineFrontComponent({
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
name: 'delete-draft',
description: 'Deletes a draft with confirmation',
component: DeleteDraft,
isHeadless: true,
});
```
E um exemplo usando `CommandOpenSidePanelPage` para abrir o registro atual no painel lateral em uma guia específica. `tab` é um id de guia de layout de página (layouts padrão usam ids como `company-tab-emails` ou `company-tab-timeline`; layouts personalizados usam o próprio id da guia). Se o id não existir no layout do registro, a guia padrão será aberta em seu lugar:
```tsx src/front-components/open-company-emails.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
CommandOpenSidePanelPage,
SidePanelPages,
useSelectedRecordIds,
} from 'twenty-sdk/front-component';
const OpenCompanyEmails = () => {
const selectedRecordIds = useSelectedRecordIds();
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
if (!recordId) {
return null;
}
return (
);
};
export default defineFrontComponent({
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
name: 'open-company-emails',
description: 'Opens the current company on its Emails tab',
component: OpenCompanyEmails,
isHeadless: true,
});
```
## Chamando uma função lógica
Os componentes de front são executados no navegador em um Web Worker em sandbox dentro de um iframe de origem opaca, enquanto as [funções lógicas](/l/pt/developers/extend/apps/logic/logic-functions) são executadas no servidor. Não há chamada direta no mesmo processo entre os dois — em vez disso, um componente de front acessa uma função lógica via HTTP.
Uma função lógica declarada com `httpRouteTriggerSettings` é acessível por HTTP em seu caminho de rota. `RestApiClient` trata caminhos que começam com `/s/` como rotas de aplicativo, resolve-os para a URL a partir da qual suas funções são servidas e os autentica com `TWENTY_APP_ACCESS_TOKEN`.
> **No Twenty Cloud, funções lógicas acionadas por HTTP são servidas em um domínio dedicado por workspace** em `https://\.withtwenty.com\`. Para chamadores externos, copie a URL exata das configurações de **HTTP trigger** da função ou da guia **Settings** do aplicativo.
Um componente de front headless pode executar a chamada ao montar via o componente `Command` e, em seguida, desmontar automaticamente:
```tsx src/front-components/sync-prs.tsx
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
await new RestApiClient().post('/s/github/fetch-prs', {
owner: 'twentyhq',
repo: 'twenty',
});
};
return ;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-prs',
description: 'Triggers the fetch-prs logic function',
isHeadless: true,
component: SyncPrs,
});
```
O caminho passado para o `RestApiClient` é o `httpRouteTriggerSettings.path` da função de lógica, prefixado com `/s`. Mantenha `isAuthRequired: true`; o `TWENTY_APP_ACCESS_TOKEN` que a Twenty gera para o seu componente autentica a solicitação:
```ts src/logic-functions/fetch-prs.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string };
// ...fetch from GitHub and persist records...
return { ok: true };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-prs',
handler,
httpRouteTriggerSettings: {
path: '/github/fetch-prs',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
`TWENTY_APP_ACCESS_TOKEN` é injetado automaticamente — consulte [Variáveis de aplicação](#application-variables). Como as variáveis de aplicação secretas nunca são expostas aos componentes de front, mantenha as chaves de API e outra lógica sensível na função lógica, não no componente de front.
### Chamando a API REST da Twenty
Para chamar rotas HTTP do aplicativo ou ler e gravar registros da Twenty a partir de um front component, use `RestApiClient` de `twenty-client-sdk/rest`. Ele envia caminhos `/s/...` para a URL base das funções do seu workspace e qualquer outro caminho, incluindo `/rest/...`, para `TWENTY_API_URL`.
Ele sempre atua como a pessoa que está visualizando a página. `runAs: 'application'` é uma opção apenas de função de lógica: um componente nunca recebe o token do seu próprio aplicativo, portanto, solicitá-lo aqui gera um erro. Coloque o trabalho que precisa do acesso do próprio aplicativo atrás de uma função de lógica e chame-a em vez disso.
| Método | Descrição |
| --------------------------------- | ------------------------------------------------------------------------------- |
| `get(path, options?)` | Envia uma requisição `GET` |
| `post(path, body?, options?)` | Envia uma requisição `POST` |
| `put(path, body?, options?)` | Envia uma requisição `PUT` |
| `patch(path, body?, options?)` | Envia uma requisição `PATCH` |
| `delete(path, options?)` | Envia uma requisição `DELETE` |
| `request(method, path, options?)` | Requisição genérica com qualquer método HTTP |
| `resolveUrl(path, options?)` | Resolve um caminho para sua URL completa sem enviar uma requisição (para links) |
`options` aceita `headers`, `query` (um registro de parâmetros de query string; valores nulos ou indefinidos são ignorados) e um `AbortSignal` via `signal`. Um objeto `body` que não seja `FormData` é serializado em JSON automaticamente. Em um `401`, o cliente atualiza o access token uma vez por meio do host e tenta a requisição novamente.
A URL base e o token são resolvidos do ambiente por padrão. Passe substituições (overrides) para o construtor quando necessário — por exemplo, em testes:
```ts
const client = new RestApiClient({
baseUrl: 'https://myworkspace.twenty.com',
token: 'my-token',
});
```
Requisições com falha geram um erro `RestApiClientError` que expõe `status`, `statusText`, `url` e o `body` analisado:
```tsx
import { RestApiClient, RestApiClientError } from 'twenty-client-sdk/rest';
const client = new RestApiClient();
try {
const people = await client.get('/rest/people', {
query: { limit: 10 },
});
} catch (error) {
if (error instanceof RestApiClientError) {
console.error(error.status, error.body);
}
}
```
## Acessando o contexto de execução
Dentro do seu componente, use hooks do SDK para acessar o usuário atual, o registro e a instância do componente:
```tsx src/front-components/record-info.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
User: {userId}
Record: {recordId ?? 'No record context'}
Component: {componentId}
);
};
export default defineFrontComponent({
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
name: 'record-info',
component: RecordInfo,
});
```
Hooks disponíveis:
| Hook | Retorna | Descrição |
| --------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------- |
| `useUserId()` | `string` ou `null` | O ID do usuário atual |
| `useSelectedRecordIds()` | `string[]` | Todos os IDs dos registros selecionados (array vazio se nenhum estiver selecionado) |
| `useRecordId()` | `string` ou `null` | **Obsoleto.** Use `useSelectedRecordIds()` em vez disso |
| `useFrontComponentId()` | `string` | O ID desta instância do componente |
| `useTimelineActivityId()` | `string` ou `null` | O ID da atividade atual da linha do tempo ao renderizar uma linha personalizada da linha do tempo |
| `useColorScheme()` | `'light'` ou `'dark'` | O esquema de cores ativo da interface do host (`System` já está resolvido) |
| `useFrontComponentExecutionContext(selector)` | varia | Acesse o contexto de execução completo com uma função seletora |
## Variáveis de aplicação
Variáveis de aplicação definidas em [`defineApplication()`](/l/pt/developers/extend/apps/config/application) com `isSecret: false` estão disponíveis nos componentes de front por meio do utilitário `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return
Hello, {recipientName}!
;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
Variáveis secretas (`isSecret: true`) **não** são expostas aos componentes de front. Elas estão disponíveis apenas em [funções de lógica](/l/pt/developers/extend/apps/logic/logic-functions), que são executadas no lado do servidor. Isso impede que valores sigilosos, como chaves de API, sejam enviados para o navegador.
`getApplicationVariable` sempre retorna uma **string** (ou `undefined`), independentemente do `type` declarado da variável. A string é serializada de forma consistente por tipo (booleanos como `"true"` / `"false"`, números como strings decimais, arrays / objetos como JSON), o mesmo formato usado para a logic-function `process.env` — faça você mesmo o parse (`Number(...)`, `JSON.parse(...)`, `=== 'true'`). Veja [Tipos de variáveis](/l/pt/developers/extend/apps/config/application#variable-types).
As seguintes variáveis de sistema estão sempre disponíveis via `process.env`:
| Variável | Descrição |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TWENTY_API_URL` | URL base da API principal da Twenty |
| `TWENTY_APP_ACCESS_TOKEN` | Token de curta duração com escopo limitado à interseção entre a função da pessoa conectada e a do seu aplicativo, de modo que um componente nunca possa fazer mais do que a pessoa que o está visualizando |
### `TWENTY_FUNCTIONS_URL`
A Twenty também injeta `TWENTY_FUNCTIONS_URL` em front components e funções de lógica: a URL base a partir da qual as funções de lógica acionadas por HTTP do seu aplicativo são servidas.
Ela existe porque essa URL nem sempre é o próprio servidor da Twenty. No Twenty Cloud, as rotas do aplicativo são servidas em um domínio dedicado por workspace (`https://\.withtwenty.com`, ou o domínio público primário da aplicação quando um é configurado) para que respostas criadas pelo aplicativo sejam executadas em uma origem isolada, em vez de na origem do aplicativo Twenty. Instâncias self-hosted e locais servem rotas do aplicativo sob o prefixo `/s` no próprio servidor e podem não definir a variável. Como a URL base varia por workspace e por instância, seu código não pode defini-la de forma fixa — o servidor injeta o valor correto em tempo de execução.
Você raramente precisa lê-la diretamente. Chame suas rotas por meio de `RestApiClient` com um caminho prefixado com `/s/` e o cliente resolverá a URL para você: ele remove o prefixo `/s` e direciona para `TWENTY_FUNCTIONS_URL`, recorrendo a `\/s` quando a variável não está definida. Use `resolveUrl('/s/\')` para obter a URL absoluta sem enviar uma requisição, por exemplo, para um link. Leia a variável diretamente apenas ao construir uma URL manualmente:
```ts
const routeUrl = `${process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`}/documents/generate`;
```
## API de comunicação do host
Componentes de front-end podem acionar navegação, modais e notificações usando funções de `twenty-sdk`:
| Função | Descrição |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Navegar para uma página no app |
| `openSidePanelPage(params)` | Abrir um painel lateral |
| `closeSidePanel()` | Fechar o painel lateral |
| `openCommandConfirmationModal(params)` | Mostrar um diálogo de confirmação |
| `enqueueSnackbar(params)` | Mostrar uma notificação do tipo toast |
| `unmountFrontComponent()` | Desmontar o componente |
| `updateProgress(progress)` | Atualizar um indicador de progresso |
| `uploadFile(file, { fieldMetadataId, fileName? })` | Carrega um `Blob` em um campo FILES; resulta em `{ status: 'uploaded', file: { fileId, path, url, size, mimeType } }` ou `{ status: 'failed', reason: 'invalid-params' \| 'upload-failed' }` |
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:
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const [recordId] = useSelectedRecordIds();
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 (
Archive this record?
);
};
export default defineFrontComponent({
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
name: 'archive-record',
description: 'Archives the current record',
component: ArchiveRecord,
});
```
### Armazenamento
`localStorage` e `sessionStorage` funcionam como em uma página normal, com a API síncrona padrão. Suas chaves são limitadas à instalação do seu app e ao usuário conectado: nenhum outro app pode lê-las, e outro usuário que entrar no mesmo navegador começará com um armazenamento vazio. Os valores gravados em `localStorage` permanecem no dispositivo entre recarregamentos; `sessionStorage` permanece durante a sessão do navegador.
```tsx src/front-components/note-draft.tsx
import { useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
const NoteDraft = () => {
const [draft, setDraft] = useState(() => localStorage.getItem('draft') ?? '');
const handleChange = (nextDraft: string) => {
setDraft(nextDraft);
localStorage.setItem('draft', nextDraft);
};
return (