Skip to main content
As funções de lógica são funções TypeScript no lado do servidor que são executadas na plataforma Twenty. Elas podem ser acionadas por solicitações HTTP, agendamentos cron ou eventos de banco de dados — e também podem ser expostas como ferramentas para agentes de IA.
Cada arquivo de função usa defineLogicFunction() para exportar uma configuração com um manipulador e gatilhos opcionais.
src/logic-functions/createPostCard.logic-function.ts
Tipos de gatilho disponíveis:
  • httpRoute: Expõe sua função em um caminho e método HTTP. No código do app, prefixe o caminho da rota com /s/ ao usar RestApiClient; a URL implantada usa a base injetada TWENTY_FUNCTIONS_URL (ou \<server-url>/s quando ela não está definida).
Para invocar uma função de lógica acionada por rota a partir de um componente de front-end (headless), consulte Chamando uma função de lógica.
  • 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, *.created, company.*
  • serverRoute: expõe uma única rota HTTP com escopo de registro. Uma função resolver (declarada com serverRouteTriggerSettings) é executada no workspace proprietário e retorna uma Response síncrona ou o workspace de destino E a função de lógica de destino para enfileirar; no caminho de enfileiramento, a plataforma confirma com 202 e executa esse destino na fila de workers. Veja gatilho de rota de servidor.
Você também pode executar manualmente uma função usando a CLI:
Você pode acompanhar os logs com:

Payload de gatilho de rota

Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto RoutePayload que segue o formato HTTP API v2 da AWS. Importe o tipo RoutePayload de twenty-sdk/logic-function:
O tipo RoutePayload tem a seguinte estrutura:

forwardedRequestHeaders

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

Resposta HTTP personalizada

Por padrão, retornar um valor simples do seu handler o envia de volta como uma resposta 200 (JSON para objetos, text/plain para strings). Para controlar o código de status e os cabeçalhos da resposta, retorne um Response de twenty-sdk/logic-function:
Por motivos de segurança, os cabeçalhos de resposta são restringidos a uma lista de permissões. Qualquer cabeçalho que não esteja na lista (por exemplo, Set-Cookie, cabeçalhos CORS como Access-Control-Allow-Origin ou cabeçalhos personalizados X-*) é silenciosamente descartado antes de a resposta ser enviada. Os cabeçalhos de resposta permitidos são:
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
O código de status deve ser um código de status HTTP válido (entre 100 e 599). Os nomes dos cabeçalhos de resposta são comparados sem distinção entre maiúsculas e minúsculas.

Respostas de erro da plataforma

Além das próprias respostas do seu handler, a plataforma responde diretamente às chamadas de rota em algumas situações: 404 quando a rota ou função não existe, 403 quando a aplicação está parada, 429 quando o limite de taxa de execução é atingido e 422 quando as dependencies de produção da aplicação são grandes demais para serem instaladas — consulte limites de tamanho de dependencies.

Gatilho de rota de servidor

httpRouteTriggerSettings expõe uma função em /s/ e resolve o workspace a partir do host da solicitação — o que funciona quando cada workspace tem seu próprio domínio. Provedores de terceiros, entretanto, entregam os eventos de todos os workspaces para uma URL. Para esse caso, use serverRouteTriggerSettings.Nesse caso, o gatilho tem duas partes:
  1. Uma função de lógica de resolver — declarada com serverRouteTriggerSettings — é executada no seu workspace proprietário (o workspace que é proprietário do registro da aplicação). Ela inspeciona a requisição recebida e retorna um dos seguintes:
    • { workspaceId, targetLogicFunctionUniversalIdentifier, payload? } — a plataforma coloca esse destino em fila no workspace resolvido e confirma com 202 { queued: true }, ou
    • uma Response de twenty-sdk/logic-function — a plataforma repete essa resposta HTTP sincronamente e não coloca nenhum destino em fila (use isso para handshakes de desafio, como o url_verification do Slack).
    O resolver é o ponto único de autorização — a URL carrega apenas o identificador do resolver. Este é o local preferencial para verificar assinaturas de requisição: o resolver é executado antes de qualquer efeito colateral, tem acesso ao rawBody original e aos headers encaminhados e pode rejeitar sem nunca tocar no alvo.
  2. Uma função de lógica de target — uma função de lógica regular por workspace — então é executada no workspace resolvido com o payload retornado pelo resolver (ou o payload original da requisição, se o resolver não o tiver transformado). Seu valor de retorno não é observado pelo chamador HTTP quando o resolver escolheu o caminho de enfileiramento.
