How to make a reference inside an array of arrays in Slack thread

28 replies
Last updated: Apr 25, 2022
How can I make a reference inside an array of arrays ?
*[_type == 'presentation'][0]{
  projects[]-> {
    'presentationSlides': presentationSlides[].images, 
  },
}

presentationSlides[].images
in this case is an array of images. What I want is keep the array inside
presentationSlides
but just have the url.
So the end result looks like:

{
  projects: [
    {
      presentationSlides: [
        [
          'https://...',
          'https://...',
        ],
        [
          'https://...',
          'https://...',
        ],
      ]
    }
  ]
}
AI Update

Preserving nested array structure in GROQ when dereferencing images is tricky because GROQ doesn't have a built-in "map" function that preserves two-dimensional arrays. The double bracket pattern (presentationSlides[].images[]) flattens everything into a single array, which you've already discovered.

Unfortunately, GROQ doesn't have native support for preserving array-of-arrays structure when you need to dereference or transform nested arrays. The [] projection operator always flattens when you chain it.

Here are your practical options:

Option 1: Handle the transformation client-side (Recommended)

Query the data in a queryable structure, then reshape it in your application code:

*[_type == 'presentation'][0]{
  projects[]-> {
    presentationSlides[]{
      images[].asset->url
    }
  }
}

Then in your JavaScript/TypeScript:

const result = await client.fetch(query);
const formatted = {
  projects: result.projects.map(project => ({
    presentationSlides: project.presentationSlides.map(slide => slide.images)
  }))
};

Option 2: Use GROQ's select() function with indices

If you have a known, fixed number of slides, you can manually construct the array structure:

*[_type == 'presentation'][0]{
  projects[]-> {
    'presentationSlides': [
      presentationSlides[0].images[].asset->url,
      presentationSlides[1].images[].asset->url,
      presentationSlides[2].images[].asset->url
    ]
  }
}

This is verbose and only works when you know the exact number of items ahead of time.

Option 3: Restructure your schema

If this is a recurring pattern, consider whether your content model could be adjusted to make querying easier. For example, storing URLs directly or flattening the structure at the schema level.

Why the limitation exists:

According to the GROQ pipeline components documentation, array projections with [] iterate over elements and return a new array. When you nest these operations (array[].nestedArray[]), GROQ flattens the results into a single-dimensional array. There's no syntax that tells GROQ to "preserve the outer array structure while transforming the inner arrays."

The most practical approach for your use case is Option 1 - keep your GROQ query relatively simple and do the final array reshaping in your application code where you have full control over data structures.

I'm trying to do
presentationSlides[].images[].asset->url
but it kills one level and do something like this:
{
  projects: [
    {
      presentationSlides: [
        'https://...',
        'https://...',
        'https://...',
        'https://...',
      ]
    }
  ]
}
So, FYI, nested arrays aren’t supported. There is a very good, basic data processing reason for this that I’m not sure I can easily state, but it’s a good reason!
The schema work arround:
• instead of
array[array[]]
• nest an object containing arrays in the schema
array[object{array[]}]
You should be able to transform the output in a groq query to something resembling what you want with some nested queries, joins, and mutations. Start
here for this.
I haven’t tried pushing this to its limits, but there may be a UI limitation to how many nested levels you can go. Here’s a schema snip of arr>obj>arr :
{
      name: 'arr1',
      title: 'Array One Test',
      type: 'array',
      of: [
        {
          name: 'object1',
          title: 'Object One Test',
          type: 'object',
          fields: [
            {
              name: 'arr2',
              title: 'Array Two Test',
              type: 'array',
              of: [
                {type: 'string'}
              ]
            }
          ]

        }
      ]
    },
Honestly, if you’re going deeper than this example, spreading out your content would be advised for the sake of easier management regardless. The middle-object also allows you to add multiple fields in the input modal nicely, so you can have a few different named arrays or other types per object within the top level array.
Chatting with
user A
about this on the side. In case you just meant just creating the desired output with a query:

If I understand the question, this is a bit different from the ‘arrays inside arrays’ limitation. It sounds like they want to change this:
```*[_type == 'presentation'][0]{
projects[]-> {
'presentationSlides': presentationSlides[].images,
},
}```
to something like this:
```*[_type == 'presentation'][0]{
'presentationSlides': projects[]->presentationSlides[].images,
}```
I haven’t tested the above but believe it would do what they’re after.
user M
This gives me
null
for each elements in the array.
user U
Thanks for testing this out. The solution you shared give me the exact same result unfortunately.
user D
I don't have your schema, so it won't be exactly the same. You'll need to apply this logic to your own structure.
That's the issue. It seems that you have a single image in each
presentationSlide
. For me I have an array of images. I don't understand at this point how to get only the url of each image in the array.
Just a few tests here: https://cln.sh/OHIsUV I don't get why if I change it to
images[].asset->url
it takes all the content and put it in the array above.
If you change
image
to
images[]
in Racheal’s snippet, do you get what you’re after at the level you want it?
He'll get a flat array, not the url's separated by slide, I believe.
Yes that's the issue. I get a flat array with 3 objects inside instead of having 2 objects (first one having 2 objects and second one having 1 object).
Ahh, like this then?

*[_type == 'presentation'][0]{
  projects[]->{
    presentationSlides[]{
      'presentationSlides': images[].asset->url
    }
  }
}
Ah that's closer but now it give me an object
presentationSlides
in each object in the array.
(by the way thanks a LOT for helping me crack this one)
Does it have to be an object in each element of the array? Can't it be an array of arrays ? If not this solution is something I could work with.
If not this solution is something I could work with.
I don't think there's any way of getting around having a named field for your array of urls if you want them in separate objects, like in Geoff's query.
The only way I can think might work is to do a naked projection to get the hierarchy you want, and then use the parent operator to break out of that depth for the things you need that are siblings of
presentationSlides
. This might not be performant and I don’t have your schema to confirm, but going from the video you posted you might try something like:

