Type something to search...
How to Use References in Sanity Schemas

How to Use References in Sanity Schemas

To use references in Sanity schemas, add a field with type: 'reference' and a to array listing the document types it can point to, such as to: [{ type: 'author' }]. Editors then pick an existing document (or create one) in Studio, and Sanity stores a small object containing the target's ID in _ref. In GROQ you follow the link with the -> operator, as in author->name, and find documents that point to something with references($id). For lists of links, put reference members inside an array, and add weak: true when the target should be allowed to be deleted or unpublished.

References are how you avoid duplicating content. An author, a category or a product is created once and linked from everywhere it's used, so a single edit updates every page. This guide covers how to define references, what they store, how to query them in both directions, how to filter and control the picker, the difference between strong and weak references, and how to write references from code.

When to Use a Reference

Use a reference when the linked content:

  • Is reused: the same author appears on many posts.
  • Has its own lifecycle: a product is edited and published independently of the pages that feature it.
  • Has its own page or identity: a category has a URL, a description and an image.

If the data only makes sense inside one document, like a postal address on a venue, use an embedded object instead. The content modelling best practices post explains how to make that call.

A Basic Reference Field

// schemaTypes/post.ts
import { defineField, defineType } from 'sanity';

export const post = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: [{ type: 'author' }],
      validation: (rule) => rule.required(),
    }),
  ],
});

The target type, author, must be a document type in your schema. In Studio, the field shows a search box listing authors, using each author's preview for the title and thumbnail. Editors can also click Create new to make an author without leaving the post.

What Gets Stored

The post stores only the link, not the author's data:

{
  "_id": "8f2c...",
  "_type": "post",
  "title": "Hello Sanity",
  "author": {
    "_type": "reference",
    "_ref": "4a1d6b0e-0d3f-4c55-9a1e-2b7c7f1e9a10"
  }
}

That's why updating the author is instant everywhere: posts never had a copy.

Referencing Several Types

to can list more than one type. A "related content" field might accept posts and videos:

defineField({
  name: 'featured',
  title: 'Featured content',
  type: 'reference',
  to: [{ type: 'post' }, { type: 'video' }],
}),

When you query a multi-type reference, check _type to know what you got, or use conditional projections in GROQ:

*[_type == "homepage"][0]{
  featured->{
    _type,
    title,
    _type == "post" => { "slug": slug.current },
    _type == "video" => { "duration": duration }
  }
}

Note that to must be a fixed list. It can't be a function or a wildcard.

Arrays of References

For many-to-many relationships, such as a post with several categories, use an array whose members are references:

import { defineArrayMember, defineField } from 'sanity';

defineField({
  name: 'categories',
  title: 'Categories',
  type: 'array',
  of: [
    defineArrayMember({
      type: 'reference',
      to: [{ type: 'category' }],
    }),
  ],
  validation: (rule) => rule.unique().max(3),
}),

rule.unique() stops editors adding the same category twice, and max(3) keeps lists focused. Each item gets a _key, as with every array of objects:

"categories": [
  { "_key": "a1", "_type": "reference", "_ref": "cat-design" },
  { "_key": "b2", "_type": "reference", "_ref": "cat-sanity" }
]

The arrays guide covers ordering and layout options.

Querying References With GROQ

Following a Reference Forward

The -> operator dereferences, replacing the reference with the target document:

*[_type == "post" && slug.current == $slug][0]{
  title,
  "authorName": author->name,
  author->{ name, "avatar": image.asset->url },
  "categories": categories[]->{ title, "slug": slug.current }
}
  • author->name returns one field.
  • author->{ ... } returns a projection of the author.
  • categories[]-> dereferences every item in the array.

Without ->, you get the raw { _ref, _type } object. The GROQ dereference guide covers more patterns.

Finding What Points to a Document

References are one-directional: the author doesn't store a list of posts. To go the other way, use references():

*[_type == "author" && slug.current == $slug][0]{
  name,
  "posts": *[_type == "post" && references(^._id)] | order(publishedAt desc){
    title,
    "slug": slug.current
  },
  "postCount": count(*[_type == "post" && references(^._id)])
}

^._id means "the _id of the outer document", here the author. This is the standard way to build author pages, category archives and "used in" lists.

Filtering by a Referenced Field

You can filter on fields of the referenced document:

*[_type == "post" && author->slug.current == "sajjad"]{ title }

That's convenient, but on large datasets it's faster to filter by ID when you have it:

*[_type == "post" && author._ref == $authorId]{ title }

Controlling the Reference Picker

Filter Which Documents Appear

options.filter takes a GROQ filter, with parameters in filterParams:

defineField({
  name: 'director',
  type: 'reference',
  to: [{ type: 'person' }],
  options: {
    filter: 'role == $role && isActive == true',
    filterParams: { role: 'director' },
  },
}),

For filters that depend on the current document, pass a function. It receives document, parent and other context, and returns filter and params:

