Skip to main content
OlonJS uses JSON $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 in posts.json stores its tags as an array of $ref pointers:
src/data/collections/posts/posts.json (excerpt)
A $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.
Nothing is stored on the tag side. The 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 $ref in the post’s tags array to its display data.
The section component handles the matching internally; you do not need to pre-join the 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 whose tags 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. the design tag).
  • "posts": { "$ref": "../../collections/posts/posts.json" } — the full posts collection. The tag-posts section filters this collection at render time, keeping only posts whose tags array contains a $ref fragment matching the current tag key.
Because the filter runs at render time, adding a new tag to a post in 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
The descriptor '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.