Edit: I just replicated how I think you have your schema, and this doesn’t look like it’ll work.


*[_type == 'presentation'][0]{
  ...,
  'cover': cover.asset->url,
  'slug': slug.current,
  'projects': projects[]->presentationSlides[]{
    'title': ^.title,
    'slug': ^.slug.current,
    'client': ^.projects[]->client->{
      title,
      'slug': slug.current,
    },
    'presentationSlides': images[].asset->url
  }
}
That said, the more I look at it, the more I think my colleagues who work on GROQ would cringe.
Alex is going to read this and just start crying
That’s who I was thinking of. 😅
Oh. I surely don't want to make anyone cry about it. The last solution you shared
user A
is mixing my data not the right way. I'm setting a list of presentationSlides in each projects. And in each slide I have an array of images.
So the array of images is part of the array of slide. And this slide is part of an array in the project. If you want I could share my entire schema.
export default {
  title: "Projects",
  name: "project",
  type: "document",
  initialValue: {
    theme: "light",
    priority: "medium",
  },
  groups: [
    {
      title: "Project Description",
      name: "projectDescription",
    },
    {
      title: "Case Study",
      name: "caseStudy",
    },
    {
      title: "Presentation",
      name: "presentation",
    },
  ],
  fields: [
    {
      title: "Cover",
      name: "cover",
      type: "image",
      group: "projectDescription",
    },
    {
      title: "Project Title",
      name: "title",
      type: "string",
      group: "projectDescription",
    },
    {
      title: "Project Slug",
      name: "slug",
      type: "slug",
      options: {
        source: "title",
      },
      group: "projectDescription",
    },
    {
      title: "Client",
      name: "client",
      type: "reference",
      to: [{ type: "client" }],
      group: "projectDescription",
    },
    {
      title: "Theme",
      name: "theme",
      type: "string",
      options: {
        list: [
          { title: "Light", value: "light" },
          { title: "Dark", value: "dark" },
        ],
      },
      group: "projectDescription",
    },
    {
      title: "Priority",
      name: "priority",
      type: "string",
      options: {
        list: [
          { title: "High", value: "high" },
          { title: "Medium", value: "medium" },
          { title: "Low", value: "low" },
        ],
      },
      group: "projectDescription",
    },
    {
      title: "Tags",
      name: "tags",
      type: "array",
      of: [
        {
          type: "reference",
          to: [{ type: "tag" }],
        },
      ],
      group: "projectDescription",
    },
    {
      title: "Blocks",
      name: "blocks",
      type: "array",
      group: "caseStudy",
      of: [
        {
          title: "Text Block",
          name: "textBlock",
          type: "document",
          initialValue: {
            title: "Text block",
          },
          fields: [
            { title: "Title", name: "title", type: "string" },
            {
              name: "text",
              title: "Text",
              type: "array",
              of: [{ type: "block" }],
            },
            {
              title: "Start",
              name: "start",
              type: "number",
            },
            {
              title: "Width",
              name: "width",
              type: "number",
            },
          ],
        },
        {
          title: "Gallery",
          name: "gallery",
          type: "document",
          initialValue: {
            title: "Gallery",
          },
          fields: [
            { title: "Title", name: "title", type: "string" },
            {
              title: "Images",
              name: "images",
              type: "array",
              of: [{ type: "image" }],
            },
          ],
        },
      ],
    },
    {
      title: "Presentation Slides",
      name: "presentationSlides",
      type: "array",
      group: "presentation",
      of: [
        {
          title: "Slide",
          name: "slide",
          type: "document",
          initialValue: {
            title: "Slide",
          },
          fields: [
            {
              title: "Title (Not visible on the website)",
              name: "title",
              type: "string",
            },
            {
              name: "images",
              title: "Images",
              type: "array",
              of: [{ type: "image" }],
            },
          ],
        },
      ],
    },
  ],
};
export default {
  title: "Presentations",
  name: "presentation",
  type: "document",
  fields: [
    {
      title: "Title",
      name: "title",
      type: "string",
    },
    {
      title: "Slug",
      name: "slug",
      type: "slug",
      options: {
        source: "title",
      },
    },
    {
      title: "Cover",
      name: "cover",
      type: "image",
    },
    {
      title: "Intro",
      name: "intro",
      type: "text",
    },
    {
      title: "Projects",
      name: "projects",
      type: "array",
      of: [
        {
          type: "reference",
          to: [{ type: "project" }],
        },
      ],
    },
  ],
};
Hi all! Your challenge here is that
[]
is a flattening operator, so any kind of expression like
array[].nestedArray[]
will cause the inner array to be flattened out.
You should be able to use this workaround. It’s not pretty, but I think it should work for you:

*[_type == 'presentation'][0]{
  projects[]-> {
    'presentationSlides': presentationSlides[] {
      'urls': images[]->url
    }.urls
  },
}
This “escapes” the flattening by doing the image-to-URL mapping inside an object, and then unwrapping it.
Ah that's exactly what I was looking for. Thanks a lot
user L
. You forgot
.asset
so just for reference this is what worked:
*[_type == 'presentation'][0]{
  projects[]-> {
    'presentationSlides': presentationSlides[] {
      'urls': images[].asset->url
    }.urls
  },
}
Ah, of course, sorry. 👍

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?