Skip to main content

What the data layer covers

A Twenty app’s data layer is the data your app adds to a workspace — the new record types it declares, the columns it adds to existing objects, and how those records connect to each other.

In this section

card-page-objects
Objects
defineObject — declare new record types with their own fields.
card-page-extending-objects
Extending Objects
defineField — add fields to standard or other apps’ objects.
card-page-relations
Relations
Bidirectional MANY_TO_ONE / ONE_TO_MANY connections between objects.
card-page-timeline
Timeline Activity Types
Define automatic audit events and explicit app events for record timelines.
card-page-objects
Validation Rules
defineValidationRule — declare the conditions a record must meet before it can be saved.

Entities at a glance

The CLI detects these via AST analysis at build time, so file organization is up to you — the convention is src/objects/, src/validation-rules/, src/fields/, src/indexes/, src/timeline-activity-types/, and src/related-record-paths/. Stable universalIdentifier UUIDs tie everything together across deploys.

Indexes (optional)

Apps can ship indexes alongside their objects to keep recurring queries fast. The most common case is a status or foreign-key column that you read frequently.
src/indexes/post-card-status.index.ts

Unique indexes

defineIndex accepts isUnique: true for both single- and multi-column uniqueness. This is the recommended primitive — defineField({ isUnique: true }) is deprecated and will be removed in a future release.

Other constraints

  • Partial WHERE clauses stay under admin control — apps can’t declare them.
  • Each object is capped at 10 custom indexes (the framework’s own indexes don’t count).
Order the fields array the way Postgres should use it — leftmost column first, like a phone book. Indexes are not free: every write to the table updates them. Add one only when you have a query that needs it.
Related record paths are in preview: they sync with your app, but record permissions don’t use them yet. Once enforced, they require the Organization plan or above, like row-level security.
A related record path starts on an object and follows up to three relation fields, each on the object the previous one reached. Record permissions use these paths to extend access from a record to its related records.
  • An inherent path (isInherent: true) applies whenever the starting record is readable, however that access was granted. Use it for records that belong to their parent, such as a post card’s attachments.
  • A regular path applies only when access to the starting record comes directly from a role, so workspaces choose which ones to rely on.
src/related-record-paths/post-card-attachments.related-record-path.ts
Add secondFieldMetadataUniversalIdentifier and thirdFieldMetadataUniversalIdentifier for longer paths. Each hop must be an active relation on the object reached so far, a path can’t visit the same object twice, and two paths can’t use the same hops.
Looking for Application Config or Roles & Permissions? Those describe the app itself rather than the data it adds — they live under Config. Looking for Connections (Linear, GitHub, Slack OAuth)? Those exist to be called from logic functions and live under Logic.