How to preserve custom order in array of references in Sanity schema?

6 replies
Last updated: Dec 14, 2022
I have a quick question on sorting. I have a Featured Projects settings of my portfolio. I setup the schema to have the Project Settings Schema be an array of references to Projects. However, when listed it orders them by _id (I know this from referencing the v3 docs) even when I change the order if projects in the array. Is there a way to add a custom index value to each reference in the array when adding it? I could then to the order() function to order by that?
AI Update

Great question! When you have an array of references in Sanity, the order is actually preserved by the array itself through the _key property that each item has. The issue you're seeing where items appear ordered by _id is likely happening in your GROQ query, not in the stored data.

The solution: Use the correct GROQ syntax

When you dereference an array of references, you need to use []-> to maintain the array order:

*[_id == "your-settings-id"]{
  featuredProjects[]->{
    _id,
    title,
    // other fields
  }
}

The []-> syntax tells GROQ to:

  1. Iterate through the array in its stored order
  2. Dereference each item
  3. Return them in the same order

Preserving the _key while dereferencing

If you also need access to the _key property (which determines the order), you can use this pattern:

*[_id == "your-settings-id"]{
  featuredProjects[]{
    _key,
    ...(@->)
  }
}

This keeps the _key from the reference object and spreads all properties from the dereferenced document.

The underlying structure

Your array of references in Sanity looks like this internally:

{
  "featuredProjects": [
    {
      "_key": "abc123",
      "_type": "reference",
      "_ref": "project-id-1"
    },
    {
      "_key": "def456", 
      "_type": "reference",
      "_ref": "project-id-2"
    }
  ]
}

The _key property is automatically generated and maintains the order. When you reorder items in the Studio array input, Sanity preserves this order through the array structure itself—you don't need to add a custom index field.

Filtering out drafts

One bonus tip: if you want to exclude draft documents from your query results, add this filter:

*[_id == "your-settings-id" && !(_id in path("drafts.*"))]{
  featuredProjects[]->{
    // fields
  }
}

The order you set in the Studio will be maintained in your query results without any additional order() function needed!

Show original thread
6 replies
Hey
user F
! An array should return items in the order that they were added (or the order that you've manually sorted them into). That is, unless you've changed the sortable option to false. Is this not the behavior you're getting?
hey
user M
! Thanks for the quick response! I don't seem to be getting this behavior. This is my schema:

export default {
title: 'Featured Projects',
name: 'featuredProjects',
type: 'document',
fields: [
{
title: 'Featured Projects',
name: 'featuredProjects',
type: 'string',
hidden: true,
},
{
name: 'projects',
title: 'Featured Projects',
type: 'array',
of: [
{
type: 'reference',
to: [
{
type: 'projects',
},
],
options: {
disableNew: true,
},
},
],
validation: (
_Rule_: _any_) =>

_Rule_.max(4).warning('Only 4 Featured Projects Allowed'),
},
],
}
Tried it again: Should be listing the Pomodoro App first and then the Test App
user M
I think I know what is wrong. It's my query. If I query the _type == "featuredProjects" the result is in order as in the array. However, if I do a left join it lists them in order by _id. Is there a better way to do this other than a Left Join?
user M
I fixed it 🙂

*[_type == "featuredProjects"]{
projects[]->{
"name": projectName,
"slug": slug.current,
excerpt,
gitHubUrl,
liveSiteUrl,
"image": image.asset._ref,
"tags": tags[]->{
name
}
}
}
Awesome! Glad you got it figured out!

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?