Show navigationHide navigation

Reference

Links one entry to another — authors on a post, posts on a blog page, related products. In the editor: sortable drag-and-drop list with search, inline editing, and "create new" flow.

Usage#

typescript
authors: {
  label: 'Authors',
  format: 'reference',
  required: true,
  reference: {
    collections: ['author'],
    cardinality: 'many',
    min: 1,
    max: 5,
  },
},

Options#

OptionTypeDefaultDescription
requiredbooleanfalseMust have at least one reference (or min if higher)
reference.collectionsstring[]All collectionsWhich collections can be referenced
reference.cardinality'one' | 'many''many'Single object or array of objects
reference.minnumberMinimum items (for many)
reference.maxnumberMaximum items (for many)
hintstringHelper text below the field

Cardinality#

Many-to-many (default)#

typescript
posts: {
  label: 'Posts',
  format: 'reference',
  reference: { collections: ['post'], cardinality: 'many' },
},

Stored as an array of reference keys. Resolved by query() to an array of full entry objects.

One-to-one#

typescript
author: {
  label: 'Author',
  format: 'reference',
  reference: { collections: ['author'], cardinality: 'one' },
},

Stored as a single reference key string. Resolved to a single entry object (not wrapped in an array).

Example: Blog with authors#

Schema — posts linked to authors, where each author has roles:

typescript
// cms/octocms.config.ts
collections: {
  role: {
    label: 'Role',
    hasMany: true,
    fields: {
      title: { label: 'Title', format: 'string', entryTitle: true, required: true },
    },
  },
  author: {
    label: 'Author',
    hasMany: true,
    fields: {
      name: { label: 'Name', format: 'string', entryTitle: true, required: true },
      avatar: { label: 'Avatar', format: 'image' },
      roles: {
        label: 'Roles',
        format: 'reference',
        reference: { collections: ['role'], cardinality: 'many' },
      },
    },
  },
  post: {
    label: 'Post',
    hasMany: true,
    fields: {
      title: { label: 'Title', format: 'string', entryTitle: true, required: true },
      author: {
        label: 'Author',
        format: 'reference',
        required: true,
        reference: { collections: ['author'], cardinality: 'one' },
      },
      body: { label: 'Body', format: 'markdown' },
    },
  },
},

Query + render:

tsx
// src/app/blog/[slug]/page.tsx
import Image from 'next/image';
import { query } from 'cms/__generated__/query';

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await query('post').filter({ slug: params.slug }).first();
  if (!post) return null;

  const author = post.fields.author;
  // author is a resolved entry: { sys: {...}, fields: { name, avatar, roles } }
  // author.fields.roles is an array of resolved role entries

  return (
    <article>
      <h1>{post.fields.title}</h1>

      <div className="author">
        {author.fields.avatar && (
          <Image
            src={author.fields.avatar.src}
            alt={author.fields.avatar.alt}
            width={48}
            height={48}
            className="rounded-full"
          />
        )}
        <span>{author.fields.name}</span>
        {author.fields.roles?.map((role) => (
          <span key={role.sys.id} className="badge">{role.fields.title}</span>
        ))}
      </div>
    </article>
  );
}

Deep resolution#

References are resolved recursively. A post → author → roles chain produces fully nested objects:

json
{
  "sys": { "id": "p1", "type": "post" },
  "fields": {
    "title": "My Post",
    "author": {
      "sys": { "id": "a1", "type": "author" },
      "fields": {
        "name": "Alice",
        "roles": [
          { "sys": { "id": "r1", "type": "role" }, "fields": { "title": "Editor" } }
        ]
      }
    }
  }
}

Inline editing#

Click a referenced item's title or pencil icon to edit it inline:

  • Opens an overlay panel with the referenced entry's full editor.
  • Stack is encoded in the URL as repeated ?overlay=<content-path> params, so a deep link round-trips when pasted into a new window.
  • Overlays stack infinitely; cycle detection prevents opening an entry already in the stack.
  • The browser back button closes one overlay (each push adds a history entry).
  • Saves are independent per entry. When an overlay closes after a save, the parent re-resolves titles, backlinks, and history.
  • Deleting a referenced entry removes it from the parent list and shows backlink warnings.

See ../inline-editing-overlays.md for the full architecture.

Storage#

Many — JSON array of reference keys:

json
{ "roles": "[\"role-r1.json\",\"role-r2.json\"]" }

One — single reference key string:

json
{ "author": "author-a1.json" }

Query result#

Many — array of resolved entry objects. One — single resolved entry object.

All nested references and images within the referenced entries are also resolved.