Skip to main content
The 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.
Roles encapsulate permissions on your workspace’s objects and actions.
restricted-company-role.ts
Every app must have exactly one 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
Notes:
  • universalIdentifier fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
  • applicationVariables become environment variables for your functions and front components (e.g., DEFAULT_RECIPIENT_NAME is available as process.env.DEFAULT_RECIPIENT_NAME).
  • defaultRoleUniversalIdentifier must reference a role defined with defineRole() (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:

Roles and permissions

The defaultRoleUniversalIdentifier 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_TOKEN is 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
This role’s 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.
Notes:
  • Start from the scaffolded role, then progressively restrict it following least-privilege.
  • Replace objectPermissions and fieldPermissions with the objects and fields your functions actually need.
  • permissionFlags control access to platform-level capabilities. Keep them minimal.
  • See a working example: hello-world/src/roles/function-role.ts.
Custom objects describe both schema and behavior for records in your workspace. Use defineObject() to define objects with built-in validation:
postCard.object.ts
Key points:
  • Use defineObject() for built-in validation and better IDE support.
  • The universalIdentifier must be unique and stable across deployments.
  • Each field requires a name, type, label, and its own stable universalIdentifier.
  • The fields array 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, Clara 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.
Use defineField() to add fields to objects you don’t own — such as standard Clara 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
Key points:
  • objectUniversalIdentifier identifies the target object. For standard objects, use STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS exported from twenty-sdk.
  • When defining fields inline in defineObject(), you do not need objectUniversalIdentifier — it’s inherited from the parent object.
  • defineField() is the only way to add fields to objects you didn’t create with defineObject().
Relations connect objects together. In Clara, relations are always bidirectional — you define both sides, and each side references the other.There are two relation types:

How relations work

Every relation requires two fields that reference each other:
  1. The MANY_TO_ONE side — lives on the object that holds the foreign key
  2. The ONE_TO_MANY side — lives on the object that owns the collection
Both fields use FieldType.RELATION and cross-reference each other via relationTargetFieldMetadataUniversalIdentifier.

Example: Post Card has many Recipients

Suppose a PostCard 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
Step 2: Define the MANY_TO_ONE side on PostCardRecipient (the “many” side — holds the foreign key):
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 Clara object (Person, Company, etc.), use STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS:
src/fields/person-on-self-hosting-user.field.ts

Relation field properties

Inline relation fields in defineObject

You can also define relation fields directly inside defineObject(). 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:
This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable universalIdentifier and the correct defineEntity() call. You can also pass the entity type directly to skip the first prompt:

Available entity types

What the scaffolder generates

Each entity type has its own template. For example, yarn twenty add object asks for:
  1. Name (singular) — e.g., invoice
  2. Name (plural) — e.g., invoices
  3. Label (singular) — auto-populated from the name (e.g., Invoice)
  4. Label (plural) — auto-populated (e.g., Invoices)
  5. Create a view and navigation item? — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object.
Other entity types have simpler prompts — most only ask for a name. The 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: