GROQ query not adding fields to markDefs for internal links

11 replies
Last updated: Dec 1, 2025
Hello! I'm trying to implement the internal link as explained here: https://www.sanity.io/guides/portable-text-internal-and-external-links
When I want to add the slug using a the GROQ query given, it does not seem to work. Nothing is added to the markDefs object when I test it in Vision:

*[_type == "post"]{
  ...,
  body[]{
    ...,
    markDefs[]{
      ...,
      _type == "internalLink" => {
        "slug": @.reference->slug
      }
    }
  }
}
In fact, even if I type something directly in the query, nothing is added to the markDefs:


*[_type == "page"]{
  ...,
  body[]{
    ...,
    markDefs[]{
      ...,
     _type == "internalLink" => {
        "something": "test"
      }
  }
}
Even if I remove the _type == "internalLink", it does not get written into the result.

Can somebody please help me and tell me what I am doing wrong?
AI Update

I can see the issue you're running into! The problem is actually simpler than you might think. Your GROQ query syntax is correct, but the conditional projection with => isn't adding fields to your result because there are no markDefs objects matching your condition or the reference isn't resolving properly.

Let's debug this step by step:

Why your query isn't working

The conditional projection syntax _type == "internalLink" => { "slug": @.reference->slug } is correct according to the official guide you're following. The => operator expands the result into the parent projection when the condition is true.

However, there are several common reasons why you're seeing no results:

1. No matching annotations exist

The most common issue: you might not have any markDefs with _type == "internalLink" in your content.

Test this first:

*[_type == "post"][0].body[].markDefs[]

This will show you the raw structure of your markDefs. Look for objects where _type == "internalLink" exists. If you don't see any, you need to add some internal links in your editor first.

2. The reference field name is different

Your schema might use a different field name than reference. Check your annotation schema definition - it could be item, document, ref, or something else.

3. Slug is an object, not a string

In Sanity, slug fields are typically objects like {current: "my-slug"}, not strings. Try:

*[_type == "post"]{
  ...,
  body[]{
    ...,
    markDefs[]{
      ...,
      _type == "internalLink" => {
        "slug": @.reference->slug.current
      }
    }
  }
}

4. The reference is broken

If the referenced document was deleted, @.reference-> returns null. Test with:

*[_type == "post"]{
  body[]{
    markDefs[]{
      ...,
      _type == "internalLink" => {
        "refExists": defined(@.reference->_id),
        "slug": @.reference->slug.current
      }
    }
  }
}

About your "something": "test" example

When you tried this:

markDefs[]{
  ...,
  _type == "internalLink" => {
    "something": "test"
  }
}

If this didn't add anything, it means no markDefs objects have _type == "internalLink". The conditional only executes when the condition is true. If there are no matching objects, nothing gets added.

The correct mental model

The => operator works like this: "When this condition is true, expand this object into the parent projection." It doesn't create a new object scope that needs spreading - it merges the result directly into the parent.

So your original query structure is correct:

markDefs[]{
  ...,                              // All existing fields
  _type == "internalLink" => {      // IF this is true, THEN...
    "slug": @.reference->slug.current  // ...add this field
  }
}

Start with the debugging queries above to see what's actually in your markDefs, and you'll quickly find where the disconnect is. Most likely, you either don't have any internal link annotations yet, or the field names don't match what you expect.

Show original thread
11 replies

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?