---
title: "Content Relationship"
description: "This article explains what the content relationship field is and how to configure it."
meta_title: "Content Relationship"
category: "fields"
audience: developers
lastUpdated: "2026-01-10T02:37:42.000Z"
---

Content writers can link pages through content relationship fields. Developers can then access content from those pages.

Content relationship fields are often used for:

* Connecting a blog post to its **author**.
* Displaying a **testimonial** on a landing page.
* Linking a product to its **category**.

Content from related pages can be accessed like any other field. Use `isFilled.contentRelationship()` to ensure the relationship has a value before accessing nested content.

**Next.js example:**

```tsx
import { isFilled } from "@prismicio/client";
import { PrismicText } from "@prismicio/react";

{
  isFilled.contentRelationship(post.data.author) && (
    <p>
      Written by <PrismicText field={post.data.author.data?.name} />
    </p>
  );
}
```

**Nuxt example:**

```vue-html
<p v-if="$prismic.isFilled.contentRelationship(post.data.author)">
  Written by <PrismicText :field="post.data.author.data?.name" />
</p>
```

**SvelteKit example:**

```svelte
<script lang="ts">
  import { isFilled } from "@prismicio/client";
  import { PrismicText } from "@prismicio/svelte";
</script>

{#if isFilled.contentRelationship(post.data.author)}
  <p>
    Written by <PrismicText field={post.data.author.data?.name} />
  </p>
{/if}
```