src/logic-functions/resolve-server-route.logic-function.ts
src/logic-functions/handle-invoice-paid.logic-function.ts
O endpoint fica acessível em:
O identificador é o universalIdentifier do resolver, vindo do seu manifest. Registre essa URL junto ao provedor.Respondendo a uma verificação GET. Alguns provedores verificam um endpoint antes de enviar dados a ele, enviando um GET com um desafio para a mesma URL à qual posteriormente enviarão eventos por POST — a API do WhatsApp Cloud da Meta é um deles. Uma rota do servidor responde somente a POST, a menos que você indique o contrário, portanto declare ambos os métodos:
O desafio chega em event.queryStringParameters, e retornar uma Response o repete para o provedor na mesma solicitação. Um corpo de string é enviado como text/plain, que é o que esses provedores esperam:
httpMethods substitui o padrão em vez de ser adicionado a ele; portanto, apenas ['GET'] faz com que a rota rejeite POST. Apenas GET e POST são compatíveis. Deixe-o não definido, a menos que o provedor precise do segundo verbo: uma rota que declara GET terá seu resolvedor executado por qualquer chamador não autenticado, incluindo os rastreadores e expansores de links que enviam GET sem solicitação. Qualquer coisa para a qual a plataforma não tenha um método recebe a resposta 405 sem que o resolvedor seja executado.
O aplicativo deve ser reivindicado e instalado em seu workspace proprietário. Como o resolvedor é executado no workspace proprietário (o workspace que é proprietário do registro do aplicativo), um acionador de rota de servidor só funciona depois que o aplicativo tiver sido reivindicado — ou seja, tiver um workspace proprietário — e esse aplicativo estiver instalado no workspace proprietário. Até que ambas as condições sejam verdadeiras, o resolvedor não tem onde ser executado, portanto a rota não pode ser despachada. Um aplicativo que expõe uma função lógica serverRouteTriggerSettings, portanto, não pode ser listado no marketplace até que seja reivindicado e instalado em seu workspace proprietário.
Contrato do resolver. O tipo LogicFunctionConfig do SDK aplica isso em tempo de compilação: assim que você define serverRouteTriggerSettings, o seu handler fica limitado a retornar uma Response ou { workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object } (ou uma Promise de qualquer um deles). No caminho de dispatch, o workspaceId deve ser um workspace onde a função de destino esteja instalada, caso contrário a requisição é rejeitada com 404. Um resultado que não corresponda a nenhum dos formatos — incluindo um cujos identificadores não sejam UUIDs — é rejeitado com 502.
A verificação de assinatura é sua responsabilidade — verifique no resolver. A plataforma não verifica assinaturas de requisição. O resolver é o local recomendado para fazer isso: ele é executado primeiro, com acesso a event.rawBody e aos headers que você listou em forwardedRequestHeaders, e um erro lançado (ou qualquer workspaceId que não corresponda) interrompe o despacho antes que o alvo seja invocado. Se, em vez disso, você empurrar a verificação para o target, o target deve ter cuidado para não perder o rawBody e os headers — isto é, o resolver não deve retornar um payload. Sempre verifique antes de qualquer efeito colateral e use uma comparação em tempo constante.
Para assinaturas de requisição, a maioria dos provedores assina com HMAC-SHA256; as partes que diferem são o nome do header, a codificação do digest e a string de payload assinada. Alguns exemplos:O exemplo de resolver acima já mostra o fluxo de HMAC-SHA256 do GitHub — adapte o nome do header, a codificação do digest e a string de payload assinada de acordo com o provedor com o qual você está integrando.
Quando o resolver retorna um objeto de dispatch, a rota responde com 202 { queued: true } e o destino é executado na fila de workers — quem faz a chamada nunca observa a latência, o resultado ou as falhas do destino (essas são registradas nos logs de execução). Isso evita que reentregas do remetente amplifiquem a lentidão do processamento, que é o que você quer para a ingestão de webhooks.Quando o chamador precisa ler o corpo da resposta na mesma requisição (handshakes de desafio, acknowledgements interativos), retorne, em vez disso, uma Response a partir do resolver. A plataforma o replica de forma síncrona e ignora a fila; seus headers passam pela mesma allow-list que as respostas de rotas HTTP. Mantenha o resolver rápido — alguns provedores (por exemplo, Slack) atingem timeout em poucos segundos. Como o resolver fica acessível como um endpoint público, proteja-o com rate limiting na sua borda.

