Adds navigation and menu item collections with a drag-and-drop tree builder in the Payload admin. Menu items can be URL, internal page, custom or passive links, with configurable nesting depth. A callback resolves internal page URLs, and a precomputed items tree is written to each navigation document. Requires Payload 3.
A blank template to get started with Payload 3.0
Installation
npm i @spon/payload-navigation2
Stars
0
Forks
0
Open Issues
4 months ago
Last Updated
34
Weekly Downloads
v0.4.1
npm Version
175.8 KB
Unpacked Size
5
Dependencies
Trends appear after 7 days of data. Collecting since Oct 22 — 3 daily snapshots so far.
Trends appear after 7 days of data. Collecting since Oct 22 — 3 daily snapshots so far.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1242722491-root)<a href="https://payloaddirectory.dev/plugins/1242722491-root"><img src="https://payloaddirectory.dev/badge/1242722491-root.svg" alt="Payload Directory"></a>README
A Payload CMS plugin for managing navigation menus. Adds a drag-and-drop tree UI to the admin panel, supports multiple link types including internal page relationships, and writes a precomputed clean tree to the navigation document for easy frontend consumption.
items field (clean JSON tree) to the navigation document on every changepnpm add @spon/payload-navigation
Add the plugin to your payload.config.ts:
import { navigationPlugin } from '@spon/payload-navigation'
export default buildConfig({
plugins: [
navigationPlugin({
internalCollections: ['pages'],
maxDepth: 3,
resolveInternalUrl: async ({ id, collection, payload }) => {
const doc = await payload.findByID({ collection, id, depth: 0 })
return doc?.slug ? `/${doc.slug}` : '#'
},
}),
],
})
| Option | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disable the plugin without removing it |
internalCollections | string[] | [] | Collection slugs that can be linked as internal pages |
maxDepth | number | 3 | Maximum nesting depth for menu items |
resolveInternalUrl | ResolveInternalUrl | Returns #id | Async function to resolve a URL from an internal document |
menuItem | Omit<Partial<CollectionConfig>, 'slug'> | {} | Extend or override the menu_item collection |
navigation | Omit<Partial<CollectionConfig>, 'slug'> | {} | Extend or override the navigation collection |
resolveInternalUrlCalled whenever a menu item of type internal is saved. Use it to resolve the document's URL from its ID.
type ResolveInternalUrl = (args: {
id: string
collection: string
payload: BasePayload
}) => Promise<string>
menuItem and navigationBoth options accept any CollectionConfig property except slug. The merge behaviour differs by property type:
| Property | Behaviour |
|---|---|
fields | Appended after plugin fields |
hooks | Run after plugin hooks (plugin invariants preserved) |
admin | Shallow-merged with plugin defaults |
access | Shallow-merged — read defaults to () => true |
| Everything else | Consumer value overrides plugin default |
navigationPlugin({
menuItem: {
// extra fields appear in the drawer and pass through to the items tree output
fields: [
{ name: 'badge', type: 'text' },
{ name: 'icon', type: 'text' },
],
hooks: {
afterChange: [({ doc }) => revalidateCache(doc)],
},
admin: { hidden: false }, // un-hide the collection
access: {
create: ({ req }) => req.user?.role === 'admin',
update: ({ req }) => req.user?.role === 'admin',
delete: ({ req }) => req.user?.role === 'admin',
},
},
navigation: {
access: {
create: ({ req }) => req.user?.role === 'admin',
delete: ({ req }) => req.user?.role === 'admin',
},
versions: { drafts: true },
},
})
The plugin registers two collections:
navigation — A named menu (e.g. "Main Nav", "Footer"). Contains the visual builder UI and a hidden items field with the precomputed tree.menu_item — Individual items within a menu. Managed through the builder UI, not directly.items fieldThe navigation document's items field contains a precomputed clean JSON tree, built from the menu_item collection on every read:
type NavigationMenuItem<TExtra extends Record<string, unknown> = Record<string, never>> = {
id: string
title: string
type: string
href: string // resolved URL for all types
depth: number
parent: string | null
collapsed: boolean
children?: NavigationMenuItem<TExtra>[]
} & TExtra
Any extra fields added to menu_item via the menuItem.fields option are automatically included in each node. Use the TExtra generic to type them in your frontend:
// If you added { name: 'badge', type: 'text' } to menuItem.fields:
type MyNavItem = NavigationMenuItem<{ badge?: string }>
const nav = await payload.find({
collection: 'navigation',
where: { slug: { equals: 'main' } },
})
const tree = nav.docs[0]?.items as MyNavItem[]
| Type | Description |
|---|---|
url | An absolute or relative web address |
internal | A document from one of your internalCollections |
custom | Any string value (e.g. an anchor #section) |
passive | A label with no link (for parent-only items) |
Tree UI components are exported from @spon/payload-navigation/client for embedding in custom admin views:
import { MenuTree, TreeItem, DeleteMenuItem } from '@spon/payload-navigation/client'
These are all client components ('use client').
# Install dependencies
pnpm install
# Copy env and start the dev server
cp dev/.env.example dev/.env
pnpm dev
# Type check
pnpm typecheck
# Lint
pnpm lint
# Unit tests
pnpm test:unit
# E2E tests (requires dev server running)
pnpm test:e2e
# Build
pnpm build
payload nested docs page tree
Page tree list view for nested docs collections with drag and drop, status badges and live or preview links
83662/wk35v3
payload nav studio
Replaces the Payload admin sidebar with a drag and drop navigation that each user can customize
7366/wk13v3
payload plugin
Payload plugin for Blockwright adding layouts, templates, menus, forms and a visual page editor
71135/wk3v3
menu plugin
Menu management for Payload with nested dropdowns, mega menus, locale support and a public menu endpoint
6856/wk6v3
payload plugin menus
Manage navigation menus from the Payload admin and render them in a frontend
573/wk0v2
plugin
Sitecore-style content tree view for the Payload CMS 3.x admin panel
150v3