Help with setting initial values for array of references in Sanity schema

32 replies
Last updated: Jun 8, 2022
  "authors": [
    {
      "_type": "reference",
      "_key": "e4072b1d3618",
      "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6"
    },
    {
      "_type": "reference",
      "_key": "e2d64d5f79e3",
      "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962"
    }
  ],

How do I groq this?

I tried:

 *[_type == "person"]{
          "authors": {
                "_type": "reference",
                "_ref": _id
            }
          }

But it gives me:
  "authors": [
    {
      "_key": "81f684c7699b7bc2f8d0a3f5d983cd5f",
      "authors": {
        "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6",
        "_type": "reference"
      }
    },
    {
      "_key": "1769186994b00072dcb9acc9910880f2",
      "authors": {
        "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962",
        "_type": "reference"
      }
    }
  ]

Tried many groq queries but just cant figure this one out, any help would be appreciated. Thanks
AI Update

Looking at your query, the issue is that you're creating an extra layer of nesting. When you query *[_type == "person"] and then project each result with an authors object, you're wrapping each reference inside an authors property.

If you want to create an array of reference objects from all person documents, you need:

*[_type == "person"]{
  "_type": "reference",
  "_key": _id,
  "_ref": _id
}

This gives you a flat array of reference objects without the extra authors wrapper.

However, I suspect what you're actually trying to do is query documents that already have an authors array and either:

1. Get the raw reference structure (as stored):

*[_type == "yourDocumentType"]{
  authors
}

This returns the authors array exactly as you showed it, with _type, _key, and _ref.

2. Dereference the authors to get their actual data:

*[_type == "yourDocumentType"]{
  authors[]->{
    name,
    bio,
    // other person fields
  }
}

The []-> syntax dereferences the entire array, replacing each reference with the actual person document.

3. Dereference but keep the _key property:

*[_type == "yourDocumentType"]{
  authors[]{
    _key,
    ...(@->{
      name,
      bio
    })
  }
}

This pattern preserves the _key while spreading the dereferenced content, which is useful if you need the key for React rendering (as mentioned in this answer about preserving _key during dereferencing).

The _key values you see (like "e4072b1d3618") are automatically generated by Sanity Studio. You typically don't construct these manually in GROQ queries - they're part of your stored document structure. If you're trying to create new reference arrays, you'd do that through mutations or the Studio interface, not GROQ queries (which are read-only).

You need to resolve the references, as explained in the documentation or shown in the cheat sheet :
authors[] -> { ... }
The
[]
means
authors
is an array and you want to do that for every item.
->
means resolving the reference to that document.
...
means all fields.
So this means “resolve all authors references and retrieve all their fields.”
Oh I do not want to get the reference of author, I want the groq projection to look exactly like the original one. I should probably explain I am trying to set an initialvalue for the authors field and have folowed along in this sanity video: https://www.youtube.com/watch?v=iR4JVsWF6uo
Oh, sorry I didn’t get that. How does your person schema look like please?
Have you tried:
*[_type == "person"]{
  _key,
  "_type": "reference",
  "_ref": _id
}
import customImage from "../../lib/custom-image";

export default {
    title: "Person",
    name: "person",
    type: "document",
    fields: [
        {
            title: "Name",
            name: "name",
            type: "string",
        },
        customImage({
            title: "Photo",
            name: "photo",
            description: "The portrait for this editor.",
        }),
        {
            title: "Bio",
            name: "bio",
            type: "text",
            description: "A short biography for this editor, to appear in articles, hub pages, etc.",
        },
    ]
};

here is my person schema
I want the groq projection to look exactly like this: 
  "authors": [
    {
      "_type": "reference",
      "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6"
    },
    {
      "_type": "reference",
      "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962"
    }
  ],
But this is not valid JavaScript/JSON to begin with. You need at least a wrapping object to have a named key.
Or you want your projection to return an array of persons.
here is the full raw json:
{
  "_createdAt": "2022-06-08T07:16:41Z",
  "_id": "drafts.98b3e8a1-a3ff-4b47-9a5a-a5d567521bed",
  "_rev": "irwkbf-ico-m75-hqn-nybjc022p",
  "_type": "post",
  "_updatedAt": "2022-06-08T07:16:57Z",
  "authors": [
    {
      "_key": "e4072b1d3618",
      "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6",
      "_type": "reference"
    },
    {
      "_key": "e2d64d5f79e3",
      "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962",
      "_type": "reference"
    }
  ],
  "date": "2022-06-08T07:16:37.535Z",
  "photos": {
    "_type": "image",
    "customRatio": 0
  }
}
I‘m a little confused, sorry. Your person schema as shown above doesn’t seem to have an array of references, so I don‘t know what you‘re trying to query.
Where did you get that raw JSON from? What does it represent?
Ah here is the authors field:
{
            title: 'Authors',
            name: 'authors',
            type: 'array',
            of: [{
                type: 'reference',
                to: [{type: 'person'}],
                options: {
                    filter: ({ document }) => {
                        const addedAuthors = document.authors
                            .map(p => p._ref)
                            .filter(Boolean)
                        return {
                            filter: '!(_id in $ids)',
                            params: {
                                ids: addedAuthors                        }
                        }
                    }
                }
            }],
            validation: (Rule) => Rule.required(),
            group: 'content'
        },
