Skip to content

feat(editor): add relation input to reference another collection - #548

Open
nadaamd wants to merge 1 commit into
nuxt-content:mainfrom
nadaamd:feat/relation-input
Open

feat(editor): add relation input to reference another collection#548
nadaamd wants to merge 1 commit into
nuxt-content:mainfrom
nadaamd:feat/relation-input

Conversation

@nadaamd

@nadaamd nadaamd commented Aug 24, 2026

Copy link
Copy Markdown

Adds a relation form input so a string field can reference a document of
another collection, and editors pick it from a searchable list instead of typing
an identifier by hand.

A first step towards #219, which asks for the full feature (arrays, multiple
target collections, MDC props) — see Follow-ups for what is left.
Deliberately not using a closing keyword, so the issue stays open for the rest.

#307 was closed in favour of #219 and asked for the same thing. It proposed an
async options callback on the field; that cannot work, because the options
would have to survive serialisation into the generated JSON Schema that Studio
reads at edit time. Naming the target collection declaratively keeps the schema
static and lets Studio resolve the documents itself.

Why

Cross-collection references are a common content model (post.author
authors, event.locationlocations). Today they render as free-text
fields, so editors copy-paste slugs and a typo is only caught later, by a build
or an integrity check. z.enum() built from the target collection is the usual
workaround, but it renders a closed dropdown and couples the schema to the data.

API

// content.config.ts
author: property(z.string()).editor({
  input: 'relation',
  relation: { collection: 'authors', labelField: 'name' },
})
  • collection — name of the referenced collection. Both page and data collections work.
  • valueField — field written to the file. Defaults to slug, the document file
    name, which is what a document is usually referenced by. path and stem are
    also available, as is any field of the referenced document.
  • labelField — field displayed in the picker. Defaults to name, then title,
    then the stored value.

How it works

InputRelation.vue mirrors InputMedia.vue: a text input plus a searchable
popover. Options come from the existing host bridge — host.document.db.list()
filtered by host.collection.getByFsPath() — so there is no new server route
and nothing external to reach. Search matches both the label and the stored
value, so editors can look up a display name or a slug they already know.

The leading icon reports whether the current value resolves to a document, which
is the main thing a picker can offer over a text field.

Backward compatibility

  • The field keeps storing a plain string. File format, content queries and
    existing documents are untouched, and the value stays editable by hand.
  • A value matching no document is flagged but never rewritten.
  • Fields without input: 'relation' are unaffected; an unknown input already
    falls back to InputText, so an older Studio reading a newer schema degrades
    to a text field.

EditorOptions in @nuxt/content

.editor() forwards its whole argument into $content.editor in the generated
schema, so relation reaches Studio at runtime with no change to
@nuxt/content. Only the types need widening: EditorOptions.input is the
closed union 'media' | 'icon' | 'textarea', so input: 'relation' currently
needs a cast (as in the playground here). That is lifted by the companion PR on
nuxt/content: nuxt/content#3840.

Follow-ups

Deliberately left out to keep this reviewable:

Test plan

  • src/app/test/unit/utils/relation.test.ts — new, 18 cases covering value and
    label resolution, option building and search.
  • src/app/test/unit/utils/form.test.ts — 2 cases for the schema → form tree
    mapping, including a relation with no target collection.
  • pnpm verify passes: lint clean, nuxt typecheck and
    vue-tsc -p src/app/tsconfig.app.json clean, 413 tests green, pnpm test:types
    clean.
  • Exercised in playground/docus, where docs.author now references the
    existing authors data collection.

Renders a searchable picker for string fields that reference a document of
another collection, instead of leaving editors to type an identifier by hand.

Options are read from the existing host bridge (`document.db.list()` filtered
by `collection.getByFsPath()`), so no new route is needed. The field keeps
storing a plain string: file format and content queries are unaffected, the
value stays editable by hand, and a value matching no document is flagged but
never rewritten.

Refs nuxt-content#219

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

@nadaamd is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant