Trendy Coder Logo
  • Home
  • About
  • Blog
  • Newsletter

Document Nesting With Payload's Nested Docs Plugin

Posted on: April 04 2025
By Dave Becker
Hero image for Document Nesting With Payload's Nested Docs Plugin

The Payload CMS Nested Docs plugin allows you to organize and manage nested documents within your content structure. By using this plugin, you can create hierarchical relationships between different collections, making it easier to handle nested data models and maintain a clean content structure.

With the Nested Docs plugin you can easily link and manage content from different collections in a parent-child relationship, ensuring that your data is well-organized and accessible throughout the CMS.

In this article I'll covering the following:

  • A Documentation Collection model using Nested Docs plugin
  • Nesting categories using the Nested Docs plugin
  • How to build a breadcrumbs path using the Nested Docs plugin
  • How to query nested documents
  • How to use Next.js with the Nested Docs plugin

Getting Started

If you would like to follow along, you can generate a Payload project with the following command.

npx create-payload-app

Installing the Payload Nested plugin

In order to add nested Collection support, the plugin needs to be installed.

pnpm add @payloadcms/plugin-nested-docs

The plugin needs to be added to the main payload.config.ts file. Later in the article, we'll add and configure the Collections.

import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
import { buildConfig } from 'payload'

export default buildConfig({
  plugins: [
    nestedDocsPlugin({
      // nested config
    }),
  ],
})

Building a Document Collection model

For this example I'll create a basic Documentation system which will have the following features:

  • Document status: A select menu to set a single relationship to a document progress status indicator.
  • Document tags: Create a Tags Collection to hold many tags that might be attached by documentation authors and editors.
  • Nested categories: Uses the Payload @payloadcms/plugin-nested-docs plugin to automatically add nested docs support with breadcrumbs.

The Document Collection

Here's the documents Collection model for this example.

import type { CollectionConfig } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export const Documents: CollectionConfig = {
  slug: 'documents',
  admin: {
    useAsTitle: 'title',
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
      admin: {
        position: 'sidebar'
      },
      hooks: {
        beforeChange: [
          ({value, operation, data}) => {
            if (operation === 'create' || operation === 'update') {
              if (data && !data?.slug) {
                return data.title.trim().toLowerCase().replace(/[^a-zA-Z0-9]/g, ' ').replaceAll(' ', '-')
              }
            }
            return value
          },
        ],
      },
    },
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'content',
      type: 'richText',
      required: true,
      editor: lexicalEditor(),
    },
    {
      name: 'documentStatus',
      type: 'select',
      interfaceName: 'DocumentStatus',
      admin: {
        position: 'sidebar',
      },
      hasMany: false,
      options: [
        {
          label: 'On hold',
          value: 'on-hold',
        },
        {
          label: 'Rough draft',
          value: 'rough-draft',
        },
        {
          label: 'In progress',
          value: 'in-progress',
        },
        {
          label: 'Read for review',
          value: 'ready-for-review',
        },
        {
          label: 'Approved',
          value: 'approved',
        },
      ],
    },
    {
      name: 'tags',
      type: 'relationship',
      hasMany: true,
      relationTo: 'tags',
    },
    {
      name: 'categories',
      type: 'relationship',
      admin: {
        position: 'sidebar',
      },
      hasMany: false,
      relationTo: 'categories',
    },
  ]
}

Analyzing the Documents Collection

The Documents Collection defines the following fields:

  • slug: A slug path that will be auto-generated using a simple slug optimization function if a value is not provided by the user.
  • title: Document page title.
  • content: Rich text editor with configurable inline block editing support.
  • documentStatus: Adds a select field type menu to set a single status option by setting hasMany to false.
  • catgories: Adds a category select field but provides nested category support with the use of the Payload plugin.
  • tags: Adds a relationship field type to add many tags to a document.

The Tags Collection

The configuration for the tags only has one field:

  • title: Holds the title for the tag.
import type { CollectionConfig } from 'payload'

export const Tags: CollectionConfig = {
  slug: 'tags',
  admin: {
    useAsTitle: 'title'
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    }
  ],
}

The Categories Collection

W can easily add nested categories support using the Payload plugin @payloadcms/plugin-nested-docs.

Why use nested Categories

In a documentation scenario, you may need to have certain features like:

  • Breadcrumbs trail for navigation
  • Categories have subcategories if needed.
  • Helps organize topics for searching by category.

Create the Categories Collection

Here's all that's needed for the categories model since the plugin will automatically add some fields to the Collection.

import type { CollectionConfig } from 'payload'

export const Categories: CollectionConfig = {
  slug: 'categories',
  admin: {
    useAsTitle: 'title',
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
  ],
}

Categories with Nested Docs Support