> The [link field](https://prismic.io/docs/fields/link.md) and the content relationship field look similar. Link fields should be used to create web links, like a "Learn More" link. Content relationship fields should be used to create data structures, like a blog post pointing to its author.

# Add a content relationship to a content model

1. **Open Slice Machine**

   In your Prismic project, start Slice Machine to begin editing content models.

   ```sh
   npx start-slicemachine --open
   ```

2. **Add a content relationship field**

   In Slice Machine, navigate to the slice, page type, or custom type you want to modify. Add a **content relationship** field.

   The **label** determines the label shown to content writers in the [Page Builder](https://prismic.io/docs/guides/page-builder.md). Use an easily understood name.

   The **API ID** determines the property name in the Content API. Use a short, snake-cased name.

3. **Select the allowed page type**

   You can restrict the field to a specific page type. For example, you can configure the field to only allow Author pages in an Author field.

   In the field's settings, click the **Add type** button to select a page type.

   You'll be able to select fields from that page type to include in the API response in the next step.

4. **Select the fields to fetch**

   If you restrict the field to [a specific page type](#select-the-allowed-page-type), you can select which fields to include in the API response.

   Under the **Allowed type** section, use the field picker to choose which fields should be included. Up to 2 levels of nested fields can be selected (e.g. `blog` -> `author` -> `profession`).

   The selected fields are included in Slice Machine's generated TypeScript types.

   > **Important**
   >
   > Slice Zone fields cannot be fetched from content relationships.

# Display content relationships

Content from a related page can be displayed like any other field. The name of a blog post's author, for example, can be rendered using the `<PrismicText>` component.

**Next.js example:**

```tsx
import { isFilled } from "@prismicio/client";
import { PrismicText } from "@prismicio/react";

{
  isFilled.contentRelationship(post.data.author) && (
    <p>
      Written by <PrismicText field={post.data.author.data?.name} />,
      {isFilled.contentRelationship(post.data.author.data?.profession) && (
        <PrismicText field={post.data.author.data.profession?.data.name} />
      )}
    </p>
  );
}
```

**Nuxt example:**

```vue-html
<p v-if="$prismic.isFilled.contentRelationship(post.data.author)">
  Written by <PrismicText :field="post.data.author.data?.name" />,
  <template
    v-if="$prismic.isFilled.contentRelationship(post.data.author.data?.profession)"
  >
    <PrismicText :field="post.data.author.data?.profession?.data.name" />
  </template>
</p>
```

**SvelteKit example:**

```svelte
<script lang="ts">
  import { isFilled } from "@prismicio/client";
  import { PrismicText } from "@prismicio/svelte";
</script>

{#if isFilled.contentRelationship(post.data.author)}
  <p>
    Written by <PrismicText field={post.data.author.data?.name} />,
    {#if isFilled.contentRelationship(post.data.author.data?.profession)}
      <PrismicText field={post.data.author.data?.profession?.data.name} />
    {/if}
  </p>
{/if}
```

[Learn about all the available fields](https://prismic.io/docs/fields.md)

# Examples

## Blog post authors

On a blog, you could have posts that reference their authors along with the author's profession.

1. **Add a content relationship field**

   On a Post type, add a content relationship field called `author`. Only allow linking Author pages on the content relationship.

2. **Select fields to include**

   Select the following fields to include in your API response:

   * `name`
   * `profession.name`

3. **Display the author information**

   Use the author data in your blog post to display the author's name and profession.

   **Next.js example:**

   ```tsx
   import { isFilled } from "@prismicio/client";
   import { PrismicText } from "@prismicio/react";

   {
     isFilled.contentRelationship(post.data.author) && (
       <p>
         Written by <PrismicText field={post.data.author.data?.name} />,
         {isFilled.contentRelationship(post.data.author.data?.profession) && (
           <PrismicText field={post.data.author.data.profession?.data.name} />
         )}
       </p>
     );
   }
   ```

   **Nuxt example:**

   ```vue-html
   <p v-if="$prismic.isFilled.contentRelationship(post.data.author)">
     Written by <PrismicText :field="post.data.author.data?.name" />,
     <template
       v-if="$prismic.isFilled.contentRelationship(post.data.author.data?.profession)"
     >
       <PrismicText :field="post.data.author.data?.profession?.data.name" />
     </template>
   </p>
   ```

   **SvelteKit example:**

   ```svelte
   <script lang="ts">
     import { isFilled } from "@prismicio/client";
     import { PrismicText } from "@prismicio/svelte";
   </script>

   {#if isFilled.contentRelationship(post.data.author)}
     <p>
       Written by <PrismicText field={post.data.author.data?.name} />,
       {#if isFilled.contentRelationship(post.data.author.data?.profession)}
         <PrismicText field={post.data.author.data?.profession?.data.name} />
       {/if}
     </p>
   {/if}
   ```

## Landing page testimonials

On a landing page, you could feature testimonials from your customers or clients.

1. **Add a content relationship field**

   On a Landing Page type, add a group field called `testimonials` containing a content relationship field. Only allow linking Testimonial pages on the content relationship.

2. **Select fields to include**

   Select the following fields to include in your API response:

   * `quote`
   * `avatar`

3. **Display the testimonials**

   Use the testimonial data in your landing page to display quotes with avatars.

   **Next.js example:**

   ```tsx
   import { isFilled } from "@prismicio/client";
   import { PrismicNextImage } from "@prismicio/next";
   import { PrismicRichText } from "@prismicio/react";

   <ul>
     {page.data.testimonials.map(
       (item) =>
         isFilled.contentRelationship(item.testimonial) && (
           <li key={item.testimonial.id}>
             <PrismicNextImage field={item.testimonial.data?.avatar} />
             <PrismicRichText field={item.testimonial.data?.quote} />
           </li>
         ),
     )}
   </ul>;
   ```

   **Nuxt example:**

   ```vue-html
   <ul>
     <template v-for="item in page.data.testimonials">
       <li
         v-if="$prismic.isFilled.contentRelationship(item.testimonial)"
         :key="item.testimonial.id"
       >
         <PrismicImage :field="item.testimonial.data?.avatar" />
         <PrismicRichText :field="item.testimonial.data?.quote" />
       </li>
     </template>
   </ul>
   ```

   **SvelteKit example:**

   ```svelte
   <script lang="ts">
     import { isFilled } from "@prismicio/client";
     import { PrismicImage, PrismicRichText } from "@prismicio/svelte";
   </script>

   <ul>
     {#each page.data.testimonials as item}
       {#if isFilled.contentRelationship(item.testimonial)}
         <li>
           <PrismicImage field={item.testimonial.data?.avatar} />
           <PrismicRichText field={item.testimonial.data?.quote} />
         </li>
       {/if}
     {/each}
   </ul>
   ```

## Taxonomies

On a blog, you could organize posts by categories.

1. **Create a Category custom type**

   This type will represent blog post categories. It should be repeatable and have a **Name** field.

2. **Add a content relationship field**

   On a Post type, add a content relationship field called `category`. Only allow linking Category pages on the content relationship.

3. **Select fields to include**

   Select the following fields to include in your API response:

   * `name`

4. **Display the category**

   Use the category data in your blog post to display the post's category.

   **Next.js example:**

   ```tsx
   import { isFilled } from "@prismicio/client";

   {
     isFilled.contentRelationship(post.data.category) && (
       <span>{post.data.category.data?.name}</span>
     );
   }
   ```

   **Nuxt example:**

   ```vue-html
   <span v-if="$prismic.isFilled.contentRelationship(post.data.category)">
     {{ post.data.category.data?.name }}
   </span>
   ```

   **SvelteKit example:**

   ```svelte
   <script lang="ts">
     import { isFilled } from "@prismicio/client";
   </script>

   {#if isFilled.contentRelationship(post.data.category)}
     <span>{post.data.category.data?.name}</span>
   {/if}
   ```

5. **Create a category page**

   In your API query, use the `filters` option to query all blog posts for a category.

   This example fetches all blog posts from a category with the page ID `YksUgRIAACEA-UZD`:

   ```ts {4}
   import { filter } from "@prismicio/client";

   const posts = await client.getAllByType("post", {
     filters: [filter.at("my.post.category", "YksUgRIAACEA-UZD")],
   });
   ```

## Nested menus

On a website, you could build a nested navigation menu.

1. **Create a Child Menu custom type**

   This type will represent a list of links. It should be repeatable and have a group field called `links` containing a link field and an icon field.

2. **Create a Menu custom type**

   This type will represent top-level lists of links. It should be a singleton and have a group field called `child_menus` containing a content relationship field. The content relationship should only allow linking to Child Menu pages.

3. **Select fields to include**

   Select the following fields to include in your API response:

   * `links.link`
   * `links.icon`

4. **Display the menu**

   Use the menu data to render your navigation with proper field checking.

   **Next.js example:**

   ```tsx
   import { isFilled } from "@prismicio/client";
   import { PrismicNextImage, PrismicNextLink } from "@prismicio/next";

   <ul>
     {menu.data.child_menus.map(
       (item) =>
         isFilled.contentRelationship(item.child_menu) && (
           <li key={item.child_menu.id}>
             <ul>
               {item.child_menu.data?.links.map(
                 (linkItem) =>
                   isFilled.link(linkItem.link) && (
                     <li key={linkItem.link.url}>
                       <PrismicNextImage field={linkItem.icon} />
                       <PrismicNextLink field={linkItem.link} />
                     </li>
                   ),
               )}
             </ul>
           </li>
         ),
     )}
   </ul>;
   ```

   **Nuxt example:**

   ```vue-html
   <ul>
     <template v-for="item in menu.data.child_menus">
       <li
         v-if="$prismic.isFilled.contentRelationship(item.child_menu)"
         :key="item.child_menu.id"
       >
         <ul>
           <template v-for="linkItem in item.child_menu.data?.links">
             <li v-if="$prismic.isFilled.link(linkItem.link)" :key="linkItem.link.url">
               <PrismicImage :field="linkItem.icon" />
               <PrismicLink :field="linkItem.link" />
             </li>
           </template>
         </ul>
       </li>
     </template>
   </ul>
   ```

   **SvelteKit example:**

   ```svelte
   <script lang="ts">
     import { isFilled } from "@prismicio/client";
     import { PrismicImage, PrismicLink } from "@prismicio/svelte";
   </script>

   <ul>
     {#each menu.data.child_menus as item}
       {#if isFilled.contentRelationship(item.child_menu)}
         <li>
           <ul>
             {#each item.child_menu.data?.links as linkItem}
               {#if isFilled.link(linkItem.link)}
                 <li>
                   <PrismicImage field={linkItem.icon} />
                   <PrismicLink field={linkItem.link} />
                 </li>
               {/if}
             {/each}
           </ul>
         </li>
       {/if}
     {/each}
   </ul>
   ```

# Check if a content relationship has a value

Use `isFilled.contentRelationship()` from [`@prismicio/client`](https://prismic.io/docs/technical-reference/prismicio-client/v7.md) to check if a content relationship field has a value.

```ts
import { isFilled } from "@prismicio/client";

if (isFilled.contentRelationship(slice.primary.my_content_relationship_field)) {
  // Field has a value
}
```

[Learn more about `isFilled`](https://prismic.io/docs/technical-reference/prismicio-client/v7.md#isfilled)

# API response

Here is what a content relationship looks like from the Content API:

```json
{
  "author": {
    "id": "XxnD3REAACYAk_CJ",
    "type": "author",
    "tags": [],
    "slug": "ada-lovelace",
    "lang": "en-us",
    "uid": "ada-lovelace",
    "data": {
      "name": [
        {
          "type": "paragraph",
          "text": "Ada Lovelace",
          "spans": []
        }
      ],
      "profession": {
        "id": "ZpqX7SFJJKEBl_VK",
        "type": "profession",
        "tags": [],
        "slug": "mathematician",
        "lang": "en-us",
        "uid": "mathematician",
        "data": {
          "name": [
            {
              "type": "paragraph",
              "text": "Mathematician",
              "spans": []
            }
          ]
        },
        "link_type": "Document",
        "isBroken": false
      }
    },
    "link_type": "Document",
    "isBroken": false
  }
}
```

The `data` property's contents is determined by the [selected fields](#select-the-fields-to-fetch).

When a selected field is deleted, it's automatically removed from the API response.

# GraphQuery (Legacy)

GraphQuery is a legacy API option that allows selective fetching (specific fields only) and deep fetching (fields from linked pages). This feature is maintained for existing users but is not recommended for new projects.

> **Caution**
>
> Fetch linked content using [field selection](#select-the-fields-to-fetch) in new projects.

## Basic Usage

GraphQuery uses GraphQL-like syntax. The top-level property represents a page type and its children represent its fields.

Pass the GraphQuery to the Prismic client's `graphQuery` option.

```ts
const blogPost = await client.getByUID("blog_post", "my-first-post", {
  graphQuery: `
    {
      blog_post {
        title
        description
      }
    }
  `,
});
```

This example fetches the `title` and `description` fields from blog posts.

## Arrays

Fields from an array of objects can be selected. Repeatable groups, slice zones, and queries for multiple pages return arrays.

```ts
const blogPost = await client.getByUID("blog_post", "my-first-post", {
  graphQuery: `
    {
      blog_post {
        title
        gallery {
          photo
          caption
        }
      }
    }
  `,
});
```

This example fetches the `gallery` group array with its `photo` and `caption` fields from blog posts.

## Unions

Use unions for content that can return multiple kinds of content, like slices, slice variations, or linked pages.

Each kind of content should be listed with desired the fields. Prepend the API ID with `...on`:

```ts
// Linked pages
const blogPost = await client.getByUID("blog_post", "my-first-post", {
  graphQuery: `
    {
      blog_post {
        author_link {
          ...on author {
            name
            bio
          }
        }
      }
    }
  `,
});

// Slices
const page = await client.getByUID("page", "my-page", {
  graphQuery: `
    {
      page {
        slices {
          ...on text_block {
            primary {
              title
            }
          }
          ...on image_gallery {
            items {
              image
            }
          }
        }
      }
    }
  `,
});
```

This example fetches a the `name` and `bio` fields from a blog post's author. It also fetches fields from the `text_block` and `image_gallery` slices.

## Spread Operator

Select all fields from a set using the spread operator with `...` and `Fields`:

```ts
const blogPost = await client.getByUID("blog_post", "my-first-post", {
  graphQuery: `
    {
      blog_post {
        ...blog_postFields
        author_link {
          ...on author {
            ...authorFields
          }
        }
      }
    }
  `,
});
```

This example fetches all fields from the blog post and all fields from the blog post's author.

## TypeScript Support

Type fetched fields using [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) (the `as` operator).

```typescript
import { Content } from "@prismicio/client";

const featuredBlogPost = home.data
  .featured_blog_post as typeof home.data.featured_blog_post & {
  data: Pick<Content.BlogPostDocument["data"], "title" | "description">;
};

const title = featuredBlogPost.data.title;
//    ^ Typed as Content.BlogPostDocument['data']['title']
```
