What Are Apps?
Apps let you build and manage Twenty customizations as code. Instead of configuring everything through the UI, you define your data model and logic functions in code — making it faster to build, maintain, and roll out to multiple workspaces. What you can do today:- Define custom objects and fields as code (managed data model)
- Build logic functions with custom triggers
- Deploy the same app across multiple workspaces
Prerequisites
- Node.js 24+ and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
Getting Started
Create a new app using the official scaffolder, then authenticate and start developing:Project structure (scaffolded)
When you runnpx create-twenty-app@latest my-twenty-app, the scaffolder:
- Copies a minimal base application into
my-twenty-app/ - Adds a local
twenty-sdkdependency and Yarn 4 configuration - Creates config files and scripts wired to the
twentyCLI - Generates a default application config and a default function role
- package.json: Declares the app name, version, engines (Node 24+, Yarn 4), and adds
twenty-sdkplus scripts likeapp:dev,app:generate,entity:add,function:logs,function:execute,app:uninstall, and authentication commands that delegate to the localtwentyCLI. - .gitignore: Ignores common artifacts such as
node_modules,.yarn,generated/(typed client),dist/,build/, coverage folders, log files, and.env*files. - yarn.lock, .yarnrc.yml, .yarn/: Lock and configure the Yarn 4 toolchain used by the project.
- .nvmrc: Pins the Node.js version expected by the project.
- eslint.config.mjs and tsconfig.json: Provide linting and TypeScript configuration for your app’s TypeScript sources.
- README.md: A short README in the app root with basic instructions.
- public/: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- src/: The main place where you define your application-as-code
Entity detection
The SDK detects entities by parsing your TypeScript files forexport default define<Entity>({...}) calls. Each entity type has a corresponding helper function exported from twenty-sdk:
| Helper function | Entity type |
|---|---|
defineObject() | Custom object definitions |
defineLogicFunction() | Logic function definitions |
defineFrontComponent() | Front component definitions |
defineRole() | Role definitions |
defineField() | Field extensions for existing objects |
File naming is flexible. Entity detection is AST-based — the SDK scans your source files for the
export default define<Entity>({...}) pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., logic-functions/, roles/) is just a convention for code organization, not a requirement.yarn app:generatewill create agenerated/folder (typed Twenty client + workspace types).yarn entity:addwill add entity definition files undersrc/for your custom objects, functions, front components, or roles.
Authentication
The first time you runyarn auth:login, you’ll be prompted for:
- API URL (defaults to http://localhost:3000 or your current workspace profile)
- API key
~/.twenty/config.json. You can maintain multiple profiles and switch between them.
Managing workspaces
auth:switch, all subsequent commands will use that workspace by default. You can still override it temporarily with --workspace <name>.
Use the SDK resources (types & config)
The twenty-sdk provides typed building blocks and helper functions you use inside your app. Below are the key pieces you’ll touch most often.Helper functions
The SDK provides helper functions for defining your app entities. As described in Entity detection, you must useexport default define<Entity>({...}) for your entities to be detected:
| Function | Purpose |
|---|---|
defineApplication() | Configure application metadata (required, one per app) |
defineObject() | Define custom objects with fields |
defineLogicFunction() | Define logic functions with handlers |
defineFrontComponent() | Define front components for custom UI |
defineRole() | Configure role permissions and object access |
defineField() | Extend existing objects with additional fields |
Defining objects
Custom objects describe both schema and behavior for records in your workspace. UsedefineObject() to define objects with built-in validation:
- Use
defineObject()for built-in validation and better IDE support. - The
universalIdentifiermust be unique and stable across deployments. - Each field requires a
name,type,label, and its own stableuniversalIdentifier. - The
fieldsarray is optional — you can define objects without custom fields. - You can scaffold new objects using
yarn entity:add, which guides you through naming, fields, and relationships.
Base fields are created automatically. When you define a custom object, Twenty automatically adds standard fields such as
name, createdAt, updatedAt, createdBy, position, and deletedAt. You don’t need to define these in your fields array — only add your custom fields.Application config (application-config.ts)
Every app has a singleapplication-config.ts file that describes:
- Who the app is: identifiers, display name, and description.
- How its functions run: which role they use for permissions.
- (Optional) variables: key–value pairs exposed to your functions as environment variables.
defineApplication() to define your application configuration:
universalIdentifierfields are deterministic IDs you own; generate them once and keep them stable across syncs.applicationVariablesbecome environment variables for your functions (for example,DEFAULT_RECIPIENT_NAMEis available asprocess.env.DEFAULT_RECIPIENT_NAME).defaultRoleUniversalIdentifiermust match the role file (see below).
Roles and permissions
Applications can define roles that encapsulate permissions on your workspace’s objects and actions. The fielddefaultRoleUniversalIdentifier in application-config.ts designates the default role used by your app’s logic functions.
- The runtime API key injected as
TWENTY_API_KEYis derived from this default function role. - The typed client will be restricted to the permissions granted to that role.
- Follow least‑privilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
Default function role (*.role.ts)
When you scaffold a new app, the CLI also creates a default role file. UsedefineRole() to define roles with built-in validation:
universalIdentifier of this role is then referenced in application-config.ts as defaultRoleUniversalIdentifier. In other words:
- *.role.ts defines what the default function role can do.
- application-config.ts points to that role so your functions inherit its permissions.
- Start from the scaffolded role, then progressively restrict it following least‑privilege.
- Replace the
objectPermissionsandfieldPermissionswith the objects/fields your functions need. permissionFlagscontrol access to platform-level capabilities. Keep them minimal; add only what you need.- See a working example in the Hello World app:
packages/twenty-apps/hello-world/src/roles/function-role.ts.
Logic function config and entrypoint
Each function file usesdefineLogicFunction() to export a configuration with a handler and optional triggers.
- route: Exposes your function on an HTTP path and method under the
/s/endpoint:
e.g.path: '/post-card/create',-> call on<APP_URL>/s/post-card/create
- cron: Runs your function on a schedule using a CRON expression.
- databaseEvent: Runs on workspace object lifecycle events. When the event operation is
updated, specific fields to listen to can be specified in theupdatedFieldsarray. If left undefined or empty, any update will trigger the function.
e.g. person.updated
Notes:
- The
triggersarray is optional. Functions without triggers can be used as utility functions called by other functions. - You can mix multiple trigger types in a single function.
Route trigger payload
When a route trigger invokes your logic function, it receives aRoutePayload object that follows the AWS HTTP API v2 format. Import the type from twenty-sdk:
RoutePayload type has the following structure:
| Property | Type | Description |
|---|---|---|
headers | Record<string, string | undefined> | HTTP headers (only those listed in forwardedRequestHeaders) |
queryStringParameters | Record<string, string | undefined> | Query string parameters (multiple values joined with commas) |
pathParameters | Record<string, string | undefined> | Path parameters extracted from the route pattern (e.g., /users/:id → { id: '123' }) |
body | object | null | Parsed request body (JSON) |
isBase64Encoded | boolean | Whether the body is base64 encoded |
requestContext.http.method | string | HTTP method (GET, POST, PUT, PATCH, DELETE) |
requestContext.http.path | string | Raw request path |
Forwarding HTTP headers
By default, HTTP headers from incoming requests are not passed to your logic function for security reasons. To access specific headers, explicitly list them in theforwardedRequestHeaders array:
Header names are normalized to lowercase. Access them using lowercase keys (for example,
event.headers['content-type']).- Scaffolded: Run
yarn entity:addand choose the option to add a new logic function. This generates a starter file with a handler and config. - Manual: Create a new
*.logic-function.tsfile and usedefineLogicFunction(), following the same pattern.
Front components
Front components let you build custom React components that render within Twenty’s UI. UsedefineFrontComponent() to define components with built-in validation:
- Front components are React components that render in isolated contexts within Twenty.
- Use the
*.front-component.tsxfile suffix for automatic detection. - The
componentfield references your React component. - Components are built and synced automatically during
yarn app:dev.
- Scaffolded: Run
yarn entity:addand choose the option to add a new front component. - Manual: Create a new
*.front-component.tsxfile and usedefineFrontComponent().
Generated typed client
Run yarn app:generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:yarn app:generate. Re-run after changing your objects or when onboarding to a new workspace.
Runtime credentials in logic functions
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:TWENTY_API_URL: Base URL of the Twenty API your app targets.TWENTY_API_KEY: Short‑lived key scoped to your application’s default function role.
- You do not need to pass URL or API key to the generated client. It reads
TWENTY_API_URLandTWENTY_API_KEYfrom process.env at runtime. - The API key’s permissions are determined by the role referenced in your
application-config.tsviadefaultRoleUniversalIdentifier. This is the default role used by logic functions of your application. - Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point
defaultRoleUniversalIdentifierto that role’s universal identifier.
Hello World example
Explore a minimal, end-to-end example that demonstrates objects, logic functions, front components, and multiple triggers here:Manual setup (without the scaffolder)
While we recommend usingcreate-twenty-app for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add twenty-sdk as a local dependency and wire scripts in your package.json:
yarn app:dev, yarn app:generate, etc.
Troubleshooting
- Authentication errors: run
yarn auth:loginand ensure your API key has the required permissions. - Cannot connect to server: verify the API URL and that the Twenty server is reachable.
- Types or client missing/outdated: run
yarn app:generate. - Dev mode not syncing: ensure
yarn app:devis running and that changes are not ignored by your environment.