How to handle internal document references in Portable Text with GraphQL?

4 replies
Last updated: Nov 29, 2025
In GraphQL how do you handle references in portable text e.g handling a link to an internal document? The portable text field is returned as RAW JSON with a ref. In GROQ you can grab the referenced document in the
markDefs
but can’t seem to figure out how to do it in just plain GraphQL.
AI Update

When working with Portable Text in Sanity's GraphQL API, handling references in annotations (like internal links in markDefs) requires a specific schema configuration approach.

The Key Solution: Hoist Annotations as Schema Types

The most important thing to know is that if you plan to use Sanity's GraphQL API, you should hoist your annotation types (like internalLink) as separate schema types instead of defining them inline.

Here's the difference:

❌ Inline definition (works in GROQ, problematic in GraphQL):

{
  type: 'block',
  marks: {
    annotations: [
      {
        name: 'internalLink',
        type: 'object',  // inline object definition
        title: 'Internal link',
        fields: [
          {
            name: 'reference',
            type: 'reference',
            to: [{ type: 'post' }]
          }
        ]
      }
    ]
  }
}

✅ Hoisted type (works well with GraphQL):

First, create a separate schema type:

// schemas/internalLink.js
export default {
  name: 'internalLink',
  type: 'object',
  title: 'Internal link',
  fields: [
    {
      name: 'reference',
      type: 'reference',
      to: [{ type: 'post' }]
    }
  ]
}

Then reference it in your block configuration:

{
  type: 'block',
  marks: {
    annotations: [
      {
        type: 'internalLink'  // reference the hoisted type
      }
    ]
  }
}

Why This Matters

When you hoist the annotation as a schema type, Sanity's GraphQL API can properly generate the necessary types and resolvers to handle the reference. This allows you to query the referenced document directly in your GraphQL queries, similar to how you would use the -> operator in GROQ to dereference in markDefs.

With the hoisted type, your GraphQL query can resolve the reference properly, whereas inline object definitions don't get the same treatment in the GraphQL schema generation. The GraphQL API needs explicit schema types to create the proper resolvers for nested references.

This approach is specifically mentioned in the official Sanity documentation on configuring the Portable Text Editor, which states: "If you plan to use Sanity's GraphQL API, you should hoist internalLink as a schema type, and use type: 'internalLink' as the annotation, instead of the anonymous example above."

Show original thread
4 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?