Payload do gatilho de evento do banco de dados

Quando um gatilho de evento do banco de dados invoca sua função lógica, ela recebe um DatabaseEventPayload por registro alterado. O payload combina metadados sobre o workspace e o objeto de origem com o evento em nível de registro.
O payload inclui:Para exclusões lógicas, .deleted segue o formato de atualização porque o campo deletedAt do registro é alterado. Para exclusões permanentes, use .destroyed.
databaseEventTriggerSettings.updatedFields filtra quais eventos de atualização disparam a função. event.properties.updatedFields informa quais campos realmente foram alterados no evento atual.
Exemplo de evento de criação:
Exemplo de evento de atualização:
Disparar somente em atualizações de email:
Exemplo de evento de destruição:

Contexto de execução

Cada manipulador recebe um segundo argumento que descreve a própria execução, independentemente do que a acionou. Enquanto o primeiro argumento muda de formato conforme o acionador, este não muda:
userWorkspaceId e workspaceMemberId são null quando ninguém acionou a execução: agendamentos cron, hooks de instalação e webhooks não autenticados não têm uma pessoa por trás deles. Eles também são null quando a pessoa não tem um registro de membro do espaço de trabalho ou quando o seu foi excluído.
O contexto informa quem acionou a execução. O que a execução pode fazer é uma declaração separada, abaixo.

O acesso que uma chamada usa

Cada cliente — CoreApiClient, MetadataApiClient e RestApiClient — atua como a pessoa que acionou a execução: seu papel é combinado com o do seu aplicativo, portanto a chamada nunca pode fazer mais do que qualquer um dos dois. Esse é o padrão e significa que uma pessoa nunca pode usar seu aplicativo para exceder as próprias permissões.Quando ninguém acionou a execução, não há uma pessoa para representar, então o mesmo cliente recorre ao acesso do próprio aplicativo: agendamentos cron, hooks de instalação e webhooks não autenticados não exigem tratamento especial.Algumas chamadas precisam legitimamente do acesso do próprio aplicativo mesmo quando há uma pessoa por trás da execução — para ler os registros de configuração do seu aplicativo ou realizar tarefas que essa pessoa não conseguiria realizar por conta própria. Crie um segundo cliente para essas chamadas:
Crie ambos uma vez, no escopo do módulo, e cada local de chamada indicará qual acesso usa pelo cliente que chama. RestApiClient aceita a mesma opção:
Uma execução que ninguém acionou atua como seu aplicativo. Agendamentos cron, hooks de instalação e webhooks não autenticados não têm uma pessoa por trás deles, portanto o cliente padrão recorre ao acesso do próprio aplicativo e continua funcionando. runAs: 'application' é necessário apenas quando você quer esse acesso em uma execução que uma pessoa acionou.Verifique context.workspaceMemberId quando uma função se comportar de modo diferente dependendo de haver alguém por trás dela, por exemplo, para atribuir um registro.
Os auxiliares do SDK que acessam os recursos do próprio aplicativo sempre usam o acesso dele e ignoram runAs: o armazenamento de chave-valor, as conexões, runAgent, getPublicAssetUrl e a cobrança de créditos.

