Quick start
Add the plugin, provide the Puck configuration and create a frontend route.
The plugin plugs into Payload's admin with minimal configuration. API endpoints and the admin editor view are registered for you; you add a config provider and a frontend route.
Add the plugin
import { fileURLToPath } from 'node:url'
import { buildConfig } from 'payload'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { createPuckPlugin } from '@delmaredigital/payload-puck/plugin'
export default buildConfig({
secret: process.env.PAYLOAD_SECRET!,
db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL! } }),
collections: [{ slug: 'users', auth: true, fields: [] }],
admin: {
user: 'users',
importMap: { baseDir: fileURLToPath(new URL('.', import.meta.url)) },
components: { providers: ['/components/admin/PuckProvider'] },
},
plugins: [
createPuckPlugin({
pagesCollection: 'pages', // Collection slug (default: 'pages')
}),
],
})This:
- Creates a
pagescollection with Puck fields, or adds the fields to your existingpagescollection. - Registers API endpoints at
/api/puck/:collection. - Adds the Puck editor view at
/admin/puck-editor/:collection/:id. - Adds "Visual Editor" buttons to the admin UI.
Provide the Puck configuration
The Payload config above registers a client provider for the admin. Create that file so the editor can read the configuration through React context:
'use client'
import type { ReactNode } from 'react'
import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
import { editorConfig } from '@delmaredigital/payload-puck/config/editor'
export default function PuckProvider({ children }: { children: ReactNode }) {
return <PuckConfigProvider config={editorConfig}>{children}</PuckConfigProvider>
}PuckConfigProvider also accepts layouts, theme, plugins, integrations, editorStylesheets and editorCss. See Layouts, Theming and the provider props.
Then regenerate Payload's types and import map, so the injected fields are typed and the admin components resolve:
payload generate:types
payload generate:importmapCreate a frontend route
Next.js App Router routes are files, so the plugin can't create this one for you. Copy it:
import { getPayload } from 'payload'
import config from '@payload-config'
import { PageRenderer } from '@delmaredigital/payload-puck/render'
import { baseConfig } from '@delmaredigital/payload-puck/config'
import { notFound } from 'next/navigation'
import type { Metadata } from 'next'
import type { Data } from '@puckeditor/core'
// Fetch page by slug (or homepage if no slug)
async function getPage(slug?: string[]) {
const payload = await getPayload({ config })
const slugPath = slug?.join('/') || ''
const { docs } = await payload.find({
collection: 'pages',
where: {
and: [
{ _status: { equals: 'published' } },
slugPath
? { slug: { equals: slugPath } }
: { isHomepage: { equals: true } },
],
},
limit: 1,
})
return docs[0] || null
}
// Generate metadata from page SEO fields
export async function generateMetadata({
params,
}: {
params: Promise<{ slug?: string[] }>
}): Promise<Metadata> {
const { slug } = await params
const page = await getPage(slug)
if (!page) return {}
return {
title: page.meta?.title || page.title,
description: page.meta?.description,
}
}
// Render the page
export default async function Page({
params,
}: {
params: Promise<{ slug?: string[] }>
}) {
const { slug } = await params
const page = await getPage(slug)
if (!page) notFound()
return <PageRenderer config={baseConfig} data={page.puckData as Data} />
}The double brackets in [[...slug]] make the segment optional, so one route handles both / (the homepage) and /any/path.
That's it
Open a page in the admin and choose "Visual Editor". The editor opens full-screen at /admin/puck-editor/:collection/:id, still under /admin, so Payload's session and access rules apply, and its Back button returns to the document. Saves go through the plugin's endpoints, registered with Payload's endpoint system.
Next
- Styling: make the components render with Tailwind.
- Editor stylesheet: give the editor canvas your site's CSS.
- Server vs client config: which config to import where.

