· Jesse McLean
Improving Type-Safety for Prismic Data
A small TypeScript helper that checks a Prismic content relationship points at the document type you expect, with the fields you asked for.
We've built sites on Prismic for years, and link fields are one of the places where content and code can quietly disagree. This is a small TypeScript helper I wrote to make content relationships type-safe. The original inspiration came from a post by angeloashmore on the Prismic community forum.
The Challenge
Prismic's flexible content modelling is great, and that flexibility has a cost. A link field can be empty, or point at a different document type than the one you expected. Without a type check, you only find out when something breaks at runtime.
The Solution
The snippet below wraps Prismic's isFilled.contentRelationship function. It checks that a link field is filled, and uses TypeScript to confirm the linked document is the type you expect and carries the fields you asked for.
/**
* Type-safe wrapper around Prismic's `isFilled.contentRelationship` function to make link fields more type-safe.
* You'll want to make sure that your Prismic client is set up to handle typed content.
*
* If using Next.js and Slice Machine, you can use the `@prismicio/client` package to set up typed content.
* @see {@link https://prismic.io/docs/typescript-nextjs} for more information.
*
* Prismic also offers support via the `prismic-ts-codegen` package.
* @see {@link https://prismic.io/docs/technical-reference/prismic-ts-codegen} for more information.
*/
import {
type Content,
type FilledContentRelationshipField,
isFilled,
type LinkField,
} from "@prismicio/client";
// Extracts the data of a specific document type from Prismic content
type DocumentData<TDocumentType extends Content.AllDocumentTypes["type"]> =
Extract<Content.AllDocumentTypes, { type: TDocumentType }>["data"];
/**
* Checks if a link field in Prismic is a filled content relationship field and contains the specified field IDs.
* This is useful for checking if a link field is a valid link to a specific document type and contains the specified field IDs.
* This acts as a wrapper around the `isFilled.contentRelationship` function from Prismic to make link fields more type-safe.
*
* @template TDocumentType - The type of the document.
* @template TFieldID - The field IDs to check within the document data.
* @param {LinkField} linkField - The link field to check.
* @param {TDocumentType} documentType - The type of the document to match against.
* @param {TFieldID[]} fieldIDs - The field IDs to check within the document data.
* @returns {linkField is FilledContentRelationshipField & { data: { [P in keyof DocumentData<TDocumentType> as P extends TFieldID ? P : never]: DocumentData<TDocumentType>[P]; } }} - Returns true if the link field is a filled content relationship field and contains the specified field IDs.
*
* @example
* ```ts
* import { isFilledLinkedContent } from "./is-filled-linked-content";
*
* const isValidLink = isFilledLinkedContent(linkField, "article", ["title"]);
* ```
*/
export function isFilledLinkedContent<
TDocumentType extends Content.AllDocumentTypes["type"],
TFieldID extends keyof DocumentData<TDocumentType>,
>(
linkField: LinkField,
documentType: TDocumentType,
fieldIDs: TFieldID[],
): linkField is FilledContentRelationshipField & {
data: {
[P in keyof DocumentData<TDocumentType> as P extends TFieldID
? P
: never]: DocumentData<TDocumentType>[P];
};
} {
return (
// Check if the link field is a filled content relationship field
isFilled.contentRelationship(linkField) &&
// Check if the link field's type matches the specified document type
linkField.type === documentType &&
// Ensure the link field's data is an object and not null
typeof linkField.data === "object" &&
linkField.data !== null &&
// Check if all specified field IDs exist in the link field's data
fieldIDs.every(
(fieldID) => fieldID in (linkField.data as Record<string, unknown>),
)
);
}Why This Matters
A link field that passes isFilledLinkedContent is typed as the document you expected, fields included, so the compiler catches a wrong assumption before a visitor does.
Where it fits with Slice Machine
Slice Machine already handles a lot of the modelling side. We build slices visually and it generates their types. This helper covers a gap it leaves, which is the shape of the document a content relationship points to.
The general rule
Every content variant in a CMS costs schemas, type signatures and queries, and if something can happen, code has to handle it. A link that might point at the wrong document is one of those cases, and isFilledLinkedContent handles it in one place, so the rest of the code can trust the type.


