Relations
Overview
Section titled “Overview”Relations define how collections are connected at the database level. They enable Rebase to:
- Render relation picker fields in entity forms
- Resolve related entities when displaying previews
- Generate foreign key constraints in the Drizzle schema
- Support cascade delete/update behaviors
Relations can be defined either inline within the property, or explicitly in the relations array of a collection:
1. Inline Relations (Recommended)
Section titled “1. Inline Relations (Recommended)”You can define the relation directly on the property. The framework automatically extracts these into the collection’s relations[] at normalization time, so you no longer need a separate relations[] entry for properties.
const postsCollection: EntityCollection = { slug: "posts", name: "Posts", table: "posts", properties: { title: { type: "string", name: "Title" }, content: { type: "string", name: "Content", multiline: true }, author: { type: "relation", name: "Author", target: () => usersCollection, cardinality: "one", direction: "owning", localKey: "author_id" } }};2. Explicit Relations Array
Section titled “2. Explicit Relations Array”For advanced use cases or when a relation doesn’t map directly to a form field, you can define it in the relations array:
const postsCollection: EntityCollection = { slug: "posts", name: "Posts", table: "posts", properties: { title: { type: "string", name: "Title" }, content: { type: "string", name: "Content", multiline: true }, author: { type: "relation", name: "Author", relationName: "author" } }, relations: [ { relationName: "author", target: () => usersCollection, cardinality: "one", localKey: "author_id" } ]};Relation Types
Section titled “Relation Types”One-to-One / Many-to-One
Section titled “One-to-One / Many-to-One”A foreign key on this table points to another table’s primary key.
relations: [ { relationName: "author", target: () => usersCollection, cardinality: "one", // This entity has ONE author direction: "owning", // The FK is on THIS table localKey: "author_id" // Column on the posts table }]This creates: posts.author_id → users.id
One-to-Many (Inverse)
Section titled “One-to-Many (Inverse)”The foreign key is on the target table, pointing back to this entity.
// On the Users collection:relations: [ { relationName: "posts", target: () => postsCollection, cardinality: "many", // This user has MANY posts direction: "inverse", // The FK is on the TARGET table foreignKeyOnTarget: "author_id" // Column on the posts table }]Many-to-Many (Junction Table)
Section titled “Many-to-Many (Junction Table)”Two collections connected through an intermediate junction table.
// On the Users collection:relations: [ { relationName: "roles", target: () => rolesCollection, cardinality: "many", direction: "owning", through: { table: "user_roles", // Junction table name sourceColumn: "user_id", // FK to this collection targetColumn: "role_id" // FK to target collection } }]This creates:
CREATE TABLE user_roles ( user_id INTEGER REFERENCES users(id), role_id INTEGER REFERENCES roles(id), PRIMARY KEY (user_id, role_id));Relation Properties
Section titled “Relation Properties”To render a relation field in a form, add a property with type: "relation":
properties: { author: { type: "relation", name: "Author", target: () => usersCollection, // Target collection widget: "select" // "select" (dropdown) or "dialog" (full picker) }}
When rendering a preview (like in a table cell or a reference chip), Rebase handles hydration automatically:

Multi-Hop Joins
Section titled “Multi-Hop Joins”For complex relationships that traverse multiple tables, use joinPath:
// Users → Permissions through Rolesrelations: [ { relationName: "permissions", target: () => permissionsCollection, cardinality: "many", joinPath: [ { table: "user_roles", on: { from: "id", to: "user_id" } }, { table: "roles", on: { from: "role_id", to: "id" } }, { table: "role_permissions", on: { from: "id", to: "role_id" } }, { table: "permissions", on: { from: "permission_id", to: "id" } } ] }]Composite Key Joins
Section titled “Composite Key Joins”joinPath: [ { table: "customers", on: { from: ["company_code", "region_id"], // Multiple columns to: ["code", "region_id"] } }]Cascade Rules
Section titled “Cascade Rules”Control what happens when related entities are updated or deleted:
relations: [ { relationName: "author", target: () => usersCollection, cardinality: "one", localKey: "author_id", onDelete: "cascade", // Delete posts when user is deleted onUpdate: "cascade" // Update FK when user ID changes }]| Action | Behavior |
|---|---|
"cascade" | Propagate the change to related rows |
"restrict" | Prevent the operation if related rows exist |
"no action" | Same as restrict (defer to constraint check) |
"set null" | Set the FK column to NULL |
"set default" | Set the FK column to its default value |
Fetching Relations in the SDK
Section titled “Fetching Relations in the SDK”When querying data through the Rebase Client SDK, relations are not included by default. Use the include() method to request related entities alongside the primary data.
Include specific relations
Section titled “Include specific relations”const { data } = await client.data.articles .include("author", "categories") .find();Include all relations
Section titled “Include all relations”const { data } = await client.data.articles .include("*") .find();Using params syntax
Section titled “Using params syntax”const { data } = await client.data.articles.find({ include: ["author", "categories"]});Response structure
Section titled “Response structure”When included, the response contains both the scalar foreign key and the hydrated relation object:
const { data } = await client.data.articles .include("author") .find();
for (const article of data) { // Scalar FK — always present article.values.author_id; // "uuid-1234"
// Hydrated relation — only present when included article.values.author?.name; // "Jane Doe"}The relation names passed to
include()must match therelationNamedefined in the collection’srelationsarray.
For the full query builder reference (filtering, sorting, pagination, real-time), see the Client SDK documentation.
Full Relation Interface
Section titled “Full Relation Interface”interface Relation { relationName?: string; target: () => EntityCollection; cardinality: "one" | "many"; direction?: "owning" | "inverse"; inverseRelationName?: string; localKey?: string; foreignKeyOnTarget?: string; through?: { table: string; sourceColumn: string; targetColumn: string; }; joinPath?: JoinStep[]; onUpdate?: "cascade" | "restrict" | "no action" | "set null" | "set default"; onDelete?: "cascade" | "restrict" | "no action" | "set null" | "set default"; overrides?: Partial<EntityCollection>; validation?: { required?: boolean };}Next Steps
Section titled “Next Steps”- Security Rules — Row Level Security
- Properties — Property types reference