Including referenced document's slug field in Sanity query results

59 replies
Last updated: Apr 7, 2021
What I am hoping for is an object like this...
{
  "_key": "c371572bc417",
  "_type": "internalLink",
  "item": {
    "_ref": "532b3294-5c63-42bd-9157-174796b2f706",
    "_type": "reference"
  },
  "slug": "new-revolutionary-product"
}
AI Update

Looking at your desired object structure, you want to have both the reference object itself AND a specific field (the slug) from the referenced document. This is a common pattern in Sanity when you need to maintain the reference relationship while also having quick access to certain fields.

You can achieve this using GROQ's reference access operator (->) combined with projection. Here's how:

*[_type == "yourDocumentType"] {
  internalLinks[] {
    _key,
    _type,
    item,
    "slug": item->slug.current
  }
}

This keeps your item field as the reference object (with _ref and _type), while also dereferencing to grab the slug and projecting it as a separate field at the same level.

If your referenced document has a slug object (like slug.current), use the syntax above. If it's a direct string field, just use item->slug.

Alternative approach - if you want to be more explicit about the reference structure:

*[_type == "yourDocumentType"] {
  internalLinks[] {
    _key,
    _type,
    item {
      _ref,
      _type
    },
    "slug": item->slug.current
  }
}

The key here is using the -> operator to dereference and access fields from the referenced document, while keeping the original reference intact. The -> operator internally executes a subquery to fetch the referenced document's data, so you can pull out any field you need (slug, title, etc.) while maintaining the reference relationship.

This pattern is particularly useful when you need the reference for mutations/updates but also want to display data without additional queries on the frontend.

Show original thread
59 replies
i think you are overcomplicating it,
_type == "internalLink" => {
            ...,
            "slug": *[_type == "route" && page._ref == ^.item._ref]{slug}
          }
could simply be

_type == "internalLink" => {
            ...,
            "slug": item->slug.current
          }