Expor uma função como ferramenta de IA ou como ação de fluxo de trabalho

As funções de lógica podem ser expostas em duas superfícies, cada uma com seu próprio gatilho:
  • toolTriggerSettings — torna a função disponível para os recursos de IA do Twenty (chat, MCP, chamadas de função). Usa o JSON Schema padrão, o formato que os LLMs entendem nativamente.
  • workflowActionTriggerSettings — torna a função visível como uma etapa no construtor visual de fluxos de trabalho. Usa o InputSchema avançado do Twenty para que o construtor possa renderizar editores de campo adequados, seletores de variáveis e rótulos.
Uma função pode optar por uma, pela outra ou por ambas. Ficam ao lado de cronTriggerSettings, databaseEventTriggerSettings e httpRouteTriggerSettings — mesmo padrão, mesmo formato.
Relação com a ação Code do fluxo de trabalho. A ação Code incorporada no construtor de fluxos de trabalho é, em si, uma função lógica — a Twenty cria uma para cada etapa Code e exibe seu editor em linha. workflowActionTriggerSettings é como você transforma esse código em linha único em uma ação reutilizável: defina a função uma vez no seu app e ela se torna selecionável em qualquer fluxo de trabalho, em vez de ser copiada e colada em cada etapa Code. Veja a ação Code no guia do usuário para a visão do usuário final.
src/logic-functions/enrich-company.logic-function.ts
Pontos-chave:
  • Uma função pode misturar superfícies — declare tanto toolTriggerSettings quanto workflowActionTriggerSettings para expô-la no chat E no construtor de fluxos de trabalho.
  • toolTriggerSettings.inputSchema e workflowActionTriggerSettings.inputSchema são opcionais. Quando omitidos, o construtor de manifestos os infere a partir do código-fonte do handler (JSON Schema para a ferramenta de IA, InputSchema do Twenty para a ação de fluxo de trabalho). Forneça um explicitamente quando quiser uma tipagem mais rica — por exemplo, com campos compatíveis com FieldMetadataType, como CURRENCY ou RELATION para o construtor de fluxos de trabalho, ou com campos description que o agente de IA pode ler:
Para declarar seus parâmetros uma vez e atender a ambas as interfaces, defina um único JSON Schema (InputJsonSchema) e converta-o para a ação de fluxo de trabalho com jsonSchemaToInputSchema de twenty-sdk/logic-function. toolTriggerSettings.inputSchema recebe o JSON Schema diretamente, enquanto workflowActionTriggerSettings.inputSchema espera o InputSchema da Twenty:
Um exemplo completo de ação de fluxo de trabalho
workflowActionTriggerSettings aceita quatro campos:Juntando tudo — uma função exposta como uma ação de fluxo de trabalho, com uma saída declarada para que etapas posteriores possam referenciar taskId:
src/logic-functions/enrich-company.logic-function.ts
Depois que o app é instalado, Enrich Company aparece no seletor de ações do construtor de fluxos de trabalho. O construtor renderiza companyName e domain como campos de entrada (cada um podendo extrair valores de etapas anteriores), e as etapas subsequentes podem referenciar as saídas taskId e enriched da etapa.
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.
Auxiliares de tempo de execução. twenty-sdk/utils reexporta pequenos auxiliares de tempo de execução para que os handlers nunca importem diretamente de twenty-shared. Por exemplo, isDefined(value) retorna false tanto para null quanto para undefined — use-o para restringir com segurança entradas opcionais de handlers, que podem chegar como null em tempo de execução mesmo quando tipadas como T | undefined:
Hooks de instalação — manipuladores de pré-instalação, pós-instalação e desinstalação — compartilham esse ambiente de execução, mas são declarados com suas próprias funções de definição e não usam configurações de gatilho. Veja Hooks de instalação para definePreInstallLogicFunction, definePostInstallLogicFunction e defineUninstallLogicFunction.

Criar uma atividade da linha do tempo.

