Discussing linking embedded documents in Sanity.io schemas

9 replies
Last updated: Nov 15, 2021
I have a document of type
Parent
that has a
content
field that is an array of documents of type
Child
(not reference to
Child
). I would like to create another field that can choose one
Child
from the `Parent`'s
content
field. Is this possible?
AI Update

This is an interesting scenario! You want to create a field that references a specific child document that's nested within the same parent document's array. This isn't directly supported in Sanity's schema system, but there are a couple of approaches you can take:

The Challenge: Sanity's reference fields are designed to point to top-level documents in your dataset, not to nested objects within arrays. Since your Child documents are embedded objects (not separate documents with their own _id), you can't use a standard reference field.

Solution 1: Use a string field with the child's _key

Since objects in arrays automatically get a _key property, you can create a string field that stores the _key of the selected child:

{
  name: 'selectedChild',
  type: 'string',
  title: 'Selected Child',
  description: 'Reference to a child from the content array'
}

Then create a custom input component that:

  1. Accesses the parent document's content array using the withDocument HOC or hooks
  2. Renders a dropdown/select showing the children
  3. Stores the selected child's _key

To retrieve the actual child data in your queries, you'd use GROQ:

*[_type == "parent" && _id == $id][0]{
  ...,
  selectedChildData: content[_key == ^.selectedChild][0]
}

Solution 2: Make Child a separate document type

If you need true referential integrity and want to use Sanity's built-in reference features, consider making Child a top-level document type instead of nested objects (similar to the approach in this parent/child taxonomy guide). Then use an array of references in your content field, and a single reference for your selection field:

{
  name: 'content',
  type: 'array',
  of: [{type: 'reference', to: [{type: 'child'}]}]
},
{
  name: 'selectedChild',
  type: 'reference',
  to: [{type: 'child'}]
}

You could even add validation to ensure the selected child exists in the content array.

Which approach to use?

  • If your children are truly "owned" by the parent and don't need to exist independently, stick with nested objects and use Solution 1
  • If children might be shared across parents or need their own identity, use Solution 2

The custom input component approach (Solution 1) gives you full control over the selection UI while keeping your data structure intact. It's more work upfront but maintains the nested structure you already have.

Hey User! I'm curious, if the
Child
documents aren't references, what does this look like in your schema? Re: selecting a
Child
, what's the ultimate goal of selecting it?
const lesson = {
  type: 'document',
  name: 'lesson',
  title: 'Lesson',
  fields: [
    {
      type: 'array',
      name: 'content',
      title: 'Content',
      of: [
        {
          type: 'section',
        }
      ],
    },
  ],
}

const module = {
  type: 'document',
  name: 'module',
  title: 'Module',
  fields: [
    {
      type: 'array',
      name: 'content',
      title: 'Content',
      sortable: true,
      of: [
        {
          type: 'lesson',
        },
      ],
    },
  ],
}
user M
simplified, but I want to be able to refer to a
lesson
that is in a `module`'s
content
array
It looks like there is no way to do this in vanilla Sanity (please tell me I'm wrong) and I'm looking at custom input components. However, the
props
passed to
React.forwardRef
don't seem to provide a means to access the
parent
object / document (or at least, I can't seem to find a way).
My use case is that I want to be able to refer to one
lesson
from another
lesson
. I was doing this with references and a custom filter, which worked when the
lesson
was a separate document and referenced by the module. Now that it is embedded in the module, I need another way of referencing / selecting the
lesson
.
The reason that we have embedded the `lesson`s inside the
module
(and
module
in
course
, and
course
in
programme
) is that there were simply too many loose documents, and the course creators where having to jump through hoops to manage the documents. Now they all exist within the context of a single
programme
container, everything is much simpler - except I still need to link from one
lesson
to another.
If you're trying to create references between lessons, I recommend you create them as documents. As you mentioned, you could create a custom input component that queries for information in a field in another document, but this is a really brittle content model. I think you'll find it's easier to maintain if you create them as documents, use dynamic filters in your reference fields and create a custom desk structure for organizing the documents.
However, if you do decide to go the custom input component route, you can access other fields in the document by exporting the component with the
withDocument
HOC .
user M
we've gone back to using references, and whilst I can link (refer) to lessons, I will need solve the organisation of content in the desk structure and programme publishing (all linked documents need to be published too). But that is for another day... :-)
Working with Structure Builder can be a bit daunting when you first start working with it, so feel free to come back if you get stuck!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?