theres no need to do ^ inside of a reference expansion here
unless i am missing part of your data structure somewhere
Thank you, I'll try this too.
This returns NULL for the value.
what does your internal link object look like in your text block?
[
  {
    "_key": "2fc431634c3b",
    "_type": "link",
    "href": "<https://bryanklein.com>"
  },
  {
    "_key": "c371572bc417",
    "_type": "internalLink",
    "item": {
      "_ref": "532b3294-5c63-42bd-9157-174796b2f706",
      "_type": "reference"
    },
    "slug": null
  }
]
i mean in like the text block in sanity not the query
        annotations: [
          {
            name: 'internalLink',
            type: 'object',
            icon: AiOutlineLink,
            title: 'Internal link',
            fields: [
              {
                name: 'reference',
                type: 'reference',
                title: 'Reference',
                to: [
                  { type: 'page' },
                  { type: 'product' }
                ]
              },
              {
                name: 'linkType',
                title: 'Link Type',
                type: "string",
                options: {
                  list: [
                    { title: "Button", value: "button" },
                    { title: "Text", value: "text" },
                    { title: "Text + Arrow", value: "text-arrow" }
                  ],
                  layout: "dropdown"
                }
              }
            ]
          }, 
example
The schema for it?
yeah the text schema
import React from 'react';

const InternalLinkRender = ({ children }) => <span style={{backgroundColor:'lightblue'}}>{children}</span>;

export default {
  title: 'Internal Link',
  name: 'internalLink',
  type: 'object',
  description: 'Locate a document you want to link to',
  fields: [
    {
      name: 'item',
      type: 'reference',
      to: [{ type: 'page' }]
    }
  ],
  blockEditor: {
    icon: () => 'šŸ”—',
    render: InternalLinkRender,
  },
};
what happens when you just try item-&gt; without the additional notation
It returns the page object that the 'item' refers to, but it doesn't have a slug value.
"slug":{
  "_createdAt": "2019-03-15T13:53:45Z",
  "_id": "532b3294-5c63-42bd-9157-174796b2f706",
  "_rev": "45Isps23253Yjlaq27Gb6c",
  "_type": "page",
  "_updatedAt": "2021-03-25T17:35:31Z",
  "content": [
    {
      "_key": "e37000e4aea5",
      "_type": "hero",
      "backgroundImage": {
        "_type": "image",
        "asset": {
          "_ref": "image-99daef03a3557b742dd9de05e3b75aba0ad2a402-1350x900-png",
          "_type": "reference"
        }
      },
      "heading": "We change industries!",
      "tagline": [
        {
          "_key": "2f2895682c0b",
          "_type": "block",
          "children": [
            {
              "_key": "2f2895682c0b0",
              "_type": "span",
              "marks": [],
              "text": "Disruptive implementations for the unknown future."
            }
          ],
          "markDefs": [],
          "style": "normal"
        }
      ]
    }
  ],
  "description": "This will change how you think about technology. We just need to figure out what it is first.",
  "openGraphImage": {
    "_type": "image",
    "asset": {
      "_ref": "image-99daef03a3557b742dd9de05e3b75aba0ad2a402-1350x900-png",
      "_type": "reference"
    }
  },
  "title": "Our upcoming groundbreaking service"
}
does that page… have a slug in the system?
Because in this example the slugs are stored in the 'routes' and not in the page.
gotcha so theres another level going on here
so you put all your pages in routes, and in the route you have a slug referencing the linked page
Exactly.
for stuff like this truthfully
 *[_type == ā€œrouteā€ && page._ref == ^.item._ref]
stick that in vision
with the proper item ref string
if it doesn’t work there something else might be going on
also i believe the ^ now fetches the proper parent and isn’t sub local anymore?
but i could be wrong depending on your api version
✨
I was getting messed up with the {} thinking that I needed to wrap the projection with them.
(I am day 2 with groq, so please excuse my ignorance)
you’re doing something with routes i’ve never seen
so kudos to whatever is going on there interested to see the why/how of it all
Yeah, it's a bit funky. That's what they are doing in the Sanity+Next.js+Netlify starter.
i wonder how they handled typed routes for like products/blogs etc there but i guess i’ve never used that starter
dang that’s like super interesting, I’ll have to harass knut about this
this is potentially solving a like large complex data structure issue i have with a few projects. huge fan of hoping into this thread.
I was adding internal links into the rendering of the website and ran into this nested relationship. https://github.com/sanity-io/sanity-template-nextjs-landing-pages/blob/master/template/web/pages/LandingPage.js
yeah i mean this pattern absolutely adds complexity
but if wrangled seems very powerful
Where I have to start by querying the route and through it to the page I am on, then on down through the content/text/etc. to the links.
yeah you’ll need to ensure you have the reference type too
the example currently only has a page* but imagining other routes that needed different nested structure
Yep, I think you would need different queries for each datatype.
Right now it is using the
pageQuery
for page data. But I could see something like that for different elements.
so this:

"slug": *[_type == "route" && page._ref == ^.item._ref][0].slug.current

you could open up to graph the type of the reference which is a deeper further nested query
Yeah, I would need to be careful with
page._ref
(depending on what you are after your life might be easier adding slugs to the page content type)
Yeah, I assume that this separation of the routes from the pages was to allow for some flexibility in what was presented in the sitemap, menus, etc.
Because there are toggles for that.
yeah
Thank you for jumping in to help on this. I spent too much time running into dead ends before I reached out.
Now I can do this in my serializer.
const serializers = {
  types: {
    embedHTML: EmbedHTML,
    figure: Figure
  },
  marks: {
    internalLink: ({mark, children}) => {
      const href = `/${mark.slug}`
      return <a href={href}>{children}</a>
    },
    link: ({mark, children}) => {
      // Read <https://css-tricks.com/use-target_blank/>
      const { blank, href } = mark
      return blank ?
        <a href={href} target="_blank" rel="noopener noreferer">{children}</a>
        : <a href={href}>{children}</a>
    }
  }
}
Works like a charm. šŸ˜„

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?