Relationships
Studio writes this format for you. The content-type builder creates reference fields visually, and this page documents the schema they produce and how references resolve at build time.
A relationship links one content entry to another: a blog post to its author, a product to its tags. You declare it once, in the field's JSON Schema inside the
content
section of
project.json
. That single declaration drives validation, build-time resolution, and Studio's entry pickers.
The reference form
A reference field's schema points at the target content type with a
#/content/<type>
pointer:
{
"content": {
"authors": { "source": "./content/authors/", "format": "Markdown" },
"blog": {
"source": "./content/blog/",
"format": "Markdown",
"schema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"author": { "$ref": "#/content/authors" }
}
}
}
}
}
Cardinality is expressed with plain JSON Schema; there is no separate relationship syntax:
| Cardinality | Field schema |
|---|---|
| To-one | { "$ref": "#/content/authors" } |
| To-many | { "type": "array", "items": { "$ref": "#/content/tags" } } |
What entries store
Entries store the target's
entry id
: a string for to-one, an array of strings for to-many. For a Markdown collection the id is the entry's filename, so a post in
content/blog/
referencing
content/authors/jane-doe.md
looks like:
---
title: My Post
author: jane-doe
tags: [intro, tooling]
---
Anything other than a string (or array of strings, for to-many) fails the field's validation.
Resolution
References resolve when content loads, at build and in the dev server and in Studio preview alike. The stored id is replaced in place with the full referenced entry, so by the time a page sees the data,
author
is no longer
"jane-doe"
but the whole author entry (
id
,
data
, body). Templates walk straight through:
{ "tagName": "span", "textContent": }
For a to-many field, each id in the array is replaced by its entry the same way.
An id that matches no entry in the target type is left untouched, and the raw string stays in the data. Guard templates accordingly, or keep ids and filenames in sync.
Beyond content
The same reference form generalizes: a pointer targets any project section whose extension declares it referenceable, as
#/<sectionKey>/<name>
. File-based content resolves at load time as described above; connection-backed table sections (from the connector extension) store the id in a column and resolve at request time instead. One direction is disallowed outright: a
content
schema cannot reference a
table
section, because content is loaded once at build time while table rows are live, so model that association from the table side.
Related
Content collections : defining content types and their schemas
Content types in Studio : building these schemas visually
References :
$refinside documents, a different mechanism