Handling references in portable text in GraphQL without Gatsby Source plugin
4 replies
Last updated: Mar 10, 2023
R
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
markDefsbut 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
R
I am not using Gatsby 🥲
Mar 9, 2023, 6:06 PM
R
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 {
...item,
__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
R
And I hate it.
Mar 10, 2023, 2:12 AM
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.