defineField({
  name: 'relatedPosts',
  type: 'array',
  of: [
    defineArrayMember({
      type: 'reference',
      to: [{ type: 'post' }],
      options: {
        filter: ({ document }) => ({
          filter: '_id != $id && !(_id in path("drafts.**"))',
          params: { id: document._id.replace(/^drafts\./, '') },
        }),
      },
    }),
  ],
}),

This stops a post linking to itself. Bear in mind that filter only limits search results in Studio. It doesn't validate references that already exist or that scripts write, so add a custom validation rule if a constraint really matters.

Stop Inline Creation

To force editors to pick existing documents, set disableNew: true:

options: { disableNew: true },

For multi-type references, creationTypeFilter lets you choose which types appear in the Create new menu without affecting which existing documents can be selected.

Strong vs Weak References

By default, references are strong. Sanity guarantees the target exists:

  • You can't delete a document while strong references point to it. Studio shows which documents link to it.
  • A document can't be published while its strong reference points to an unpublished draft.

A weak reference relaxes those rules:

defineField({
  name: 'sponsor',
  type: 'reference',
  to: [{ type: 'company' }],
  weak: true,
}),
Strong (default)Weak (weak: true)
Target can be deletedNo, blocked while referencedYes
Can publish with an unpublished targetNoYes
Dereferencing a missing targetNot possible, target always existsReturns null
Good forAuthors, categories, core relationshipsOptional, loosely coupled or temporary links

Use strong references for anything your frontend relies on, and weak ones where a broken link is acceptable and you'd rather not block editors. Always handle null in your frontend when using weak references. Changing weak in the schema doesn't update existing reference data, so set it deliberately from the start.

Previews That Follow References

Document previews can read fields through a reference with dot notation:

preview: {
  select: {
    title: 'title',
    authorName: 'author.name',
    media: 'author.image',
  },
  prepare: ({ title, authorName, media }) => ({
    title,
    subtitle: authorName ? `by ${authorName}` : 'No author',
    media,
  }),
},

In preview.select, use a dot (author.name), not the GROQ arrow.

Writing References From Code

When you create documents with @sanity/client, write the reference object yourself:

import { createClient } from '@sanity/client';

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2025-10-01',
  token: process.env.SANITY_WRITE_TOKEN,
  useCdn: false,
});

await client.create({
  _type: 'post',
  title: 'Written from a script',
  author: { _type: 'reference', _ref: 'author-sajjad' },
  categories: [
    { _key: 'c1', _type: 'reference', _ref: 'cat-sanity' },
  ],
});

The target must exist before you create a strong reference to it, otherwise the write is rejected. When importing data, create referenced documents first, or use a transaction that creates both together.


FAQ: References in Sanity

A reference stores only the ID of another document, so the data lives in one place and can be reused. An object embeds the data directly in the parent document. Use references for shared content like authors and categories, and objects for data that belongs to a single document.

Use the references function in GROQ, for example filtering posts with references and the author's ID. Inside a projection on the author, write references(^._id) to refer to the outer document's ID.

Another document holds a strong reference to it. Studio lists the referencing documents so you can remove or change those references first. If the link should not block deletion, make the field a weak reference.

Yes. List several types in the to array, such as post and video. When querying, check the _type of the dereferenced document or use conditional projections to pick fields for each type.

No. A reference is stored only on the document that makes it. To find documents pointing at a given document, query in reverse with the references function rather than storing links on both sides, which would drift out of sync.

Yes, with cross-dataset references, which point to documents in a different dataset of the same project. They're designed for larger multi-dataset setups, so check your plan and the official documentation before using them.


Conclusion

References are the backbone of a well-structured Sanity model. Define them with type: 'reference' and a to list, use arrays of references for many-to-many links, and query them with -> to go forward and references() to go back. Filter the picker so editors see the right options, and keep the default strong references for relationships your site depends on.

Switch to weak references only where a missing target is acceptable, handle null in your frontend when you do, and remember that picker filters aren't validation. With those habits, your content stays connected, consistent and easy to query. To package shared field groups that don't need their own documents, continue with building reusable object types.

Tags :
Share :

Related Posts

Building a Static Site With Astro and Sanity

Building a Static Site With Astro and Sanity

To build a static site with Astro and Sanity, keep Astro's default output: 'static', configure @sanity/astro with useCdn: false, and fetch

Dive Deeper
Common Sanity Mistakes and How to Fix Them

Common Sanity Mistakes and How to Fix Them

The most common Sanity mistakes are using the CDN where you need fresh data, fetching whole documents with ..., leaving slugs unvalidated, ship

Dive Deeper
How to Create a Custom Sanity Plugin

How to Create a Custom Sanity Plugin

To create a custom Sanity plugin, wrap a piece of Studio configuration in definePlugin from the sanity package and add it to the plugins ar

Dive Deeper