Passer au contenu principal
Les fonctions logiques sont des fonctions TypeScript côté serveur qui s’exécutent sur la plateforme Twenty. Elles peuvent être déclenchées par des requêtes HTTP, des programmations cron ou des événements de base de données — et peuvent également être exposées comme des outils pour des agents d’IA.
Chaque fichier de fonction utilise defineLogicFunction() pour exporter une configuration avec un gestionnaire et des déclencheurs facultatifs.
src/logic-functions/createPostCard.logic-function.ts
Types de déclencheurs disponibles :
  • httpRoute : Expose votre fonction sur un chemin et une méthode HTTP. Dans le code de l’application, préfixez le chemin de la route avec /s/ lorsque vous utilisez RestApiClient ; l’URL déployée utilise la base injectée TWENTY_FUNCTIONS_URL (ou \<server-url>/s lorsqu’elle n’est pas définie).
Pour appeler une fonction logique déclenchée par une route depuis un composant frontal (sans interface), consultez Appeler une fonction logique.
  • cron : Exécute votre fonction selon une planification à l’aide d’une expression CRON.
  • databaseEvent: S’exécute lors des événements du cycle de vie des objets de l’espace de travail. Lorsque l’opération de l’événement est updated, des champs spécifiques à surveiller peuvent être spécifiés dans le tableau updatedFields. S’il est laissé indéfini ou vide, toute mise à jour déclenchera la fonction.
p. ex. person.updated, *.created, company.*
  • serverRoute : expose une seule route HTTP à portée d’enregistrement. Une fonction de résolution (déclarée avec serverRouteTriggerSettings) s’exécute dans l’espace de travail propriétaire et renvoie l’espace de travail cible ET la fonction logique cible vers laquelle acheminer la requête ; la plateforme exécute ensuite cette fonction cible et renvoie sa réponse. Voir déclencheur de route serveur.
Vous pouvez également exécuter manuellement une fonction à l’aide de la CLI :
Vous pouvez consulter les journaux avec :

Charge utile du déclencheur de route

Lorsqu’un déclencheur de route invoque votre fonction logique, elle reçoit un objet RoutePayload qui suit le format AWS HTTP API v2. Importez le type RoutePayload depuis twenty-sdk/logic-function :
Le type RoutePayload a la structure suivante :

forwardedRequestHeaders

Par défaut, les en-têtes HTTP des requêtes entrantes ne sont pas transmis à votre fonction logique pour des raisons de sécurité. Pour accéder à des en-têtes spécifiques, listez-les dans le tableau forwardedRequestHeaders :
Dans votre gestionnaire, accédez aux en-têtes transférés comme ceci :
Les noms d’en-têtes sont normalisés en minuscules. Accédez-y en utilisant des clés en minuscules (p. ex., event.headers['content-type']).

Réponse HTTP personnalisée

Par défaut, le retour d’une valeur simple depuis votre gestionnaire l’envoie en réponse 200 (JSON pour les objets, text/plain pour les chaînes). Pour contrôler le code d’état et les en-têtes de la réponse, retournez un objet Response depuis twenty-sdk/logic-function :
Pour des raisons de sécurité, les en-têtes de réponse sont restreints à une liste d’autorisation. Tout en-tête qui ne figure pas dans la liste (par exemple Set-Cookie, les en-têtes CORS tels que Access-Control-Allow-Origin, ou les en-têtes personnalisés X-*) est silencieusement supprimé avant l’envoi de la réponse. Les en-têtes de réponse autorisés sont :
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
Le code d’état doit être un code d’état HTTP valide (compris entre 100 et 599). Les noms des en-têtes de réponse sont comparés sans tenir compte de la casse.

Déclencheur de route serveur

