Query basics
Each object is exposed as a table named by its slug, with columns named after its attribute’s slugs. Theobjects schema is the default, so you can query an object directly by its slug (lists live under the lists schema, e.g. FROM lists.sales):
Joins
Record reference attributes (including relationship attributes) are exposed in SQL as columns containing structs with two text fields:entity_definition_id, the UUID of the target object, and entity_instance_id, the UUID of the target record.
These correspond to target_object and target_record_id in the REST API, except that entity_definition_id is always the object’s UUID rather than its api_slug.
To follow a reference, join entity_instance_id against the target table’s record_id.
As an example, every list entry exposes a parent_record reference column pointing at the record the entry belongs to, so you can join from a list back to its underlying object:
Multi-object references
A record reference attribute can be configured to allow more than one target object. When it is, each value’sentity_definition_id tells you which object that reference points at and can differ from row to row, so you can’t resolve the whole column against one table.
Resolve it the same way as any reference, but filter each join on entity_definition_id so a join only considers the references that point at its object.
To pull in several object types at once, write one filtered join per object and UNION ALL them:
entity_definition_id filter on the unnested struct.
Joining through record reference arraysMulti-select record reference attributes (like
associated_deals on people, or associated_people on deals) are stored as arrays of structs, where each struct is a reference of the shape {entity_definition_id, entity_instance_id}.
To join from a record to the records it references, you need to unnest that array into one row per reference, then join on entity_instance_id.We don’t support unnesting a table’s own column directly in the FROM clause e.g. FROM people, UNNEST(people.associated_deals) AS associated_dealsThe supported method to write this join is to unnest inside a CTE’s SELECT, then field-access and join in the outer query.References without a table
Not every reference resolves to a queryable table, so some can’t be followed with aJOIN.
The most common case is actor references: built-in attributes like created_by that point at the workspace member, app, or automation responsible for something, rather than at another record.
These are exposed as a struct with two text fields, type (e.g. workspace-member, api-token, system) and id.
There’s no actors table but you can filter and group on created_by.type and created_by.id directly.
For example, to see how your people records were created — teammates versus imports, enrichment, or automations:
workspace-member IDs into names, emails, or avatars, fetch the list of workspace members and cross-reference against it.
References to other entities that aren’t queryable yet behave the same way.
Task references, for example the next_due_task attribute, use the entity-reference struct shape of {entity_definition_id, entity_instance_id} but have no table to join against.