Ah right. If you query the
authors
field without any projection or resolution, it should return the array as you expect I believe.
Here is the full code for this page so it makes it easier to see what im trying do
import React from 'react'
import sanityClient from 'part:@sanity/base/client'
import { Gift } from 'phosphor-react'
import customImage from '../../lib/custom-image'

import { getIcon } from './filter'

export default {
    name: "post",
    title: "Post",
    type: "document",
    groups: [
        { title: 'Content', name: 'content', default: true },
        { title: 'SEO', name: 'seo' },
        { title: 'Settings', name: 'settings' },
    ],
    initialValue: async () => ({
        date: new Date().toISOString(),
        authors: await sanityClient.fetch(`
          *[_type == "person"]{
              _key,
              "_type": "reference",
              "_ref": _id
            }
        `)
        // authors: [
        //     {
        //         "_key": "18fdc834e74a",
        //         "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6",
        //         "_type": "reference"
        //     },
        //     {
        //         "_key": "3e9896fdaed3",
        //         "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962",
        //         "_type": "reference"
        //     }
        // ],
    }),
    fields: [
        {
            name: "title",
            type: "string",
            title: "Title",
            validation: (Rule) => Rule.required(),
            group: 'content'
        },
        {
            title: 'Authors',
            name: 'authors',
            type: 'array',
            of: [{
                type: 'reference',
                to: [{type: 'person'}],
                options: {
                    filter: ({ document }) => {
                        const addedAuthors = document.authors
                            .map(p => p._ref)
                            .filter(Boolean)
                        return {
                            filter: '!(_id in $ids)',
                            params: {
                                ids: addedAuthors                        }
                        }
                    }
                }
            }],
            validation: (Rule) => Rule.required(),
            group: 'content'
        },
        {
            name: "slug",
            type: "slug",
            title: "Slug",
            options: {
                source: "title",
                maxLength: 100,
            },
            validation: (Rule) => Rule.required(),
            group: 'content'
        },
        customImage({
            title: 'Photo',
            name: 'photos',
            group: 'content',
        }),
        {
            name: "date",
            title: "Date",
            type: "datetime",
            validation: (Rule) => Rule.required(),
            group: 'content'
        },
        {
            title: 'Content',
            name: 'content',
            type: 'complexPortableText',
            group: 'content'
        },
        {
            title: 'Overlay header with transparency?',
            name: 'hasTransparentHeader',
            type: 'boolean',
            description:
                'When activated the header will overlay the first content module with a transparent background and white text until scrolling is engaged.',
            group: 'settings'
        },
        {
            title: 'Filters',
            name: 'filters',
            type: 'array',
            description: 'Define what filters are associated with this product',
            of: [
                {
                    title: 'Filter',
                    name: 'filter',
                    type: 'object',
                    fields: [
                        {
                            title: 'Filter',
                            name: 'filter',
                            type: 'reference',
                            to: [{ type: 'filter' }]
                        },
                        {
                            title: 'Which option is this for?',
                            name: 'forOption',
                            type: 'string',
                            options: {
                                list: [{ title: 'All', value: '' }],
                                from: 'options',
                                fromData: { title: 'name' },
                                joinWith: 'values'
                            }
                        }
                    ],
                    preview: {
                        select: {
                            title: 'filter.title',
                            type: 'filter.type',
                            color: 'filter.color.color',
                            forOption: 'forOption'
                        },
                        prepare({ title = 'Untitled', type, color, forOption }) {
                            const displayType = type && type.trim() ? type : 'simple'
                            const option = forOption ? forOption.split(':') : null

                            return {
                                title,
                                subtitle:
                                    option && option.length > 1
                                        ? `${option[0]}: ${option[1]}`
                                        : 'All Variants',
                                media: getIcon(displayType, color?.hex.toUpperCase())
                            }
                        }
                    }
                }
            ],
            options: {
                editModal: 'popover'
            },
            validation: Rule => Rule.unique(),
            group: 'settings'
        },
        {
            title: 'SEO / Share Settings',
            name: 'seo',
            type: 'seo',
            group: 'seo'
        }
    ]
}
the part im working on is the initialvalue property
So you’re trying to have 2 specific authors as an initial value, right?
yup
I want it to eventually get all the new authors I also add
Right.
So I’m not sure how to populate initial references. I’m searching.
The image is populated as a reference indeed.
So what I would do is query your 2 authors or whatever initial documents. And then in JS, pass that structure.
initialValue: () => myDocuments.map(document => ({
  _type: 'reference',
  _ref: document._id
}))
I’m cutting some corners here, sorry.
But basically, I wouldn‘t mess with groq that much.
Interesting, i initially thought it would be some easy groq manipulation that i might be missing. i though i could make the authors properties and objects go into an array to make it look like this:   "authors": [
    {
      "_type": "reference",
      "_key": "e4072b1d3618",
      "_ref": "1fcfcd8f-db73-40a7-ac74-684a7a97a2e6"
    },
    {
      "_type": "reference",
      "_key": "e2d64d5f79e3",
      "_ref": "5f700b37-47cd-4a0f-b541-2176046ce962"
    }
  ]

but looks like its tougher than I thought. Truly appreciate you trying though, thank you
For now I will just set it manually like above instead of trying to fetch the authors through groq.
I think we misunderstood each other. I would still recommend fetching the documents with your client. I just wouldn‘t overthink the structure in the groq query and do it in JS instead.
Will give it a try but am a little confused how i should go about it, is there any examples of something similar?

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?