$ref pointers to express relations between collections — for example, a post referencing its tags. Relations have a single source of truth on one side and are computed in the other direction at render time. This guide shows you how to set up bidirectional navigation between any two collections.
Storing a relation
The source of truth lives on the owning side of the relation — in the Inkwell demo, that is the post. Each post entry inposts.json stores its tags as an array of $ref pointers:
src/data/collections/posts/posts.json (excerpt)
$ref value has two parts separated by #:
- Path — a relative path from the current JSON file to the target collection file, e.g.
../tags/tags.json. - Fragment (optional) —
#/<key>selects a single item from the target collection by its record key, e.g.#/design.
tags.json file contains only tag attributes (name, description, accent color) with no back-references to posts.
Forward resolution (post → tags)
When a visitor opens a post page, OlonJS resolves each$ref pointer in the post’s tags array to a full Tag object during the bake and render step. The related-tags section receives the resolved tags and renders them as clickable chips.
Wire the related-tags section in your dynamic post page template like this:
src/data/pages/posts/[slug].json (excerpt)
"item": { "$ref": "collection:current" }— injects the current post item (resolved from the URL slug)."tags": { "$ref": "../../collections/tags/tags.json" }— passes the full tags collection so the section can match each$refin the post’stagsarray to its display data.
Inverse relation (tag → posts)
A tag page needs to show all posts that belong to it — but the tag entry itself stores no post references. OlonJS computes the inverse at render time by filtering the entire posts collection for entries whosetags array includes the current tag’s key.
Wire the tag-posts section in your dynamic tag page template:
src/data/pages/tags/[slug].json (excerpt)
"item": { "$ref": "collection:current" }— the current tag item (e.g. thedesigntag)."posts": { "$ref": "../../collections/posts/posts.json" }— the full posts collection. Thetag-postssection filters this collection at render time, keeping only posts whosetagsarray contains a$reffragment matching the current tag key.
posts.json automatically updates the tag page — no changes to tags.json needed.
Adding a relation to your schema
To add a relation field to your own collection schema, accept either a resolved entity object or a raw$ref pointer. This union type lets OlonJS store pointers in authored JSON while the bake step resolves them to full objects at build time.
src/collections/posts/schema.ts
'ui:collection-ref:tags' tells Studio to render the tags field as a multi-select dropdown populated with entries from the registered tags collection.
To relate to a different collection, change the suffix: 'ui:collection-ref:products', 'ui:collection-ref:authors', and so on. The suffix must match the key you used when registering the collection in CollectionRegistry.ts.
Testing the relation
If the tag page shows zero posts after adding a new relation, run
npm run build again. The inverse filter is computed during the bake step, so it requires a rebuild to reflect new data..png?fit=max&auto=format&n=AyzhRn9-CO-nDNc4&q=85&s=63c78e2325701880237e6cbce1399c2a)
.png?fit=max&auto=format&n=AyzhRn9-CO-nDNc4&q=85&s=0db283df6e4d31745079d6d03fd029ed)