twenty-sdk package provides defineEntity functions to declare your app’s data model. You must use export default defineEntity({...}) for the SDK to detect your entities. These functions validate your configuration at build time and provide IDE autocompletion and type safety.
File organization is up to you.
Entity detection is AST-based — the SDK finds
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
Roles encapsulate permissions on your workspace’s objects and actions.
restricted-company-role.ts
defineApplication
Configure application metadata (required, one per app)
defineApplication
Configure application metadata (required, one per app)
Every app must have exactly one Notes:
This role’s
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.
src/application-config.ts
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:src/roles/default-role.ts
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
Custom objects describe both schema and behavior for records in your workspace. Use Key points:
defineObject() to define objects with built-in validation:postCard.object.ts
- 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.
Base fields are created automatically. When you define a custom object, Twenty automatically adds standard fields
such as
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
Use Key points:
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:src/fields/company-loyalty-tier.field.ts
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
Relations connect objects together. In Twenty, relations are always bidirectional — you define both sides, and each side references the other.There are two relation types:
Step 2: Define the MANY_TO_ONE side on PostCardRecipient (the “many” side — holds the foreign key):
| 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):src/fields/post-card-recipients-on-post-card.field.ts
src/fields/post-card-on-post-card-recipient.field.ts
Circular imports: Both relation fields reference each other’s
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:src/fields/person-on-self-hosting-user.field.ts
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: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: