Skip to content

Relationships

Everything that connects two tables follows a foreign key the database declares, or a reference the App declares in its place.

related('Opportunities', 'opportunity', 'account_id', { show: ['name', 'stage', 'amount'] }),
metric('Open tasks', 'task', { whose: 'account_id', query: 'status = open' }),

account_id here is a hop: the name of a foreign key, written as its column (a+b for a composite key, the constraint’s name where two keys share columns). tablewalk-schema.d.ts lists every table’s hops under References.

Where Written
A reference cell or field, drawn as its record’s name and a link a column that is a foreign key
A label path in show, recordLabel, a card account_id.name, opportunity_id.account_id.city
A related list, a metric, breakdown or chart on a page whose: the hop on the from rows that points at the record
A section about another record than the page’s about: the hop from the page’s record to it
A timeline stream stream(from, whose, at, show)
A rollup (count, sum, min, max) and a related list’s create the same hops

similar() and a related list’s same match by column value and need no foreign key.

A text column holding another table’s code (site_code → site.code) is not a relationship until something says so: paths refuse it, naming it a column but not a foreign key. Declare the key in the database (ALTER TABLE work_order ADD FOREIGN KEY (site_code) REFERENCES site (code); SQLite declares keys in CREATE TABLE), or in the App:

work_order: { references: { site_code: { to: 'site', column: 'code' } } },

Either way the target must be unique (its primary key, the default, or a unique constraint on that column alone) and of the same kind. A declared reference is followed like a key everywhere above, is never authority for a write, and adds no delete impact. Then run tablewalk check.

A foreign key lives in one database. Two sources on one connection (a schema each) are one database, so a key or a declared reference between them joins like any other. A declared reference into a source on another connection is read in two steps: the list’s rows, then one read of the targets the page names, through that source’s own grants and row policy. It labels the list, opens the target, and gives the target a related list; filters, sorts, groupings, rollups, tags and notify paths through it are refused, since each database filters what it holds. A tenant App’s references stay within its one source.