Skip to main content
Custom objects are new record types your app adds to a workspace — Post Card, Invoice, Subscription, anything specific to your domain. Each object declares its schema (fields, relations, default values) and a stable universal identifier that survives across syncs and deploys.
src/objects/post-card.object.ts

Key points

  • 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.
  • Inline fields defined here do not need an objectUniversalIdentifier — it’s inherited from the parent object. Use defineField() to add fields to objects you don’t own.
  • You can scaffold new objects with yarn twenty dev:add object, which guides you through naming, fields, and relationships. See Architecture → Scaffolding entities.
Base fields are added automatically. When you define a custom object, Twenty creates standard fields like id, name, createdAt, updatedAt, createdBy, updatedBy, and deletedAt for you. You don’t need to declare them in your fields array — only your custom fields. You can override a default field by declaring one with the same name, but this is rarely a good idea.

Field types

The full set of FieldType values, exported from twenty-sdk/define: Composite types store multiple sub-fields (e.g. FULL_NAME = first + last name; CURRENCY = amountMicros + currencyCode). SELECT and MULTI_SELECT require an options array as in the example above.

Default values

Literal string defaults must be wrapped in single quotes inside the string — defaultValue: "'Draft'", not defaultValue: "Draft". That’s why the status field above uses `'${PostCardStatus.DRAFT}'`. Unquoted strings are reserved for computed defaults, evaluated when a record is created:
  • 'uuid' — generates a UUID (for UUID fields)
  • 'now' — the current timestamp (for DATE_TIME fields)
The same convention applies to string sub-fields of composite defaults (e.g. { source: "'MANUAL'" } on an ACTOR field) and to SELECT/MULTI_SELECT values. A literal string default left unquoted raises a warning when your app is built.

Nullability

isNullable controls whether a field accepts NULL. It defaults to true — omit it for optional fields. Set isNullable: false to make a field required at the database level. Changes to isNullable are applied on every sync, including syncs that update an existing field — so you can flip a field’s nullability by editing the manifest and re-syncing.
Making an existing field non-nullable requires a default value. When you change a field to isNullable: false, you must also provide a non-null defaultValue. The default backfills any existing NULL rows before the NOT NULL constraint is applied; without it the sync fails with Default value cannot be null for non-nullable fields. Relation fields and TS_VECTOR fields are always nullable, so isNullable has no effect on them.

What’s next

  • Connect this object to others — see Relations for the bidirectional relation pattern.
  • Add fields to objects from other apps — see Extending Objects for defineField().
  • Display this object in the UI — see Views and Navigation Menu Items to put it in the sidebar.