Saltar al contenido principal
Las funciones lógicas son funciones de TypeScript del lado del servidor que se ejecutan en la plataforma Twenty. Pueden activarse mediante solicitudes HTTP, programaciones de cron o eventos de base de datos — y también pueden exponerse como herramientas para agentes de IA.
Cada archivo de función usa defineLogicFunction() para exportar una configuración con un controlador y desencadenadores opcionales.
src/logic-functions/createPostCard.logic-function.ts
Tipos de desencadenadores disponibles:
  • httpRoute: Expone tu función en una ruta y método HTTP. En el código de la aplicación, añade el prefijo /s/ a la ruta cuando uses RestApiClient; la URL desplegada utiliza la base inyectada TWENTY_FUNCTIONS_URL (o \<server-url>/s cuando no está configurada).
Para invocar una función de lógica activada por una ruta desde un componente de frontend (headless), consulta Llamar a una función de lógica.
  • cron: Ejecuta tu función en un horario usando una expresión CRON.
  • databaseEvent: Se ejecuta en eventos del ciclo de vida de objetos del espacio de trabajo. Cuando la operación del evento es updated, se pueden especificar campos específicos que se deben escuchar en la matriz updatedFields. Si se deja sin definir o vacío, cualquier actualización activará la función.
p. ej. person.updated, *.created, company.*
  • serverRoute: expone una única ruta HTTP con ámbito de registro. Una función de resolver (declarada con serverRouteTriggerSettings) se ejecuta en el espacio de trabajo propietario y devuelve el espacio de trabajo de destino Y la función de lógica de destino a la que se debe enviar; la plataforma luego ejecuta esa función de destino y devuelve su respuesta. Consulta Disparador de ruta de servidor.
También puedes ejecutar manualmente una función usando la CLI:
Puedes ver los registros con:

Carga útil del disparador de ruta

Cuando un desencadenador de ruta invoca tu función de lógica, esta recibe un objeto RoutePayload que sigue el formato AWS HTTP API v2. Importa el tipo RoutePayload desde twenty-sdk/logic-function:
El tipo RoutePayload tiene la siguiente estructura:

forwardedRequestHeaders

De forma predeterminada, los encabezados HTTP de las solicitudes entrantes no se pasan a tu función de lógica por razones de seguridad. Para acceder a encabezados específicos, enuméralos explícitamente en el arreglo forwardedRequestHeaders:
En tu controlador, accede a los encabezados reenviados así:
Los nombres de los encabezados se normalizan a minúsculas. Accede a ellos usando claves en minúsculas (p. ej., event.headers['content-type']).

Respuesta HTTP personalizada

De forma predeterminada, devolver un valor sencillo desde tu controlador lo envía de vuelta como una respuesta 200 (JSON para objetos, text/plain para cadenas). Para controlar el código de estado y los encabezados de la respuesta, devuelve un Response desde twenty-sdk/logic-function:
Por razones de seguridad, los encabezados de la respuesta están restringidos a una lista de permitidos. Cualquier encabezado que no esté en la lista (por ejemplo, Set-Cookie, encabezados CORS como Access-Control-Allow-Origin, o encabezados personalizados X-*) se descarta silenciosamente antes de que se envíe la respuesta. Los encabezados de respuesta permitidos son:
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
El código de estado debe ser un código de estado HTTP válido (entre 100 y 599). Los nombres de los encabezados de respuesta se comparan sin distinguir mayúsculas de minúsculas.

Disparador de ruta de servidor

httpRouteTriggerSettings expone una función bajo /s/ y resuelve el espacio de trabajo a partir del host de la solicitud — lo cual funciona cuando cada espacio de trabajo tiene su propio dominio. Los proveedores de terceros, sin embargo, entregan los eventos de cada inquilino a una URL. Para ese caso, usa serverRouteTriggerSettings.El disparador tiene dos partes:
  1. Una función de lógica de resolver — declarada con serverRouteTriggerSettings — se ejecuta en tu espacio de trabajo propietario (el espacio de trabajo que es propietario del registro de la aplicación). Inspecciona la solicitud entrante y devuelve { workspaceId, targetLogicFunctionUniversalIdentifier, payload? }, eligiendo tanto el espacio de trabajo de destino como la función de destino. El resolver es el único punto de autorización: la URL solo lleva el identificador del resolver. Este es el lugar preferido para verificar las firmas de las solicitudes: el resolver se ejecuta antes de cualquier efecto secundario, tiene acceso al rawBody original y a los encabezados reenviados, y puede rechazar sin tocar nunca el destino.
  2. Luego, una función de lógica de destino — una función de lógica normal por espacio de trabajo — se ejecuta en el espacio de trabajo resuelto con el payload devuelto por el resolver (o el payload original de la solicitud si el resolver no lo transformó). Su valor de retorno se convierte en la respuesta HTTP.
src/logic-functions/resolve-server-route.logic-function.ts
src/logic-functions/handle-invoice-paid.logic-function.ts
El endpoint es accesible en:
El identificador es el universalIdentifier del resolver de tu manifiesto. Registra esa URL con el proveedor.
La aplicación debe reclamarse e instalarse en el espacio de trabajo propietario. Dado que el resolver se ejecuta en el espacio de trabajo propietario (el espacio de trabajo que es propietario del registro de la aplicación), un desencadenador de ruta de servidor solo funciona una vez que la aplicación ha sido reclamada, es decir, tiene un espacio de trabajo propietario, y esa aplicación está instalada en el espacio de trabajo propietario. Hasta que ambas condiciones se cumplan, el resolver no tiene dónde ejecutarse, por lo que la ruta no puede despacharse. Por lo tanto, una aplicación que expone una función lógica serverRouteTriggerSettings no puede figurar en el marketplace hasta que haya sido reclamada e instalada en su espacio de trabajo propietario.
Contrato del resolver. El tipo LogicFunctionConfig del SDK aplica esto en tiempo de compilación: tan pronto como configuras serverRouteTriggerSettings, tu handler se ve obligado a devolver { workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object } (o un Promise de esto). El workspaceId debe ser un espacio de trabajo donde la función de destino esté instalada; de lo contrario, la solicitud se rechaza con 404.
La verificación de la firma es tu responsabilidad: verifica en el resolver. La plataforma no verifica las firmas de las solicitudes. El resolver es el lugar recomendado para hacerlo: se ejecuta primero, con acceso a event.rawBody y a los encabezados que incluiste en forwardedRequestHeaders, y un error lanzado (o cualquier workspaceId que no coincida) detiene el despacho antes de que se invoque el destino. Si en cambio trasladas la verificación al destino, el destino debe tener cuidado de no perder rawBody y los encabezados; es decir, el resolver no debe devolver un payload. Verifica siempre antes de cualquier efecto secundario y usa una comparación en tiempo constante.
Para las firmas de solicitudes, la mayoría de los proveedores firman con HMAC-SHA256; las partes que difieren son el nombre del encabezado, la codificación del digest y la cadena firmada del payload. Algunos ejemplos:El ejemplo de resolver anterior ya muestra el flujo HMAC-SHA256 de GitHub; adapta el nombre del encabezado, la codificación del digest y la cadena firmada del payload según el proveedor con el que te estés integrando.
El destino se ejecuta de forma sincrónica y su valor devuelto se convierte en la respuesta HTTP, por lo que quienes llaman ven tu código de estado y pueden reintentar cuando no sea 2xx. Mantén ambos handlers rápidos: algunos proveedores (p. ej. Slack) agotan el tiempo de espera en pocos segundos. Como el resolver es accesible como un endpoint público, protégelo con limitación de tasa en el edge.

Payload del disparador de evento de base de datos

Cuando un disparador de evento de base de datos invoca tu función de lógica, esta recibe un DatabaseEventPayload por cada registro modificado. El payload combina metadatos sobre el espacio de trabajo y el objeto de origen con el evento a nivel de registro.
La carga útil incluye:Para eliminaciones lógicas (soft deletes), .deleted sigue la estructura de estilo de actualización porque el campo deletedAt del registro cambia. Para eliminaciones permanentes, usa .destroyed.
databaseEventTriggerSettings.updatedFields filtra qué eventos de actualización activan la función. event.properties.updatedFields te indica qué campos realmente cambiaron en el evento actual.
Ejemplo de evento de creación:
Ejemplo de evento de actualización:
Ejecutar solo en actualizaciones de correo electrónico:
Ejemplo de evento de eliminación:

Exponer una función como herramienta de IA o acción de flujo de trabajo

Las funciones lógicas pueden exponerse en dos ámbitos, cada uno con su propio disparador:
  • toolTriggerSettings — hace que la función sea descubrible por las funciones de IA de Twenty (chat, MCP, llamadas a funciones). Usa el JSON Schema estándar, el formato que los LLM entienden de forma nativa.
  • workflowActionTriggerSettings — hace que la función aparezca como un paso en el constructor visual de flujos de trabajo. Usa el InputSchema completo de Twenty para que el constructor pueda renderizar editores de campos adecuados, selectores de variables y etiquetas.
Una función puede optar por una, por la otra o por ambas. Se ubican junto a cronTriggerSettings, databaseEventTriggerSettings y httpRouteTriggerSettings — mismo patrón, misma estructura.
Relación con la acción Code del flujo de trabajo. La acción integrada Code en el generador de flujos de trabajo es en sí misma una función lógica: Twenty crea una por cada paso de Code y expone su editor en línea. workflowActionTriggerSettings es la forma de convertir ese código puntual en línea en una acción reutilizable: defines la función una vez en tu aplicación y se vuelve seleccionable en cualquier flujo de trabajo, en lugar de copiarla y pegarla en cada paso de Code. Consulta la acción Code en la guía del usuario para ver la perspectiva del usuario final.
src/logic-functions/enrich-company.logic-function.ts
Puntos clave:
  • Una función puede mezclar superficies — declara tanto toolTriggerSettings como workflowActionTriggerSettings para exponerla en el chat Y en el constructor de flujos de trabajo.
  • Ambos, toolTriggerSettings.inputSchema y workflowActionTriggerSettings.inputSchema, son opcionales. Cuando se omiten, el generador del manifiesto los infiere a partir del código fuente del controlador (JSON Schema para la herramienta de IA, InputSchema de Twenty para la acción de flujo de trabajo). Proporciona uno explícitamente cuando quieras un tipado más rico — por ejemplo, con campos compatibles con FieldMetadataType como CURRENCY o RELATION para el constructor de flujos de trabajo, o con campos description que el agente de IA pueda leer:
Para declarar tus parámetros una sola vez y atender ambas superficies, define un único JSON Schema (InputJsonSchema) y conviértelo para la acción de flujo de trabajo con jsonSchemaToInputSchema de twenty-sdk/logic-function. toolTriggerSettings.inputSchema usa directamente el JSON Schema, mientras que workflowActionTriggerSettings.inputSchema espera el InputSchema de Twenty:
Ejemplo completo de una acción de flujo de trabajo
workflowActionTriggerSettings acepta cuatro campos:Uniéndolo todo: una función expuesta como una acción de flujo de trabajo, con una salida declarada para que los pasos posteriores puedan hacer referencia a taskId:
src/logic-functions/enrich-company.logic-function.ts
Una vez que la aplicación está instalada, Enrich Company aparece en el selector de acciones del generador de flujos de trabajo. El generador representa companyName y domain como campos de entrada (cada uno puede extraer valores de pasos anteriores), y los pasos posteriores pueden hacer referencia a las salidas taskId y enriched de ese paso.
Escribe una buena description. Los agentes de IA dependen del campo description de la función para decidir cuándo usar la herramienta. Sé específico acerca de lo que hace la herramienta y cuándo debe invocarse.
Utilidades en tiempo de ejecución. twenty-sdk/utils vuelve a exportar pequeñas utilidades en tiempo de ejecución para que los handlers nunca importen directamente desde twenty-shared. Por ejemplo, isDefined(value) devuelve false tanto para null como para undefined — utilízalo para acotar de forma segura las entradas opcionales de los handlers, que pueden llegar como null en tiempo de ejecución incluso cuando están tipadas como T | undefined:
Hooks de instalación — los controladores de preinstalación y postinstalación — comparten este entorno de ejecución, pero se declaran con sus propias funciones ‘define’ y no aceptan configuraciones de disparador. Consulta Hooks de instalación para definePreInstallLogicFunction y definePostInstallLogicFunction.

Clientes de API tipados (twenty-client-sdk)

El paquete twenty-client-sdk proporciona dos clientes GraphQL tipados para interactuar con la API de Twenty desde tus funciones de lógica y componentes de frontend.
CoreApiClient es el cliente principal para consultar y mutar datos del espacio de trabajo. Se genera a partir del esquema de tu espacio de trabajo durante yarn twenty dev o yarn twenty dev:build, por lo que está completamente tipado para coincidir con tus objetos y campos.
El cliente usa una sintaxis de conjunto de selección: pasa true para incluir un campo, usa __args para los argumentos y anida objetos para las relaciones. Obtienes autocompletado completo y verificación de tipos basados en el esquema de tu espacio de trabajo.
CoreApiClient se genera en tiempo de desarrollo/compilación. Si intentas usarlo sin ejecutar primero yarn twenty dev o yarn twenty dev:build, lanzará un error. La generación ocurre automáticamente: la CLI inspecciona el esquema GraphQL de tu espacio de trabajo y genera un cliente tipado usando @genql/cli.

Uso de CoreSchema para anotaciones de tipos

CoreSchema proporciona tipos de TypeScript que coinciden con los objetos de tu espacio de trabajo; útil para tipar el estado de componentes o parámetros de funciones:
MetadataApiClient viene preconstruido con el SDK (no se requiere generación). Consulta el endpoint /metadata para la configuración del espacio de trabajo, las aplicaciones y las cargas de archivos.

Subir archivos

El MetadataApiClient incluye un método uploadFile para adjuntar archivos a los campos de tipo archivo:
Puntos clave:
  • Utiliza el universalIdentifier del campo (no su ID específico del espacio de trabajo), por lo que tu código de carga funciona en cualquier espacio de trabajo donde esté instalada tu aplicación.
  • La url devuelta es una URL firmada que puedes usar para acceder al archivo cargado.
Cuando tu código se ejecuta en Twenty (funciones de lógica o componentes de frontend), la plataforma inyecta credenciales como variables de entorno:
  • TWENTY_API_URL — URL base de la API de Twenty
  • TWENTY_APP_ACCESS_TOKEN — Token de corta duración con alcance al rol de función predeterminado de tu aplicación
No necesitas pasar estas credenciales a los clientes — leen de process.env automáticamente. Los permisos de la clave de API están determinados por el rol declarado con defineApplicationRole() (o referenciado mediante defaultRoleUniversalIdentifier en application-config.ts).