twenty-sdk package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK.
DefineEntity functions
The SDK provides functions to define your app entities. You must useexport default defineEntity({...}) for the SDK to detect your entities. These functions validate your configuration at build time and provide IDE autocompletion and type safety.
export default defineEntity(...) calls regardless of where the file lives. Grouping files by type (e.g., logic-functions/, roles/) is just a convention, not a requirement.defineRole
Configure role permissions and object access
defineRole
Configure role permissions and object access
defineApplication
Configure application metadata (required, one per app)
defineApplication
Configure application metadata (required, one per app)
defineApplication call that describes:- Identity: identifiers, display name, and description.
- Permissions: which role its functions and front components use.
- (Optional) Variables: key–value pairs exposed to your functions as environment variables.
- (Optional) Pre-install / post-install functions: logic functions that run before or after installation.
universalIdentifierfields are deterministic IDs you own. Generate them once and keep them stable across syncs.applicationVariablesbecome environment variables for your functions and front components (e.g.,DEFAULT_RECIPIENT_NAMEis available asprocess.env.DEFAULT_RECIPIENT_NAME).defaultRoleUniversalIdentifiermust reference a role defined withdefineRole()(see above).- Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in
defineApplication().
Marketplace metadata
If you plan to publish your app, these optional fields control how it appears in the marketplace:| Field | Description |
|---|---|
author | Author or company name |
category | App category for marketplace filtering |
logoUrl | Path to your app logo (e.g., public/logo.png) |
screenshots | Array of screenshot paths (e.g., public/screenshot-1.png) |
aboutDescription | Longer markdown description for the “About” tab. If omitted, the marketplace uses the package’s README.md from npm |
websiteUrl | Link to your website |
termsUrl | Link to terms of service |
emailSupport | Support email address |
issueReportUrl | Link to issue tracker |
Roles and permissions
ThedefaultRoleUniversalIdentifier in application-config.ts designates the default role used by your app’s logic functions and front components. See defineRole above for details.- The runtime token injected as
TWENTY_APP_ACCESS_TOKENis derived from this role. - The typed client is restricted to the permissions granted to that role.
- Follow least-privilege: create a dedicated role with only the permissions your functions need.
Default function role
When you scaffold a new app, the CLI creates a default role file:universalIdentifier is referenced in application-config.ts as defaultRoleUniversalIdentifier:- *.role.ts defines what the 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
objectPermissionsandfieldPermissionswith the objects and fields your functions actually need. permissionFlagscontrol access to platform-level capabilities. Keep them minimal.- See a working example:
hello-world/src/roles/function-role.ts.
defineObject
Define custom objects with fields
defineObject
Define custom objects with fields
defineObject() 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 twenty add, which guides you through naming, fields, and relationships.
id, name, createdAt, updatedAt, createdBy, updatedBy and deletedAt.
You don’t need to define these in your fields array — only add your custom fields.
You can override default fields by defining a field with the same name in your fields array,
but this is not recommended.defineField — Standard fields
Extend existing objects with additional fields
defineField — Standard fields
Extend existing objects with additional fields
defineField() to add fields to objects you don’t own — such as standard Twenty objects (Person, Company, etc.) or objects from other apps. Unlike inline fields in defineObject(), standalone fields require an objectUniversalIdentifier to specify which object they extend:objectUniversalIdentifieridentifies the target object. For standard objects, useSTANDARD_OBJECT_UNIVERSAL_IDENTIFIERSexported fromtwenty-sdk.- When defining fields inline in
defineObject(), you do not needobjectUniversalIdentifier— it’s inherited from the parent object. defineField()is the only way to add fields to objects you didn’t create withdefineObject().
defineField — Relation fields
Connect objects together with bidirectional relations
defineField — Relation fields
Connect objects together with bidirectional relations
| Relation type | Description | Has foreign key? |
|---|---|---|
MANY_TO_ONE | Many records of this object point to one record of the target | Yes (joinColumnName) |
ONE_TO_MANY | One record of this object has many records of the target | No (inverse side) |
How relations work
Every relation requires two fields that reference each other:- The MANY_TO_ONE side — lives on the object that holds the foreign key
- The ONE_TO_MANY side — lives on the object that owns the collection
FieldType.RELATION and cross-reference each other via relationTargetFieldMetadataUniversalIdentifier.Example: Post Card has many Recipients
Suppose aPostCard can be sent to many PostCardRecipient records. Each recipient belongs to exactly one post card.Step 1: Define the ONE_TO_MANY side on PostCard (the “one” side):universalIdentifier. To avoid circular import issues, export your field IDs as named constants from each file, and import them in the other file. The build system resolves these at compile time.Relating to standard objects
To create a relation with a built-in Twenty object (Person, Company, etc.), useSTANDARD_OBJECT_UNIVERSAL_IDENTIFIERS:Relation field properties
| Property | Required | Description |
|---|---|---|
type | Yes | Must be FieldType.RELATION |
relationTargetObjectMetadataUniversalIdentifier | Yes | The universalIdentifier of the target object |
relationTargetFieldMetadataUniversalIdentifier | Yes | The universalIdentifier of the matching field on the target object |
universalSettings.relationType | Yes | RelationType.MANY_TO_ONE or RelationType.ONE_TO_MANY |
universalSettings.onDelete | MANY_TO_ONE only | What happens when the referenced record is deleted: CASCADE, SET_NULL, RESTRICT, or NO_ACTION |
universalSettings.joinColumnName | MANY_TO_ONE only | Database column name for the foreign key (e.g., postCardId) |
Inline relation fields in defineObject
You can also define relation fields directly insidedefineObject(). In that case, omit objectUniversalIdentifier — it’s inherited from the parent object:defineLogicFunction
Define logic functions and their triggers
defineLogicFunction
Define logic functions and their triggers
defineLogicFunction() to export a configuration with a handler and optional triggers.- httpRoute: Exposes your function on an HTTP path and method under the
/s/endpoint:
e.g.path: '/post-card/create'is callable athttps://your-twenty-server.com/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,*.created,company.*
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 RoutePayload type from twenty-sdk:RoutePayload type has the following structure:| Property | Type | Description | Example |
|---|---|---|---|
headers | Record<string, string | undefined> | HTTP headers (only those listed in forwardedRequestHeaders) | see section below |
queryStringParameters | Record<string, string | undefined> | Query string parameters (multiple values joined with commas) | /users?ids=1&ids=2&ids=3&name=Alice -> { ids: '1,2,3', name: 'Alice' } |
pathParameters | Record<string, string | undefined> | Path parameters extracted from the route pattern | /users/:id, /users/123 -> { id: '123' } |
body | object | null | Parsed request body (JSON) | { id: 1 } -> { id: 1 } |
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 |
forwardedRequestHeaders
By default, HTTP headers from incoming requests are not passed to your logic function for security reasons. To access specific headers, list them in theforwardedRequestHeaders array:event.headers['content-type']).Exposing a function as a tool
Logic functions can be exposed as tools for AI agents and workflows. When marked as a tool, a function becomes discoverable by Twenty’s AI features and can be used in workflow automations.To mark a logic function as a tool, setisTool: true:- You can combine
isToolwith triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. toolInputSchema(optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly:
description. AI agents rely on the function’s description field to decide when to use the tool. Be specific about what the tool does and when it should be called.definePreInstallLogicFunction
Define a pre-install logic function (one per app)
definePreInstallLogicFunction
Define a pre-install logic function (one per app)
- Pre-install functions use
definePreInstallLogicFunction()— a specialized variant that omits trigger settings (cronTriggerSettings,databaseEventTriggerSettings,httpRouteTriggerSettings,isTool). - The handler receives an
InstallLogicFunctionPayloadwith{ previousVersion: string }— the version of the app that was previously installed (or an empty string for fresh installs). - Only one pre-install function is allowed per application. The manifest build will error if more than one is detected.
- The function’s
universalIdentifieris automatically set aspreInstallLogicFunctionUniversalIdentifieron the application manifest during the build — you do not need to reference it indefineApplication(). - The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks.
definePostInstallLogicFunction
Define a post-install logic function (one per app)
definePostInstallLogicFunction
Define a post-install logic function (one per app)
- Post-install functions use
definePostInstallLogicFunction()— a specialized variant that omits trigger settings (cronTriggerSettings,databaseEventTriggerSettings,httpRouteTriggerSettings,isTool). - The handler receives an
InstallLogicFunctionPayloadwith{ previousVersion: string }— the version of the app that was previously installed (or an empty string for fresh installs). - Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
- The function’s
universalIdentifieris automatically set aspostInstallLogicFunctionUniversalIdentifieron the application manifest during the build — you do not need to reference it indefineApplication(). - The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
defineFrontComponent
Define front components for custom UI
defineFrontComponent
Define front components for custom UI
Basic example
The quickest way to see a front component in action is to register it as a command. Adding acommand field with isPinned: true makes it appear as a quick-action button in the top-right corner of the page — no page layout needed:yarn twenty dev, the quick action appears in the top-right corner of the page:
Configuration fields
| Field | Required | Description |
|---|---|---|
universalIdentifier | Yes | Stable unique ID for this component |
component | Yes | A React component function |
name | No | Display name |
description | No | Description of what the component does |
isHeadless | No | Set to true if the component has no visible UI (see below) |
command | No | Register the component as a command (see command options below) |
Placing a front component on a page
Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a page layout. See the definePageLayout section for details.Headless components (isHeadless: true)
Headless components render no visible UI but still run React logic. This is useful for effect components — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification.null, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API.Accessing runtime context
Inside your component, use SDK hooks to access the current user, record, and component instance:| Hook | Returns | Description |
|---|---|---|
useUserId() | string or null | The current user’s ID |
useRecordId() | string or null | The current record’s ID (when placed on a record page) |
useFrontComponentId() | string | This component instance’s ID |
useFrontComponentExecutionContext(selector) | varies | Access the full execution context with a selector function |
Host communication API
Front components can trigger navigation, modals, and notifications using functions fromtwenty-sdk:| Function | Description |
|---|---|
navigate(to, params?, queryParams?, options?) | Navigate to a page in the app |
openSidePanelPage(params) | Open a side panel |
closeSidePanel() | Close the side panel |
openCommandConfirmationModal(params) | Show a confirmation dialog |
enqueueSnackbar(params) | Show a toast notification |
unmountFrontComponent() | Unmount the component |
updateProgress(progress) | Update a progress indicator |
Command options
Adding acommand field to defineFrontComponent registers the component in the command menu (Cmd+K). If isPinned is true, it also appears as a quick-action button in the top-right corner of the page.| Field | Required | Description |
|---|---|---|
universalIdentifier | Yes | Stable unique ID for the command |
label | Yes | Full label shown in the command menu (Cmd+K) |
shortLabel | No | Shorter label displayed on the pinned quick-action button |
icon | No | Icon name displayed next to the label (e.g. 'IconBolt', 'IconSend') |
isPinned | No | When true, shows the command as a quick-action button in the top-right corner of the page |
availabilityType | No | Controls where the command appears: 'GLOBAL' (always available), 'RECORD_SELECTION' (only when records are selected), or 'FALLBACK' (shown when no other commands match) |
availabilityObjectUniversalIdentifier | No | Restrict the command to pages of a specific object type (e.g. only on Company records) |
conditionalAvailabilityExpression | No | A boolean expression to dynamically control whether the command is visible (see below) |
Conditional availability expressions
TheconditionalAvailabilityExpression field lets you control when a command is visible based on the current page context. Import typed variables and operators from twenty-sdk to build expressions:| Variable | Type | Description |
|---|---|---|
pageType | string | Current page type (e.g. 'RecordIndexPage', 'RecordShowPage') |
isInSidePanel | boolean | Whether the component is rendered in a side panel |
numberOfSelectedRecords | number | Number of currently selected records |
isSelectAll | boolean | Whether “select all” is active |
selectedRecords | array | The selected record objects |
favoriteRecordIds | array | IDs of favorited records |
objectPermissions | object | Permissions for the current object type |
targetObjectReadPermissions | object | Read permissions for the target object |
targetObjectWritePermissions | object | Write permissions for the target object |
featureFlags | object | Active feature flags |
objectMetadataItem | object | Metadata of the current object type |
hasAnySoftDeleteFilterOnView | boolean | Whether the current view has a soft-delete filter |
| Operator | Description |
|---|---|
isDefined(value) | true if the value is not null/undefined |
isNonEmptyString(value) | true if the value is a non-empty string |
includes(array, value) | true if the array contains the value |
includesEvery(array, prop, value) | true if every item’s property includes the value |
every(array, prop) | true if the property is truthy on every item |
everyDefined(array, prop) | true if the property is defined on every item |
everyEquals(array, prop, value) | true if the property equals the value on every item |
some(array, prop) | true if the property is truthy on at least one item |
someDefined(array, prop) | true if the property is defined on at least one item |
someEquals(array, prop, value) | true if the property equals the value on at least one item |
someNonEmptyString(array, prop) | true if the property is a non-empty string on at least one item |
none(array, prop) | true if the property is falsy on every item |
noneDefined(array, prop) | true if the property is undefined on every item |
noneEquals(array, prop, value) | true if the property does not equal the value on any item |
Public assets
Front components can access files from the app’spublic/ directory using getPublicAssetUrl:Styling
Front components support multiple styling approaches. You can use:- Inline styles —
style={{ color: 'red' }} - Twenty UI components — import from
twenty-sdk/ui(Button, Tag, Status, Chip, Avatar, and more) - Emotion — CSS-in-JS with
@emotion/react - Styled-components —
styled.divpatterns - Tailwind CSS — utility classes
- Any CSS-in-JS library compatible with React
defineSkill
Define AI agent skills
defineSkill
Define AI agent skills
defineSkill() to define skills with built-in validation:nameis a unique identifier string for the skill (kebab-case recommended).labelis the human-readable display name shown in the UI.contentcontains the skill instructions — this is the text the AI agent uses.icon(optional) sets the icon displayed in the UI.description(optional) provides additional context about the skill’s purpose.
defineAgent
Define AI agents with custom prompts
defineAgent
Define AI agents with custom prompts
defineAgent() to create agents with a custom system prompt:nameis the unique identifier string for the agent (kebab-case recommended).labelis the display name shown in the UI.promptis the system prompt that defines the agent’s behavior.description(optional) provides context about what the agent does.icon(optional) sets the icon displayed in the UI.modelId(optional) overrides the default AI model used by the agent.
defineView
Define saved views for objects
defineView
Define saved views for objects
defineView() to ship pre-configured views with your app:objectUniversalIdentifierspecifies which object this view applies to.keydetermines the view type (e.g.,ViewKey.INDEXfor the main list view).fieldscontrols which columns appear and their order. Each field references afieldMetadataUniversalIdentifier.- You can also define
filters,filterGroups,groups, andfieldGroupsfor more advanced configurations. positioncontrols the ordering when multiple views exist for the same object.
defineNavigationMenuItem
Define sidebar navigation links
defineNavigationMenuItem
Define sidebar navigation links
definePageLayout
Define custom page layouts for record views
definePageLayout
Define custom page layouts for record views
definePageLayout() to ship custom layouts with your app:typeis typically'RECORD_PAGE'to customize the detail view of a specific object.objectUniversalIdentifierspecifies which object this layout applies to.- Each
tabdefines a section of the page with atitle,position, andlayoutMode(CANVASfor free-form layout). - Each
widgetinside a tab can render a front component, a relation list, or other built-in widget types. positionon tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones.
Public assets (public/ folder)
The public/ folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server.
Files placed in public/ are:
- Publicly accessible — once synced to the server, assets are served at a public URL. No authentication is needed to access them.
- Available in front components — use asset URLs to display images, icons, or any media inside your React components.
- Available in logic functions — reference asset URLs in emails, API responses, or any server-side logic.
- Used for marketplace metadata — the
logoUrlandscreenshotsfields indefineApplication()reference files from this folder (e.g.,public/logo.png). These are displayed in the marketplace when your app is published. - Auto-synced in dev mode — when you add, update, or delete a file in
public/, it is synced to the server automatically. No restart needed. - Included in builds —
yarn twenty buildbundles all public assets into the distribution output.
Accessing public assets with getPublicAssetUrl
Use the getPublicAssetUrl helper from twenty-sdk to get the full URL of a file in your public/ directory. It works in both logic functions and front components.
In a logic function:
path argument is relative to your app’s public/ folder. Both getPublicAssetUrl('logo.png') and getPublicAssetUrl('public/logo.png') resolve to the same URL — the public/ prefix is stripped automatically if present.
Using npm packages
You can install and use any npm package in your app. Both logic functions and front components are bundled with esbuild, which inlines all dependencies into the output — nonode_modules are needed at runtime.
Installing a package
How bundling works
The build step (yarn twenty dev or yarn twenty build) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle.
Logic functions run in a Node.js environment. Node built-in modules (fs, path, crypto, http, etc.) are available and do not need to be installed.
Front components run in a Web Worker. Node built-in modules are not available — only browser APIs and npm packages that work in a browser environment.
Both environments have twenty-client-sdk/core and twenty-client-sdk/metadata available as pre-provided modules — these are not bundled but resolved at runtime by the server.
Scaffolding entities with yarn twenty add
Instead of creating entity files by hand, you can use the interactive scaffolder:
universalIdentifier and the correct defineEntity() call.
You can also pass the entity type directly to skip the first prompt:
Available entity types
| Entity type | Command | Generated file |
|---|---|---|
| Object | yarn twenty add object | src/objects/<name>.ts |
| Field | yarn twenty add field | src/fields/<name>.ts |
| Logic function | yarn twenty add logicFunction | src/logic-functions/<name>.ts |
| Front component | yarn twenty add frontComponent | src/front-components/<name>.tsx |
| Role | yarn twenty add role | src/roles/<name>.ts |
| Skill | yarn twenty add skill | src/skills/<name>.ts |
| Agent | yarn twenty add agent | src/agents/<name>.ts |
| View | yarn twenty add view | src/views/<name>.ts |
| Navigation menu item | yarn twenty add navigationMenuItem | src/navigation-menu-items/<name>.ts |
| Page layout | yarn twenty add pageLayout | src/page-layouts/<name>.ts |
What the scaffolder generates
Each entity type has its own template. For example,yarn twenty add object asks for:
- Name (singular) — e.g.,
invoice - Name (plural) — e.g.,
invoices - Label (singular) — auto-populated from the name (e.g.,
Invoice) - Label (plural) — auto-populated (e.g.,
Invoices) - Create a view and navigation item? — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object.
field entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like TEXT, NUMBER, SELECT, RELATION, etc.), and the target object’s universalIdentifier.
Custom output path
Use the--path flag to place the generated file in a custom location:
Typed API clients (twenty-client-sdk)
Thetwenty-client-sdk package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components.
| Client | Import | Endpoint | Generated? |
|---|---|---|---|
CoreApiClient | twenty-client-sdk/core | /graphql — workspace data (records, objects) | Yes, at dev/build time |
MetadataApiClient | twenty-client-sdk/metadata | /metadata — workspace config, file uploads | No, ships pre-built |
CoreApiClient
Query and mutate workspace data (records, objects)
CoreApiClient
Query and mutate workspace data (records, objects)
CoreApiClient is the main client for querying and mutating workspace data. It is generated from your workspace schema during yarn twenty dev or yarn twenty build, so it is fully typed to match your objects and fields.true to include a field, use __args for arguments, and nest objects for relations. You get full autocompletion and type checking based on your workspace schema.yarn twenty dev or yarn twenty build first, it throws an error. The generation happens automatically — the CLI introspects your workspace’s GraphQL schema and generates a typed client using @genql/cli.Using CoreSchema for type annotations
CoreSchema provides TypeScript types matching your workspace objects — useful for typing component state or function parameters:MetadataApiClient
Workspace config, applications, and file uploads
MetadataApiClient
Workspace config, applications, and file uploads
MetadataApiClient ships pre-built with the SDK (no generation required). It queries the /metadata endpoint for workspace configuration, applications, and file uploads.Uploading files
MetadataApiClient includes an uploadFile method for attaching files to file-type fields:| Parameter | Type | Description |
|---|---|---|
fileBuffer | Buffer | The raw file contents |
filename | string | The name of the file (used for storage and display) |
contentType | string | MIME type (defaults to application/octet-stream if omitted) |
fieldMetadataUniversalIdentifier | string | The universalIdentifier of the file-type field on your object |
- Uses the field’s
universalIdentifier(not its workspace-specific ID), so your upload code works across any workspace where your app is installed. - The returned
urlis a signed URL you can use to access the uploaded file.
TWENTY_API_URL— Base URL of the Twenty APITWENTY_APP_ACCESS_TOKEN— Short-lived key scoped to your application’s default function role
process.env automatically. The API key’s permissions are determined by the role referenced in defaultRoleUniversalIdentifier in your application-config.ts.Testing your app
The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with Vitest and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server.Setup
The scaffolded app already includes Vitest. If you set it up manually, install the dependencies:vitest.config.ts at the root of your app:
Programmatic SDK APIs
Thetwenty-sdk/cli subpath exports functions you can call directly from test code:
| Function | Description |
|---|---|
appBuild | Build the app and optionally pack a tarball |
appDeploy | Upload a tarball to the server |
appInstall | Install the app on the active workspace |
appUninstall | Uninstall the app from the active workspace |
success: boolean and either data or error.
Writing an integration test
Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace:Running tests
Make sure your local Twenty server is running, then:Type checking
You can also run type checking on your app without running tests:tsc --noEmit and reports any type errors.
CLI reference
Beyonddev, build, add, and typecheck, the CLI provides commands for executing functions, viewing logs, and managing app installations.
Executing functions (yarn twenty exec)
Run a logic function manually without triggering it via HTTP, cron, or database event:
Viewing function logs (yarn twenty logs)
Stream execution logs for your app’s logic functions:
yarn twenty server logs, which shows the Docker container logs. yarn twenty logs shows your app’s function execution logs from the Twenty server.