Relations
MeshQL relations live under schema.joins. Each key is {entityKey}.{refName} — always the entity key, never the plural selection path.
| Query path (selection) | Schema join key |
|---|---|
{ blog { tags } } |
blog.tags |
{ user { blogs { tags } } } |
still blog.tags (not blogs.tags) |
Selection paths (blogs.tags) are used for SQL aliases and shaping. Schema keys stay on the entity.
One (belongs-to)
Section titled “One (belongs-to)”"blog.author": { entity: "user", on: "blogs.author_id = users.id", type: "one",}{ blog { id title author { name } } }Many (has-many)
Section titled “Many (has-many)”"user.blogs": { entity: "blog", on: "blogs.author_id = users.id", type: "many",}{ user { id blogs { title } } }Many-to-many (through)
Section titled “Many-to-many (through)”"blog.tags": { entity: "tag", on: "blog_tags.blog_id = blogs.id", type: "many", through: { table: "blog_tags", from: "blog_id", to: "tag_id", },},"tag.blogs": { entity: "blog", on: "blog_tags.tag_id = tags.id", type: "many", through: { table: "blog_tags", from: "tag_id", to: "blog_id", },}Declare each direction you want to query. SQL builders emit parent → junction → child.
Nested selections (no duplicate keys)
Section titled “Nested selections (no duplicate keys)”With user.blogs, blog.tags, and blog.author only:
{ user { blogs { title tags { name } author { name } } } }MeshQL looks up nested refs via the parent entity (blog), so you do not declare blogs.tags or blogs.author.
Self-relation
Section titled “Self-relation”"comment.replies": { entity: "comment", on: "comments.parent_id = comments.id", type: "many",}The join key is still {entity}.{ref} (comment.replies). Nested paths use the ref name for aliases (replies.replies), but schema keys stay on comment.
Polymorphic one (commentable)
Section titled “Polymorphic one (commentable)”Parent row holds a type discriminator + target id:
"comment.commentable": { type: "one", entities: ["post", "image"], on: "/* derived from polymorphic */", polymorphic: { typeColumn: "commentable_type", idColumn: "commentable_id", map: { Post: "post", Image: "image" }, },}{ "comment": { "$select": { "id": true, "commentable": { "$select": { "id": true, "title": true, "url": true } } } }}Selected fields may exist on any target. The response includes $entity:
{ "id": 1, "commentable": { "$entity": "post", "id": 10, "title": "Hello" }}v1 limits: polymorphic type must be "one"; no through + polymorphic; no further nested joins under the polymorphic ref. Prefer SQL adapters (@meshql/sqlite / @meshql/postgres); ORM include trees may need a custom / preshaped resolver.
What not to do
Section titled “What not to do”- Do not declare
blogs.tagsfor nesting underuser.blogs— useblog.tags. - Do not use SQL table names as join-key parents (
users.tokens); use entity keys (user.tokens). - Pointing
entity.tableat a database view is fine; there is no separate view join type.
See also Concepts and the JoinPlan spec.