httpRouteTriggerSettings expose une fonction sous /s/ et résout l’espace de travail à partir de l’hôte de la requête — ce qui fonctionne lorsque chaque espace de travail a son propre domaine. Les fournisseurs tiers, en revanche, envoient les événements de chaque locataire vers une URL. Dans ce cas, utilisez serverRouteTriggerSettings.Le déclencheur comporte deux parties :
  1. Une fonction de logique de résolution — déclarée avec serverRouteTriggerSettings — s’exécute dans votre espace de travail propriétaire (l’espace de travail qui possède l’enregistrement de l’application). Elle inspecte la requête entrante et retourne { workspaceId, targetLogicFunctionUniversalIdentifier, payload? }, en choisissant à la fois l’espace de travail cible et la fonction cible. Le résolveur est le point d’autorisation unique — l’URL transporte uniquement l’identifiant du résolveur. C’est l’endroit privilégié pour vérifier les signatures des requêtes : le résolveur s’exécute avant tout effet de bord, a accès au rawBody original et aux en-têtes transmis, et peut rejeter la requête sans jamais toucher la cible.
  2. Une fonction de logique cible — une fonction de logique classique par espace de travail — s’exécute ensuite dans l’espace de travail résolu avec la charge utile renvoyée par le résolveur (ou la charge utile originale de la requête si le résolveur ne l’a pas transformée). Sa valeur de retour devient la réponse HTTP.
src/logic-functions/resolve-server-route.logic-function.ts
src/logic-functions/handle-invoice-paid.logic-function.ts
Le point de terminaison est accessible à l’adresse :
L’identifiant est le universalIdentifier du résolveur issu de votre manifeste. Enregistrez cette URL auprès du fournisseur.
L’application doit être revendiquée et installée sur son espace de travail propriétaire. Comme le résolveur s’exécute dans l’espace de travail propriétaire (l’espace de travail qui détient l’enregistrement de l’application), un déclencheur de route serveur ne fonctionne que lorsque l’application a été revendiquée — c’est‑à‑dire qu’elle possède un espace de travail propriétaire — et que cette application est installée sur l’espace de travail propriétaire. Tant que ces deux conditions ne sont pas remplies, le résolveur n’a nulle part où s’exécuter, donc la route ne peut pas être envoyée. Une application qui expose une fonction logique serverRouteTriggerSettings ne peut donc pas être répertoriée sur la place de marché tant qu’elle n’a pas été revendiquée et installée sur son espace de travail propriétaire.
Contrat du résolveur. Le type LogicFunctionConfig du SDK impose cela à la compilation : dès que vous définissez serverRouteTriggerSettings, votre gestionnaire est contraint de retourner { workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object } (ou une Promise de cette valeur). Le workspaceId doit être celui d’un espace de travail où la fonction cible est installée, sinon la requête est rejetée avec un 404.
La vérification de la signature est de votre responsabilité — effectuez-la dans le résolveur. La plateforme ne vérifie pas les signatures des requêtes. Le résolveur est l’endroit recommandé pour le faire : il s’exécute en premier, avec accès à event.rawBody et aux en-têtes que vous avez listés dans forwardedRequestHeaders, et une erreur levée (ou tout workspaceId ne correspondant pas) interrompt la distribution avant que la cible ne soit invoquée. Si, à la place, vous repoussez la vérification vers la cible, celle-ci doit faire attention à ne pas perdre rawBody et les en-têtes — c’est-à-dire que le résolveur ne doit pas retourner de payload. Vérifiez toujours avant tout effet de bord et utilisez une comparaison en temps constant.
Pour les signatures de requêtes, la plupart des fournisseurs signent avec HMAC-SHA256 ; les éléments qui diffèrent sont le nom de l’en-tête, l’encodage de l’empreinte et la chaîne de la charge utile signée. Quelques exemples :L’exemple de résolveur ci-dessus montre déjà le flux GitHub HMAC-SHA256 — adaptez le nom de l’en-tête, l’encodage de l’empreinte et la chaîne de la charge utile signée en fonction du fournisseur avec lequel vous vous intégrez.
La cible s’exécute de manière synchrone et sa valeur de retour devient la réponse HTTP, de sorte que les appelants voient votre code d’état et peuvent réessayer en cas de réponse non-2xx. Gardez les deux gestionnaires rapides — certains fournisseurs (par ex. Slack) expirent au bout de quelques secondes. Comme le résolveur est accessible en tant que point de terminaison public, protégez-le avec une limitation de débit à votre périphérie.

