How to expand/resolve reference in Portable Text serializer mark?

8 replies
Last updated: Oct 15, 2021
Hello hello! Quick question:I have this cool link component which can link to internal documents or to external URLS:


import { HiLink } from "react-icons/hi";
import { LINK_REFERENCES } from "../config";

export default {
  name: "link",
  type: "object",
  title: "Link",
  blockEditor: {
    icon: HiLink,
  },
  description: "You shall only use one of these – trust me :)",
  fields: [
    {
      name: "external",
      type: "url",
      title: "URL",
      validation: (Rule) =>
        Rule.uri({
          scheme: ["http", "https", "tel", "mailto"],
          allowRelative: true,
        }),
      hidden: ({ parent, value }) => !value && parent?.internal,
    },
    {
      name: "internal",
      type: "reference",
      to: LINK_REFERENCES.map((type) => ({ type })),
      hidden: ({ parent, value }) => !value && parent?.external,
    },
  ],
};
Now I’m stuffing that into this blockContent component:

export default {
  title: "Block Content",
  name: "blockContent",
  type: "array",
  of: [
    {
      title: "Block",
      type: "block",
      styles: [
        ...
      ],
      lists: [
        ...
      ],
      marks: {
        decorators: [
          ...
        ],
        annotations: [
          {
            type: "link",
          },
        ],
      },
    },
    {
      type: "figure",
    },
    {
      type: "attachment",
    },
  ],
};
Now uhm I’m trying to figure out how to use that in the serializers of React Portabletext:

const link = ({ children, mark }) => {
  console.log({ mark });

  return <Link link={mark}>{children}</Link>;
};
which gives me this on the console:

{
  "mark": {
    "_key": "e602ef2fe9a9",
    "_type": "link",
    "internal": {
      "_ref": "127465e0-3999-4a66-b54d-fa42b0f020ce",
      "_type": "reference"
    }
  }
}
Is there a way to expand this reference ..?
AI Update

Looking at your code, you're running into the classic issue where references in Portable Text annotations aren't automatically expanded - you just get the reference object with _ref and _type.

There are two main approaches to solve this:

The cleanest solution is to expand the references when you fetch your content. You'll need to use GROQ's dereferencing operator (->) to expand references within your markDefs array:

*[_type == "yourDocType"] {
  ...,
  body[] {
    ...,
    markDefs[] {
      ...,
      _type == "link" => {
        ...,
        "internal": internal->{
          _id,
          _type,
          slug,
          title
          // Add whatever fields you need from the referenced document
        }
      }
    }
  }
}

This way, when your serializer receives the mark object, the internal property will contain the full document data instead of just a reference stub.

2. Fetch References Client-Side

If you can't modify your query, you can fetch the referenced document in your serializer component using Sanity's client:

import { useEffect, useState } from 'react';
import { client } from './sanityClient'; // your configured client

const link = ({ children, mark }) => {
  const [resolvedLink, setResolvedLink] = useState(null);

  useEffect(() => {
    if (mark.internal?._ref) {
      client.fetch('*[_id == $id][0]', { id: mark.internal._ref })
        .then(doc => setResolvedLink(doc));
    }
  }, [mark.internal?._ref]);

  const linkData = mark.external 
    ? { url: mark.external } 
    : resolvedLink;

  return <Link link={linkData}>{children}</Link>;
};

However, this approach can lead to performance issues with multiple references (lots of individual requests).

Best Practice

The first approach (expanding in your GROQ query) is strongly recommended because it:

The key insight from the Beginners guide to Portable Text is that Portable Text stores references in the markDefs array, and you need to explicitly tell GROQ to expand them using the -> operator. References aren't automatically expanded because that would be inefficient - you only expand what you actually need.

Show original thread
8 replies
ohh I guess I have to do that in the query
hold on šŸ”®
Got it thanks for rubber ducking šŸ™‚
user N
Could you share your query? I am having the same problem, only I can dereference anything but links in markdefs for some reason
Oh yes sure!
_type == "richText" => {
    _type,
    _key,
    headline,
    "content": content[] {
      ...,
      markDefs[] {
        ...,
        _type == "link" => {
          "internal": internal->slug.current,
          external
        }
      },
      _type == "attachment" => {
        ...,
        asset-> {
          extension,
          mimeType,
          size,
          url,
          "name": originalFilename,
          "createdAt": _createdAt,
          "updatedAt": _updatedAt,
        },
        author->,
      },
    }
  }
this is the complete query for blockContent in our setup. The interesting part is probably:
      markDefs[] {
        ...,
        _type == "link" => {
          "internal": internal->slug.current,
          external
        }
      },
Huh, thanks! For some reason my reference -&gt; won't work. Thanks!

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?