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
Objects
defineObject — declare new record types with their own fields.Extending Objects
defineField — add fields to standard or other apps’ objects.Relations
Bidirectional MANY_TO_ONE / ONE_TO_MANY connections between objects.Timeline Activity Types
Define automatic audit events and explicit app events for record timelines.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
WHEREclauses 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).
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 (optional)
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
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.