The categories configuration has just one field title but the plugin will automatically add the following fields to the categories Collection:

  • title: Holds the title for the category.
  • parent: (Added by plugin) Self-reference to categories Collection to reference a parent hierarchy. If the parent is null or not set, then it is considered a root category.
  • breadcrumbs: (Added by plugin) A chain of categories all the way up to the first parent that is null or not set.

Configuring the Nested Docs Plugin

Now that we have all of the Collections ready, let's add them to the collections array in the main payload.config.ts file.

Let's also add the Payload Nested Docs configuration details so the categories Collection can be automatically managed.

// ...
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
import { buildConfig } from 'payload'
import { Documents } from './payload/collections/Documents'
import { Categories } from './payload/collections/Categories'
import { Tags } from './payload/collections/Tags'

export default buildConfig({
  // ...
  collections: [Documents, Categories, Tags],
  editor: lexicalEditor(),
  
  // ...
  plugins: [
    nestedDocsPlugin({
      // the collections to add nested support to
      collections: ['categories'],
      // generate the label for breadcrumbs
      generateLabel: (_, doc) => doc.title as string,
      // generate the url for breadcrumbs
      generateURL: (docs, currentDoc) => {
        // formats url as '/2' and can be used as link path '/documents/2'
        return `/${currentDoc.id}`
      },
    }),
  ],
})

Here's what the plugin is configured to do:

  • collections: It will add nested support to any Collection slug listed in this array.
  • generateLabel: A function to customize the category label.
  • generateURL: A function that will customize the breadcrumb item's url.

If each breadcrumb needs the full category url path, then the following generateURL could be use instead.

Creates a full category path to parent
plugins: [
    nestedDocsPlugin({
      // generate the url as '/Technical/Development/Payload'
      generateURL: (docs) =>
        docs.reduce((url, doc) => `${url}/${doc.slug}`, '')
    }),
  ],

Running the Documentation System

Now that all the changes are in place, start up the dev server so the changes can take affect.

The Collections that were created can now be viewed in the Admin UI.

# documents admin page
http://localhost:3000/admin/documents

# categories admin page
http://localhost:3000/admin/catgories

# tags admin page
http://localhost:3000/admin/tags

Admin UI for Documents

The documents Collection will generate the following Admin UI for managing a document's content:

Shows Payload document collection form in Admin UI

The documentStatus field will populate the options provided in the configuration to set a documents status.

Shows a category dropdown select menu in Admin UI

The documentStatus select field will actually create a one-to-one mapping with another table using a foreign key database reference.

So, even though it looks like a regular HTML dropdown select menu, there is a good amount of database mapping that Payload simplifies behind the scenes.

The select field type is used for select one (one-to-one) or select many (one-to-many) menu.

However, for a many-to-many type relationship, you'll want to use a relationship field, which is why tags has its own Collection.

Admin UI for Tags

The Tags configuration will generate the following Admin UI to list and edit tags.

Shows a tag dropdown select menu in Admin UI

Using the useAsTitle field lets the relationship select field know what value to show as the label.

Admin useAsTitle for select and list
admin: {
    useAsTitle: 'title'
}

The select and relationship field types will use the id value as a label by default. This might be confusing to users, so it's better to indicate which field to use for a label with useAsTitle admin setting.

Adding Tags

One of the great features of the select field type, is that it provides a way to create new items on the fly, using the "+" icon.

Since tags is a separate Collection, each document can have many tags.

Shows a tag dropdown select menu in Admin UI

Admin UI for Categories

The categories configuration generates the following Admin UI pages for editing categories.

Shows a categories table list in Admin UI

Adding Nested Categories

All categories can now have a parent category.

  • A category is a root category, if its parent is null.
  • A category is subcategory, if its parent is another category.
  • The breadcrumbs array will contain the current category all of its parents up to the first null value.

The breadcrumbs array field is readonly because the plugin will keep the paths in sync as they change over time.

Shows a Payload edit category form in Admin UI

If you've ever tried to maintain a breadcrumbs path, this plugin will make the job so much easier.

This plugin is not limited to simple category nesting but any Collection can use the parent/child relationship if needed.

Document query response with REST

To fetch from the documents Collection, simply use the REST API as follows.

# gets all docs
http://localhost:3000/api/documents

# fetch a single doc
http://localhost:3000/api/documents/2

Here's the documents query response using Payload's REST API endpoints.

As you can see the response returns the relationship data as well, including:

  • documentStatus
  • tags
  • categories - which includes the breadcrumbs
  • content: Serialized JSON rich text content.

The rich text and dates are omitted since it's quite lengthy.

