--- title: Applications Twenty description: Créez et gérez les personnalisations Twenty sous forme de code. --- Les applications sont actuellement en phase de test alpha. La fonctionnalité est fonctionnelle mais encore en évolution. ## Que sont les applications ? Les applications vous permettent de créer et de gérer des personnalisations Twenty **sous forme de code**. Au lieu de tout configurer via l’interface utilisateur, vous définissez votre modèle de données et des fonctions sans serveur dans le code — ce qui accélère la création, la maintenance et le déploiement sur plusieurs espaces de travail. **Ce que vous pouvez faire aujourd'hui:** * Définir des objets et des champs personnalisés sous forme de code (modèle de données géré) * Créer des fonctions sans serveur avec des déclencheurs personnalisés * Déployer la même application sur plusieurs espaces de travail **Bientôt disponible :** * Mises en page et composants d’interface utilisateur personnalisés ## Prérequis * Node.js 24+ et Yarn 4 * Un espace de travail Twenty et une clé API (créez-en une sur https://app.twenty.com/settings/api-webhooks) ## Prise en main Créez une nouvelle application avec l’outil d’amorçage officiel, puis authentifiez-vous et commencez à développer : ```bash filename="Terminal" # Générez une nouvelle application npx create-twenty-app@latest my-twenty-app cd my-twenty-app # Si vous n'utilisez pas yarn@4 corepack enable yarn install # Authentifiez-vous avec votre clé API (une invite s'affichera) yarn auth:login # Démarrez le mode développement : synchronise automatiquement les modifications locales avec votre espace de travail yarn app:dev ``` À partir d'ici, vous pouvez : ```bash filename="Terminal" # Add a new entity to your application (guided) yarn app:create-entity # Generate a typed Twenty client and workspace entity types yarn app:generate # Run a one‑time sync (instead of watch mode) yarn app:sync # Watch your application's functions logs yarn function:logs # Execute a function by name yarn function:execute -n my-function -p '{"name": "test"}' # Uninstall the application from the current workspace yarn app:uninstall # Display commands' help yarn app:help},{ ``` Voir aussi : les pages de référence CLI pour [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) et [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). ## Structure du projet (générée) Lorsque vous exécutez `npx create-twenty-app@latest my-twenty-app`, l’outil de scaffolding : * Copie une application de base minimale dans `my-twenty-app/` * Ajoute une dépendance locale à `twenty-sdk` et la configuration Yarn 4 * Crée des fichiers de configuration et des scripts reliés à la CLI `twenty` * Génère une configuration d’application par défaut et un rôle de fonction par défaut Une application nouvellement générée ressemble à ceci : ```text filename="my-twenty-app/" my-twenty-app/ package.json yarn.lock .gitignore .nvmrc .yarnrc.yml .yarn/ install-state.gz eslint.config.mjs tsconfig.json README.md src/ app/ application.config.ts # Obligatoire - configuration principale de l'application default-function.role.ts # Rôle par défaut pour les fonctions serverless // vos entités (*.object.ts, *.function.ts, *.role.ts) utils/ # Optionnel - implémentations de gestionnaires et utilitaires ``` ### Convention plutôt que configuration Les applications adoptent une approche **convention plutôt que configuration** où les entités sont détectées par leur suffixe de fichier. Cela permet une organisation flexible dans le dossier `src/app/` : | Suffixe de fichier | Type d’entité | | ------------------ | ------------------------------------- | | `*.object.ts` | Définitions d’objets personnalisés | | `*.function.ts` | Définitions de fonctions sans serveur | | `*.role.ts` | Définitions de rôles | ### Structures de dossiers prises en charge Vous pouvez organiser vos entités selon l’un des schémas suivants : **Traditionnelle (par type) :** ```text src/app/ ├── application.config.ts ├── objects/ │ └── postCard.object.ts ├── functions/ │ └── createPostCard.function.ts └── roles/ └── admin.role.ts ``` **Par fonctionnalité :** ```text src/app/ ├── application.config.ts └── post-card/ ├── postCard.object.ts ├── createPostCard.function.ts └── postCardAdmin.role.ts ``` **À plat :** ```text src/app/ ├── application.config.ts ├── postCard.object.ts ├── createPostCard.function.ts └── admin.role.ts ``` Dans les grandes lignes : * **package.json** : Déclare le nom de l’application, la version, les moteurs (Node 24+, Yarn 4), et ajoute `twenty-sdk` ainsi que des scripts comme `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` et `auth` qui délèguent à la CLI locale `twenty`. * **.gitignore** : Ignore les artefacts courants tels que `node_modules`, `.yarn`, `generated/` (client typé), `dist/`, `build/`, les dossiers de couverture, les fichiers journaux et les fichiers `.env*`. * **yarn.lock**, **.yarnrc.yml**, **.yarn/** : Verrouillent et configurent la chaîne d’outils Yarn 4 utilisée par le projet. * **.nvmrc** : Fige la version de Node.js attendue par le projet. * **eslint.config.mjs** et **tsconfig.json** : Fournissent la configuration de linting et TypeScript pour les sources TypeScript de votre application. * **README.md** : Un bref README à la racine de l’application avec des instructions de base. * **src/app/** : L’endroit principal où vous définissez votre application sous forme de code : * `application.config.ts` : Configuration globale de votre application (métadonnées et liaisons d’exécution). Voir « Configuration de l’application » ci-dessous. * `*.role.ts` : Définitions de rôles utilisées par vos fonctions sans serveur. Voir « Rôle de fonction par défaut » ci-dessous. * `*.object.ts` : Définitions d’objets personnalisés. * `*.function.ts` : Définitions de fonctions sans serveur. * **src/utils/** : Dossier facultatif pour les implémentations de gestionnaires et utilitaires. Des commandes ultérieures ajouteront d’autres fichiers et dossiers : * `yarn app:generate` créera un dossier `generated/` (client Twenty typé + types de l’espace de travail). * `yarn app:create-entity` ajoutera des fichiers de définition d’entité sous `src/app/` pour vos objets, fonctions ou rôles personnalisés. l ## Authentification La première fois que vous exécutez `yarn auth:login`, il vous sera demandé : * URL de l’API (par défaut http://localhost:3000 ou votre profil d’espace de travail actuel) * Clé API Vos identifiants sont stockés par utilisateur dans `~/.twenty/config.json`. Vous pouvez gérer plusieurs profils et basculer entre eux. ### Gestion des espaces de travail ```bash filename="Terminal" # Login interactively (recommended) yarn auth:login # Login to a specific workspace profile yarn auth:login --workspace my-custom-workspace # List all configured workspaces yarn auth:list # Switch the default workspace (interactive) yarn auth:switch # Switch to a specific workspace yarn auth:switch production # Check current authentication status yarn auth:status ``` Une fois que vous avez changé d'espace de travail avec `auth:switch`, toutes les commandes suivantes utiliseront cet espace de travail par défaut. Vous pouvez toujours le surcharger temporairement avec `--workspace `. ## Utiliser les ressources du SDK (types et configuration) Le paquet twenty-sdk fournit des blocs de construction typés et des fonctions utilitaires que vous utilisez dans votre application. Voici les éléments clés que vous manipulerez le plus souvent. ### Fonctions utilitaires Le SDK fournit quatre fonctions utilitaires avec validation intégrée pour définir les entités de votre application : | Fonction | Objectif | | ------------------ | ---------------------------------------------------------- | | `defineApp()` | Configurer les métadonnées de l’application | | `defineObject()` | Définir des objets personnalisés avec des champs | | `defineFunction()` | Définir des fonctions sans serveur avec des gestionnaires | | `defineRole()` | Configurer les autorisations de rôle et l’accès aux objets | Ces fonctions valident votre configuration à l’exécution et offrent une meilleure autocomplétion IDE et une sécurité de typage accrue. ### Définir des objets Les objets personnalisés décrivent à la fois le schéma et le comportement des enregistrements dans votre espace de travail. Utilisez `defineObject()` pour définir des objets avec validation intégrée : ```typescript // src/app/postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { DRAFT = 'DRAFT', SENT = 'SENT', DELIVERED = 'DELIVERED', RETURNED = 'RETURNED', } export default defineObject({ universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', nameSingular: 'postCard', namePlural: 'postCards', labelSingular: 'Post Card', labelPlural: 'Post Cards', description: 'A post card object', icon: 'IconMail', fields: [ { universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', name: 'content', type: FieldType.TEXT, label: 'Content', description: "Postcard's content", icon: 'IconAbc', }, { universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', name: 'recipientName', type: FieldType.FULL_NAME, label: 'Recipient name', icon: 'IconUser', }, { universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', name: 'recipientAddress', type: FieldType.ADDRESS, label: 'Recipient address', icon: 'IconHome', }, { universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', name: 'status', type: FieldType.SELECT, label: 'Status', icon: 'IconSend', defaultValue: `'${PostCardStatus.DRAFT}'`, options: [ { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, ], }, { universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', name: 'deliveredAt', type: FieldType.DATE_TIME, label: 'Delivered at', icon: 'IconCheck', isNullable: true, defaultValue: null, }, ], }); ``` Points clés : * Utilisez `defineObject()` pour une validation intégrée et une meilleure prise en charge par l’IDE. * Le `universalIdentifier` doit être unique et stable entre les déploiements. * Chaque champ nécessite un `name`, un `type`, un `label` et son propre `universalIdentifier` stable. * Le tableau `fields` est facultatif — vous pouvez définir des objets sans champs personnalisés. * Vous pouvez générer de nouveaux objets avec `yarn app:create-entity`, qui vous guide à travers le nommage, les champs et les relations. **Les champs de base sont créés automatiquement.** Lorsque vous définissez un objet personnalisé, Twenty ajoute automatiquement des champs standard tels que `name`, `createdAt`, `updatedAt`, `createdBy`, `position` et `deletedAt`. Vous n'avez pas besoin de les définir dans votre tableau `fields` — ajoutez uniquement vos champs personnalisés. Vous pouvez également définir des objets à l’aide de décorateurs TypeScript. Cette approche utilise une syntaxe basée sur les classes avec les décorateurs `@Object`, `@Field` et `@Relation` : ```typescript import { type AddressField, Field, FieldType, type FullNameField, Object, OnDeleteAction, Relation, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, } from 'twenty-sdk'; import { type Note } from '../../generated'; @Object({ universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', nameSingular: 'postCard', namePlural: 'postCards', labelSingular: 'Post card', labelPlural: 'Post cards', description: 'A post card object', icon: 'IconMail', }) export class PostCard { @Field({ universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', type: FieldType.TEXT, label: 'Content', description: "Postcard's content", icon: 'IconAbc', }) content: string; @Relation({ universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe', type: RelationType.ONE_TO_MANY, label: 'Notes', icon: 'IconComment', inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, onDelete: OnDeleteAction.CASCADE, }) notes: Note[]; } ``` Remarque : l’approche avec décorateurs requiert `experimentalDecorators` dans votre configuration TypeScript. ### Configuration de l’application (application.config.ts) Chaque application dispose d’un seul fichier `application.config.ts` qui décrit : * **Identité de l’application** : identifiants, nom d’affichage et description. * **Exécution des fonctions** : le rôle utilisé pour les autorisations. * **Variables (facultatif)** : paires clé–valeur exposées à vos fonctions en tant que variables d’environnement. Utilisez `defineApp()` pour définir la configuration de votre application : ```typescript // src/app/application.config.ts import { defineApp } from 'twenty-sdk'; import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role'; export default defineApp({ universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', displayName: 'My Twenty App', description: 'My first Twenty app', icon: 'IconWorld', applicationVariables: { DEFAULT_RECIPIENT_NAME: { universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', description: 'Default recipient name for postcards', value: 'Jane Doe', isSecret: false, }, }, functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER, }); ``` Notes : * Les champs `universalIdentifier` sont des identifiants déterministes que vous possédez ; générez-les une fois et conservez-les stables entre les synchronisations. * `applicationVariables` deviennent des variables d’environnement pour vos fonctions (par exemple, `DEFAULT_RECIPIENT_NAME` est disponible sous `process.env.DEFAULT_RECIPIENT_NAME`). * `functionRoleUniversalIdentifier` doit correspondre au rôle que vous définissez dans votre fichier `*.role.ts` (voir ci-dessous). #### Rôles et autorisations Les applications peuvent définir des rôles qui encapsulent des autorisations sur les objets et actions de votre espace de travail. Le champ `functionRoleUniversalIdentifier` dans `application.config.ts` désigne le rôle par défaut utilisé par les fonctions sans serveur de votre application. * La clé API d’exécution injectée sous `TWENTY_API_KEY` est dérivée de ce rôle de fonction par défaut. * Le client typé sera limité aux autorisations accordées à ce rôle. * Appliquez le principe du moindre privilège : créez un rôle dédié avec uniquement les autorisations nécessaires à vos fonctions, puis référencez son identifiant universel. ##### Rôle de fonction par défaut (\*.role.ts) Lorsque vous générez une nouvelle application, la CLI crée également un fichier de rôle par défaut. Utilisez `defineRole()` pour définir des rôles avec validation intégrée : ```typescript // src/app/default-function.role.ts import { defineRole, PermissionFlag } from 'twenty-sdk'; export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER = 'b648f87b-1d26-4961-b974-0908fd991061'; export default defineRole({ universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER, label: 'Default function role', description: 'Default role for function Twenty client', canReadAllObjectRecords: false, canUpdateAllObjectRecords: false, canSoftDeleteAllObjectRecords: false, canDestroyAllObjectRecords: false, canUpdateAllSettings: false, canBeAssignedToAgents: false, canBeAssignedToUsers: false, canBeAssignedToApiKeys: false, objectPermissions: [ { objectNameSingular: 'postCard', canReadObjectRecords: true, canUpdateObjectRecords: true, canSoftDeleteObjectRecords: false, canDestroyObjectRecords: false, }, ], fieldPermissions: [ { objectNameSingular: 'postCard', fieldName: 'content', canReadFieldValue: false, canUpdateFieldValue: false, }, ], permissionFlags: [PermissionFlag.APPLICATIONS], }); ``` Le `universalIdentifier` de ce rôle est ensuite référencé dans `application.config.ts` en tant que `functionRoleUniversalIdentifier`. En d’autres termes : * **\*.role.ts** définit ce que le rôle de fonction par défaut peut faire. * **application.config.ts** pointe vers ce rôle afin que vos fonctions héritent de ses autorisations. Notes : * Partez du rôle généré, puis restreignez-le progressivement en suivant le principe du moindre privilège. * Remplacez `objectPermissions` et `fieldPermissions` par les objets/champs dont vos fonctions ont besoin. * `permissionFlags` contrôlent l’accès aux capacités au niveau de la plateforme. Gardez-les au minimum ; n’ajoutez que ce dont vous avez besoin. * Voir un exemple fonctionnel dans l’application Hello World : [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). ### Configuration et point d’entrée des fonctions sans serveur Chaque fichier de fonction utilise `defineFunction()` pour exporter une configuration avec un gestionnaire et des déclencheurs facultatifs. Utilisez le suffixe de fichier `*.function.ts` pour la détection automatique. ```typescript // src/app/createPostCard.function.ts import { defineFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; import Twenty, { type Person } from '../../generated'; const handler = async ( params: | RoutePayload | DatabaseEventPayload> | CronPayload, ) => { const client = new Twenty(); // generated typed client const name = 'name' in params.queryStringParameters ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' : 'Hello world'; const result = await client.mutation({ createPostCard: { __args: { data: { name } }, id: true, name: true, }, }); return result; }; export default defineFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'create-new-post-card', timeoutSeconds: 2, handler, triggers: [ // Public HTTP route trigger '/s/post-card/create' { universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', type: 'route', path: '/post-card/create', httpMethod: 'GET', isAuthRequired: false, }, // Cron trigger (CRON pattern) { universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', type: 'cron', pattern: '0 0 1 1 *', }, // Database event trigger { universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', type: 'databaseEvent', eventName: 'person.updated', updatedFields: ['name'], }, ], }); ``` Types de déclencheurs courants : * **route** : Expose votre fonction sur un chemin et une méthode HTTP **sous l’endpoint `/s/`** : > p. ex. `path: '/post-card/create',` -> appel sur `/s/post-card/create` * **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` Notes : * Le tableau `triggers` est facultatif. Les fonctions sans déclencheurs peuvent servir de fonctions utilitaires appelées par d’autres fonctions. * Vous pouvez combiner plusieurs types de déclencheurs dans une seule fonction. ### Charge utile du déclencheur de route **Changement incompatible (v1.16, janvier 2026):** Le format de la charge utile du déclencheur de route a changé. Avant la v1.16, les paramètres de requête, les paramètres de chemin et le corps de la requête étaient envoyés directement en tant que charge utile. À partir de la v1.16, ils sont imbriqués dans un objet `RoutePayload` structuré. **Avant la v1.16 :** ```typescript const handler = async (params) => { const { param1, param2 } = params; // Direct access }; ``` **Après la v1.16 :** ```typescript const handler = async (event: RoutePayload) => { const { param1, param2 } = event.body; // Access via .body const { queryParam } = event.queryStringParameters; const { id } = event.pathParameters; }; ``` **Pour migrer les fonctions existantes :** Mettez à jour votre gestionnaire pour déstructurer à partir de `event.body`, `event.queryStringParameters` ou `event.pathParameters` plutôt que directement à partir de l'objet params. Lorsqu'un déclencheur de route appelle votre fonction, elle reçoit un objet `RoutePayload` qui suit le format AWS HTTP API v2. Importez le type depuis `twenty-sdk` : ```typescript import { defineFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { // Access request data const { headers, queryStringParameters, pathParameters, body } = event; // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; }; ``` Le type `RoutePayload` a la structure suivante : | Nom de la propriété | Type | Description | | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- | | `headers` | `Record` | En-têtes HTTP (uniquement ceux répertoriés dans `forwardedRequestHeaders`) | | `queryStringParameters` | `Record` | Paramètres de la chaîne de requête (plusieurs valeurs séparées par des virgules) | | `pathParameters` | `Record` | Paramètres de chemin extraits du modèle de route (p. ex., `/users/:id` → `{ id: '123' }`) | | `corps du message` | `object \| null` | Corps de la requête analysé (JSON) | | `isBase64Encoded` | `booléen` | Indique si le corps est encodé en base64 | | `requestContext.http.method` | `string` | Méthode HTTP (GET, POST, PUT, PATCH, DELETE) | | `requestContext.http.path` | `string` | Chemin de la requête brut | ### Transfert des en-têtes HTTP Par défaut, les en-têtes HTTP des requêtes entrantes ne sont pas transmis à votre fonction sans serveur pour des raisons de sécurité. Pour accéder à des en-têtes spécifiques, listez-les explicitement dans le tableau `forwardedRequestHeaders` : ```typescript export default defineFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, triggers: [ { universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', type: 'route', path: '/webhook', httpMethod: 'POST', isAuthRequired: false, forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], }, ], }); ``` Dans votre gestionnaire, vous pouvez ensuite accéder à ces en-têtes : ```typescript const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; // Validate webhook signature... return { received: true }; }; ``` Les noms d'en-têtes sont normalisés en minuscules. Accédez-y en utilisant des clés en minuscules (par exemple, `event.headers['content-type']`). Vous pouvez créer de nouvelles fonctions de deux façons : * **Générée** : Exécutez `yarn app:create-entity` et choisissez l’option pour ajouter une nouvelle fonction. Cela génère un fichier de démarrage avec un gestionnaire et une configuration. * **Manuelle** : Créez un nouveau fichier `*.function.ts` et utilisez `defineFunction()`, en suivant le même modèle. ### Client typé généré Exécutez yarn app:generate pour créer un client typé local dans generated/ basé sur le schéma de votre espace de travail. Utilisez-le dans vos fonctions : ```typescript import Twenty from './generated'; const client = new Twenty(); const { me } = await client.query({ me: { id: true, displayName: true } }); ``` Le client est régénéré par `yarn app:generate`. Relancez la commande après avoir modifié vos objets et exécuté `yarn app:sync`, ou lors de l’intégration à un nouvel espace de travail. #### Identifiants d’exécution dans les fonctions sans serveur Lorsque votre fonction s’exécute sur Twenty, la plateforme injecte des identifiants sous forme de variables d’environnement avant l’exécution de votre code : * `TWENTY_API_URL` : URL de base de l’API Twenty ciblée par votre application. * `TWENTY_API_KEY` : Clé de courte durée limitée au rôle de fonction par défaut de votre application. Notes: * Vous n’avez pas besoin de passer l’URL ou la clé API au client généré. Il lit `TWENTY_API_URL` et `TWENTY_API_KEY` depuis process.env à l’exécution. * Les autorisations de la clé API sont déterminées par le rôle référencé dans votre `application.config.ts` via `functionRoleUniversalIdentifier`. Il s’agit du rôle par défaut utilisé par les fonctions sans serveur de votre application. * Les applications peuvent définir des rôles pour appliquer le principe du moindre privilège. N’accordez que les autorisations dont vos fonctions ont besoin, puis faites pointer `functionRoleUniversalIdentifier` vers l’identifiant universel de ce rôle. ### Exemple Hello World Découvrez un exemple minimal de bout en bout qui démontre des objets, des fonctions et plusieurs déclencheurs [ici](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world) : ## Configuration manuelle (sans l’outil de scaffolding) Même si nous recommandons d’utiliser `create-twenty-app` pour une expérience de démarrage optimale, vous pouvez également configurer un projet manuellement. N’installez pas la CLI globalement. Ajoutez plutôt `twenty-sdk` comme dépendance locale et reliez des scripts dans votre package.json : ```bash filename="Terminal" yarn add -D twenty-sdk ``` Ajoutez ensuite des scripts comme ceux-ci : ```json filename="package.json" { "scripts": { "auth": "twenty auth login", "generate": "twenty app generate", "dev": "twenty app dev", "sync": "twenty app sync", "uninstall": "twenty app uninstall", "logs": "twenty app logs", "create-entity": "twenty app add", "help": "twenty --help" } } ``` Vous pouvez désormais exécuter les mêmes commandes via Yarn, par exemple `yarn app:dev`, `yarn app:sync`, etc. ## Résolution des problèmes * Erreurs d’authentification : exécutez `yarn auth:login` et assurez-vous que votre clé API dispose des autorisations requises. * Impossible de se connecter au serveur : vérifiez l’URL de l’API et que le serveur Twenty est accessible. * Types ou client manquants/obsolètes : exécutez `yarn app:generate` puis `yarn app:dev`. * Le mode dev ne se synchronise pas : assurez-vous que `yarn app:dev` est en cours d’exécution et que les modifications ne sont pas ignorées par votre environnement. Canal d’aide Discord : https://discord.com/channels/1130383047699738754/1130386664812982322