Use createTimelineActivity() para publicar um evento de domínio explícito a partir de uma função lógica. Primeiro, defina o evento como um tipo de atividade da linha do tempo e, em seguida, enderece o tipo e os objetos por seus identificadores universais estáveis:
O Twenty resolve os identificadores universais para os IDs de metadados específicos da instalação, valida se o tipo de atividade da linha do tempo pertence ao app chamador e cria um instantâneo de seus metadados de apresentação na nova atividade. As entradas obrigatórias são timelineActivityTypeUniversalIdentifier, targetObjectUniversalIdentifier e targetRecordId. Você também pode fornecer happensAt, properties e workspaceMemberId. happensAt controla a posição e o horário exibido do evento na linha do tempo; por padrão, usa o horário de criação. Para associar outro registro ao evento, forneça linkedRecordId e linkedObjectMetadataUniversalIdentifier juntos. Você também pode fornecer linkedRecordCachedName como uma alternativa histórica de exibição:
A role da função lógica precisa de permissão de escrita no objeto padrão timelineActivity. Mantenha os tipos de evento explícitos não vinculados a uma action; um tipo vinculado a uma ação já recebe eventos de auditoria automáticos e, caso contrário, produziria linhas duplicadas.

Clientes de API tipados (twenty-client-sdk)

O pacote twenty-client-sdk fornece dois clientes GraphQL tipados para interagir com a API do Twenty a partir das suas funções de lógica e componentes de front-end.
CoreApiClient é o cliente principal para consultar e mutar dados do espaço de trabalho. Ele é gerado a partir do schema do seu espaço de trabalho durante yarn twenty dev ou yarn twenty dev:build, então é totalmente tipado para corresponder aos seus objetos e campos.
O cliente usa uma sintaxe de selection-set: passe true para incluir um campo, use __args para argumentos e aninhe objetos para relações. Você tem preenchimento automático e verificação de tipos completos com base no schema do seu espaço de trabalho.
CoreApiClient é gerado em tempo de dev/build. Se você usá-lo sem executar primeiro yarn twenty dev ou yarn twenty dev:build, ele lançará um erro. A geração ocorre automaticamente — a CLI analisa o schema GraphQL do seu espaço de trabalho e gera um cliente tipado usando @genql/cli.

Usando CoreSchema para anotações de tipo

CoreSchema fornece tipos TypeScript que correspondem aos objetos do seu espaço de trabalho — útil para tipar o estado de componentes ou parâmetros de função:
MetadataApiClient é fornecido pré-compilado com o SDK (não é necessário gerar). Ele consulta o endpoint /metadata para configuração do espaço de trabalho, aplicativos e upload de arquivos. Ele aceita a mesma opção runAs que CoreApiClient — consulte O acesso que uma chamada usa.

Carregamento de arquivos

MetadataApiClient inclui um método uploadFile para anexar arquivos a campos do tipo arquivo:
Pontos-chave:
  • 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 aplicativo esteja instalado.
  • A url retornada é uma URL assinada que você pode usar para acessar o arquivo enviado.
Quando seu código é executado no Twenty (funções de lógica ou componentes de front-end), a plataforma injeta credenciais como variáveis de ambiente:
  • TWENTY_API_URL — URL base da API do Twenty
  • TWENTY_APP_ACCESS_TOKEN — Chave de curta duração para o acesso padrão: o papel de uma pessoa combinado com o do seu aplicativo quando há alguém por trás da execução, o papel do próprio aplicativo quando não há ninguém. A pessoa é quem acionou a execução em uma função lógica ou quem está visualizando a página em um componente front.
  • TWENTY_APP_APPLICATION_ACCESS_TOKEN — Chave de curta duração com escopo apenas para o papel do próprio aplicativo. Apenas funções lógicas, sempre injetada nelas, e é o que runAs: 'application' usa.
Você não precisa passá-las aos clientes — eles leem de process.env automaticamente, e O acesso que uma chamada usa explica como escolher entre elas. As permissões do próprio aplicativo são determinadas pelo papel declarado com defineApplicationRole() (ou referenciado via defaultRoleUniversalIdentifier em application-config.ts); uma execução que atua como uma pessoa nunca pode exceder esse papel nem o dela.