Response with rich text content and dates omitted
{
    "id": 2,
    "slug": "all-about-payload-collections",
    "title": "All About Payload Collections",
    "content": {},
    "documentStatus": "ready-for-review",
    "tags": [
        {
            "id": 4,
            "title": "Media"
        },
        {
            "id": 2,
            "title": "Collections"
        },
        {
            "id": 6,
            "title": "GraphQL"
        }
    ],
    "categories": {
        "id": 3,
        "title": "Collections",
        "parent": 2,
        "breadcrumbs": [
            {
                "id": "67d905d018fc884ea8d9beea",
                "doc": 1,
                "url": "/1",
                "label": "Development"
            },
            {
                "id": "67d905d018fc884ea8d9beeb",
                "doc": 2,
                "url": "/2",
                "label": "Payload"
            },
            {
                "id": "67d905d018fc884ea8d9beec",
                "doc": 3,
                "url": "/3",
                "label": "Collections"
            }
        ]
    }
}

Document breadcrums example

Here's an example of how to use the category breadcrumbs to build a breadcrumbs path in a Next.js page.

A Next.js page showing a breadcrumbs path generated using data returned from a documents query

The Next.js page looks for a category id path param named cid, which then does a fetch to get all the documents related to a specific category.

import React from 'react'
import { type Document as DocumentType } from '@payload-types'
import { PaginatedDocs } from 'payload'

type Args = {
  params: Promise<{
    cid?: string
  }>
}

export default async function Page({ params: paramsPromise }: Args) {
  const { cid } = await paramsPromise

  const res = await fetch(`http://localhost:3000/api/documents?[where][categories.id]=${cid}`, {
    method: 'GET',
  })
  const data: PaginatedDocs<DocumentType> = await res.json()
  const document = data?.docs?.[0]

  const { categories } = document
  const breadcrumbs = typeof categories === 'object' ? categories?.breadcrumbs : []

  return (
    <nav className="flex" aria-label="Breadcrumb">
      <ol className="inline-flex items-center">
        {breadcrumbs?.map((crumb) => {
          return (
            <li className="inline-flex items-center">
              <svg
                className="w-6 h-6 text-gray-400"
                fill="currentColor"
                viewBox="0 0 20 20"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  fill-rule="evenodd"
                  d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                  clip-rule="evenodd"
                ></path>
              </svg>
              <a href={`/documents${crumb ? (crumb?.url as string) : ''}`}>{crumb.label}</a>
            </li>
          )
        })}
      </ol>
    </nav>
  )
}

In Conclusion

So by now we've seen how to use Payload's Nested Docs Plugin in a practical way.

Nested Collections can be extremely powerful and can take the complexity out of building it by hand.

The Nested Docs Plugin is not limited to simple categories and can be used for very complex Collection models.

Hope this article helps.

Topics

SEOLinuxSecuritySSHEmail MarketingMore posts...

Related Posts

Hero image for Boost Payload CMS with Search: Step-by-Step Tutorial
Posted on: August 11 2025
By Dave Becker
Boost Payload CMS with Search: Step-by-Step Tutorial
Hero image for Create Custom Forms Using Payload CMS Form Builder Plugin
Posted on: August 11 2025
By Dave Becker
Create Custom Forms Using Payload CMS Form Builder Plugin
Hero image for Exploring the Game-Changing Features of Payload CMS 3.0
Posted on: April 04 2025
By Dave Becker
Exploring the Game-Changing Features of Payload CMS 3.0
Hero image for GraphQL Optimization in Payload CMS
Posted on: April 04 2025
By Dave Becker
GraphQL Optimization in Payload CMS
Hero image for Maximizing Efficiency: The Power of Payload CMS Blocks
Posted on: August 11 2025
By Dave Becker
Maximizing Efficiency: The Power of Payload CMS Blocks
Hero image for Payload CMS Collections: How They Streamline Content Management
Posted on: April 04 2025
By Dave Becker
Payload CMS Collections: How They Streamline Content Management
Hero image for Payload CMS: Getting Started Using the New Join Field
Posted on: August 11 2025
By Dave Becker
Payload CMS: Getting Started Using the New Join Field
Hero image for Payload CMS SEO Plugin: Boosting Your Site's Search Ranking
Posted on: April 04 2025
By Dave Becker
Payload CMS SEO Plugin: Boosting Your Site's Search Ranking
Hero image for Server-Side Pagination Made Easy in Payload CMS
Posted on: August 11 2025
By Dave Becker
Server-Side Pagination Made Easy in Payload CMS
Hero image for Uploading Made Easy: Exploring Media Features in Payload CMS
Posted on: September 23 2025
By Dave Becker
Uploading Made Easy: Exploring Media Features in Payload CMS
Trendy Coder Logo
Resources
  • Blog
Website
  • Home
  • About us
Subscribe

Get the latest news and articles to your inbox periodically.

We respect your email privacy

© TrendyCoder.com. All rights reserved.