Charge utile du déclencheur d’événement de base de données

Lorsqu’un déclencheur d’événement de base de données appelle votre fonction logique, celle-ci reçoit un DatabaseEventPayload par enregistrement modifié. La charge utile combine les métadonnées concernant l’espace de travail et l’objet source avec l’événement au niveau de l’enregistrement.
La charge utile inclut :Pour les suppressions logiques (soft deletes), .deleted suit la structure de type mise à jour, car le champ deletedAt de l’enregistrement change. Pour les suppressions permanentes, utilisez .destroyed.
databaseEventTriggerSettings.updatedFields filtre les événements de mise à jour qui déclenchent la fonction. event.properties.updatedFields indique quels champs ont réellement changé pour l’événement actuel.
Exemple d’événement de création :
Exemple d’événement de mise à jour :
Déclencher uniquement lors des mises à jour de l’adresse e-mail :
Exemple d’événement de destruction :

Exposer une fonction en tant qu’outil d’IA ou en tant qu’action de workflow

Les fonctions logiques peuvent être exposées sur deux surfaces, chacune avec son propre déclencheur :
  • toolTriggerSettings — rend la fonction découvrable par les fonctionnalités d’IA de Twenty (chat, MCP, appel de fonctions). Utilise le schéma JSON standard, le format que les LLM comprennent nativement.
  • workflowActionTriggerSettings — fait apparaître la fonction comme une étape dans le concepteur visuel de workflows. Utilise le InputSchema riche de Twenty afin que le concepteur puisse afficher des éditeurs de champs appropriés, des sélecteurs de variables et des libellés.
Une fonction peut opter pour l’un, l’autre ou les deux. Elles côtoient cronTriggerSettings, databaseEventTriggerSettings et httpRouteTriggerSettings — même modèle, même structure.
Lien avec l’action Code du workflow. L’action Code intégrée dans le générateur de workflows est elle-même une fonction logique — Twenty en crée une pour chaque étape Code et affiche son éditeur en ligne. workflowActionTriggerSettings est la manière de transformer ce code ponctuel en ligne en une action réutilisable : définissez la fonction une fois dans votre application et elle devient sélectionnable dans n’importe quel workflow, au lieu d’être copiée-collée dans chaque étape Code. Voir l’action Code dans le guide utilisateur pour la vue côté utilisateur final.
src/logic-functions/enrich-company.logic-function.ts
Points clés :
  • Une fonction peut mélanger les surfaces — déclarez à la fois toolTriggerSettings et workflowActionTriggerSettings pour l’exposer à la fois dans le chat ET dans le concepteur de workflows.
  • toolTriggerSettings.inputSchema et workflowActionTriggerSettings.inputSchema sont tous deux facultatifs. Lorsqu’ils sont omis, le générateur de manifeste les déduit à partir du code source du gestionnaire (schéma JSON pour l’outil d’IA, InputSchema de Twenty pour l’action de workflow). Fournissez-en un explicitement lorsque vous souhaitez un typage plus riche — par exemple, avec des champs compatibles avec FieldMetadataType comme CURRENCY ou RELATION pour le concepteur de workflows, ou avec des champs description que l’agent d’IA peut lire :
