Handling references in portable text in GraphQL without Gatsby Source plugin

4 replies
Last updated: Mar 10, 2023
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
but can’t seem to figure out how to do it in just plain GraphQL.
Mar 9, 2023, 4:26 AM
If you’re using the Gatsby Source plugin you can automatically resolve references on your raw fields . I’m not sure if this is true outside of the plugin, though.
Mar 9, 2023, 4:29 PM
I am not using Gatsby 🥲
Mar 9, 2023, 6:06 PM
If anyone searches this in the future, here’s my solution:

import { sanityApiServer } from "@/lib/sanity";

 * Recursively iterates over an object looking for markDefs keys and resolving internal links to Sanity references.
 * @template T - The type of the object being resolved.
 * @param {T} object - The object to resolve.
 * @returns {Promise<T>} - A Promise that resolves to the modified object with internal links resolved.
export async function resolveSanityTasks<T>(object: T): Promise<T> {
  // Recursively iterate over the object looking for keys called "markDefs"
  for (const KEY in object) {
    if (KEY === "markDefs") {
      // Extract the markDefs array
      const MARK_DEFS = object[KEY] as any[];
      if (Array.isArray(MARK_DEFS)) {
         * Loop over all the markDefs and search for internal links.
         * If they exist, query Sanity for the reference slugs.
        object[KEY] = (await Promise.all(
          MARK_DEFS.map(async (item) => {
            // Check if the item is an internal link with a reference slug
            if (
              "_type" in item &&
              item._type === "internalLink" &&
              "reference" in item &&
              "_ref" in item.reference &&
              typeof item.reference._ref === "string"
            ) {
              try {
                // Query Sanity for the reference slug using the ID
                const RESPONSE = await sanityApiServer.fetch(`*[_id == "${item.reference._ref}"] { _type, slug }`);

                // Check that the query response is valid
                if (
                  Array.isArray(RESPONSE) &&
                  RESPONSE.length > 0 &&
                  typeof RESPONSE[0] === "object" &&
                  "_type" in RESPONSE[0] &&
                  typeof RESPONSE[0]._type === "string" &&
                  "slug" in RESPONSE[0] &&
                  typeof RESPONSE[0].slug === "object" &&
                  "current" in RESPONSE[0].slug &&
                  typeof RESPONSE[0].slug.current === "string"
                ) {
                   * We need to capitalize the type to match
                   * the type returned from GraphQL
                  const TYPE = RESPONSE?.[0]._type;
                  const CAPITALIZED_TYPE = TYPE.charAt(0).toUpperCase() + TYPE.slice(1);

                  // Update the item with the reference slug and type
                  return {
                    __typename: CAPITALIZED_TYPE,
                    slug: RESPONSE?.[0].slug,
                } else {
                  // If the query response is not valid, return the original item
                  return item;
              } catch (errResponse) {
                // If there is an error with the query, throw an error
                throw new Error();
            } else {
              // If the item is not an internal link with a reference slug, return the original item
              return item;
        )) as T[Extract<keyof T, string>];
      } else {
        // If the markDefs array is not valid, recursively call this function on the object
        object[KEY] = (await resolveSanityTasks(object[KEY])) as T[Extract<keyof T, string>];
    } else if (typeof object[KEY] === "object") {
      // If the value of this key is an object, recursively call this function on that object
      object[KEY] = (await resolveSanityTasks(object[KEY])) as T[Extract<keyof T, string>];
  // Return the modified object
  return object;
Mar 10, 2023, 12:36 AM
And I hate it.
Mar 10, 2023, 2:12 AM

Sanity– build remarkable experiences at scale

The Sanity Composable Content Cloud is the headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?