Pour déclarer vos paramètres une seule fois et les utiliser sur les deux surfaces, définissez un seul schéma JSON (InputJsonSchema) et convertissez-le pour l’action de flux de travail avec jsonSchemaToInputSchema depuis twenty-sdk/logic-function. toolTriggerSettings.inputSchema prend directement le schéma JSON, tandis que workflowActionTriggerSettings.inputSchema attend le InputSchema de Twenty :
Un exemple complet d’action de workflow
workflowActionTriggerSettings accepte quatre champs :Assembler le tout — une fonction exposée comme action de workflow, avec une sortie déclarée pour que les étapes ultérieures puissent référencer taskId :
src/logic-functions/enrich-company.logic-function.ts
Une fois l’application installée, Enrich Company apparaît dans le sélecteur d’actions du générateur de workflows. Le générateur affiche companyName et domain sous forme de champs de saisie (chacun pouvant récupérer des valeurs à partir des étapes précédentes), et les étapes en aval peuvent référencer les sorties taskId et enriched de l’étape.
Rédigez une bonne description. Les agents IA s’appuient sur le champ description de la fonction pour décider quand utiliser l’outil. Soyez précis sur ce que fait l’outil et quand il doit être appelé.
Aides à l’exécution. twenty-sdk/utils réexporte de petites aides à l’exécution afin que les gestionnaires n’importent jamais directement depuis twenty-shared. Par exemple, isDefined(value) renvoie false à la fois pour null et undefined — utilisez-le pour restreindre en toute sécurité les entrées de gestionnaire optionnelles, qui peuvent arriver sous forme de null à l’exécution même lorsqu’elles sont typées T | undefined :
Hooks d’installation — les gestionnaires de pré-installation et de post-installation — partagent ce runtime mais sont déclarés avec leurs propres fonctions define et ne prennent pas de paramètres de déclenchement. Voir hooks d’installation pour definePreInstallLogicFunction et definePostInstallLogicFunction.

Clients d’API typés (twenty-client-sdk)

Le package twenty-client-sdk fournit deux clients GraphQL typés pour interagir avec l’API Twenty depuis vos fonctions logiques et vos composants frontaux.
CoreApiClient est le client principal pour interroger et modifier les données de l’espace de travail. Il est généré à partir du schéma de votre espace de travail lors de l’exécution de yarn twenty dev ou yarn twenty dev:build, il est donc entièrement typé pour correspondre à vos objets et champs.
Le client utilise une syntaxe d’ensemble de sélection : passez true pour inclure un champ, utilisez __args pour les arguments et imbriquez des objets pour les relations. Vous bénéficiez d’une autocomplétion complète et d’une vérification de types basée sur le schéma de votre espace de travail.
CoreApiClient est généré au moment du dev/build. Si vous l’utilisez sans exécuter d’abord yarn twenty dev ou yarn twenty dev:build, une erreur est levée. La génération se fait automatiquement — la CLI inspecte le schéma GraphQL de votre espace de travail et génère un client typé à l’aide de @genql/cli.

Utiliser CoreSchema pour les annotations de type

CoreSchema fournit des types TypeScript correspondant à vos objets d’espace de travail — utile pour typer l’état des composants ou les paramètres de fonction :
MetadataApiClient est livré prêt à l’emploi avec le SDK (aucune génération requise). Il interroge le point de terminaison /metadata pour la configuration de l’espace de travail, les applications et les téléversements de fichiers.

Téléverser des fichiers

Le MetadataApiClient inclut une méthode uploadFile pour joindre des fichiers aux champs de type fichier :
Points clés :
  • Utilise le universalIdentifier du champ (et non son ID propre à l’espace de travail), de sorte que votre code de téléversement fonctionne dans tout espace de travail où votre application est installée.
  • L’url renvoyée est une URL signée que vous pouvez utiliser pour accéder au fichier téléversé.
Lorsque votre code s’exécute sur Twenty (fonctions logiques ou composants frontaux), la plateforme injecte des identifiants sous forme de variables d’environnement :
  • TWENTY_API_URL — URL de base de l’API Twenty
  • TWENTY_APP_ACCESS_TOKEN — Clé de courte durée limitée au rôle de fonction par défaut de votre application
Vous n’avez pas besoin de les transmettre aux clients — ils lisent automatiquement depuis process.env. Les autorisations de la clé API sont déterminées par le rôle déclaré avec defineApplicationRole() (ou référencé via defaultRoleUniversalIdentifier dans application-config.ts).