{"id":43866541,"url":"https://github.com/delmaredigital/payload-puck","last_synced_at":"2026-02-06T12:28:28.174Z","repository":{"id":331945496,"uuid":"1127989065","full_name":"delmaredigital/payload-puck","owner":"delmaredigital","description":"Puck visual page builder plugin for Payload CMS","archived":false,"fork":false,"pushed_at":"2026-01-28T17:16:42.000Z","size":2047,"stargazers_count":23,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-29T07:36:49.067Z","etag":null,"topics":["page-builder","payload-plugin","payloadcms","puck","typescript","visual-editor"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@delmaredigital/payload-puck","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/delmaredigital.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-05T01:04:40.000Z","updated_at":"2026-01-28T20:04:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/delmaredigital/payload-puck","commit_stats":null,"previous_names":["delmaredigital/payload-puck"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/delmaredigital/payload-puck","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/delmaredigital%2Fpayload-puck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/delmaredigital%2Fpayload-puck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/delmaredigital%2Fpayload-puck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/delmaredigital%2Fpayload-puck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/delmaredigital","download_url":"https://codeload.github.com/delmaredigital/payload-puck/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/delmaredigital%2Fpayload-puck/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29160816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T07:18:23.844Z","status":"ssl_error","status_checked_at":"2026-02-06T07:13:32.659Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["page-builder","payload-plugin","payloadcms","puck","typescript","visual-editor"],"created_at":"2026-02-06T12:28:27.054Z","updated_at":"2026-02-06T12:28:28.156Z","avatar_url":"https://github.com/delmaredigital.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @delmaredigital/payload-puck\n\nA PayloadCMS plugin for integrating [Puck](https://puckeditor.com) visual page builder. Build pages visually with drag-and-drop components while leveraging Payload's content management capabilities.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://demo.delmaredigital.com\"\u003e\u003cimg src=\"https://img.shields.io/badge/Live_Demo-Try_It_Now-2ea44f?style=for-the-badge\u0026logo=vercel\u0026logoColor=white\" alt=\"Live Demo - Try It Now\"\u003e\u003c/a\u003e\n  \u0026nbsp;\u0026nbsp;\n  \u003ca href=\"https://github.com/delmaredigital/dd-starter\"\u003e\u003cimg src=\"https://img.shields.io/badge/Starter_Template-Use_This-blue?style=for-the-badge\u0026logo=github\u0026logoColor=white\" alt=\"Starter Template - Use This\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdelmaredigital%2Fdd-starter\u0026project-name=my-payload-site\u0026build-command=pnpm%20run%20ci\u0026env=PAYLOAD_SECRET,BETTER_AUTH_SECRET\u0026stores=%5B%7B%22type%22%3A%22integration%22%2C%22protocol%22%3A%22storage%22%2C%22productSlug%22%3A%22neon%22%2C%22integrationSlug%22%3A%22neon%22%7D%2C%7B%22type%22%3A%22blob%22%7D%5D\"\u003e\u003cimg src=\"https://vercel.com/button\" alt=\"Deploy with Vercel\" height=\"32\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Documentation\n\nFor additional documentation, visit: [https://deepwiki.com/delmaredigital/payload-puck](https://deepwiki.com/delmaredigital/payload-puck)\n\n---\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n  - [Adding to Existing Projects](#adding-to-existing-projects)\n- [Styling Setup](#styling-setup)\n- [Core Concepts](#core-concepts)\n- [Components](#components)\n- [Custom Fields](#custom-fields)\n- [Building Custom Components](#building-custom-components)\n- [Theming](#theming)\n- [Layouts](#layouts)\n- [Dark Mode Support](#dark-mode-support)\n- [Page-Tree Integration](#page-tree-integration)\n- [Hybrid Integration](#hybrid-integration)\n- [AI Integration](#ai-integration)\n- [Plugin Order](#plugin-order)\n- [Advanced Configuration](#advanced-configuration)\n- [License](#license)\n\n---\n\n## Installation\n\n### Requirements\n\n| Dependency | Version | Purpose |\n|------------|---------|---------|\n| `@puckeditor/core` | \u003e= 0.21.0 | Visual editor core |\n| `payload` | \u003e= 3.69.0 | CMS backend |\n| `@payloadcms/next` | \u003e= 3.69.0 | Payload Next.js integration |\n| `next` | \u003e= 15.4.8 | React framework |\n| `react` | \u003e= 19.2.1 | UI library |\n| `@tailwindcss/typography` | \u003e= 0.5.0 | RichText component styling |\n\n\u003e **Note:** Puck 0.21+ moved from `@measured/puck` to `@puckeditor/core`. This plugin requires the new package scope.\n\n### Install\n\n```bash\npnpm add @delmaredigital/payload-puck @puckeditor/core\n```\n\n---\n\n## Quick Start\n\nThe plugin integrates directly into Payload's admin UI with minimal configuration. API endpoints and admin views are registered automatically.\n\n### Step 1: Add the Plugin\n\n```typescript\n// src/payload.config.ts\nimport { buildConfig } from 'payload'\nimport { createPuckPlugin } from '@delmaredigital/payload-puck/plugin'\n\nexport default buildConfig({\n  plugins: [\n    createPuckPlugin({\n      pagesCollection: 'pages', // Collection slug (default: 'pages')\n    }),\n  ],\n  // ...\n})\n```\n\nThis automatically:\n- Creates a `pages` collection with Puck fields (or adds fields to your existing collection)\n- Registers API endpoints at `/api/puck/:collection`\n- Adds the Puck editor view at `/admin/puck-editor/:collection/:id`\n- Adds \"Edit with Puck\" buttons to the admin UI\n\n### Step 2: Provide Puck Configuration\n\nWrap your app with `PuckConfigProvider` to supply the Puck configuration. This makes the config available to the editor via React context.\n\n```typescript\n// app/(app)/layout.tsx (covers both admin and frontend)\nimport { PuckConfigProvider } from '@delmaredigital/payload-puck/client'\nimport { editorConfig } from '@delmaredigital/payload-puck/config/editor'\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003chtml lang=\"en\"\u003e\n      \u003cbody\u003e\n        \u003cPuckConfigProvider config={editorConfig}\u003e\n          {children}\n        \u003c/PuckConfigProvider\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n\u003e **Tip:** `PuckConfigProvider` also accepts `layouts` and `theme` props. See [Layouts](#layouts) and [Theming](#theming) sections.\n\n\u003e **Note:** For custom editor UIs (outside Payload admin), you can also pass the config directly to `PuckEditor` instead of using the context provider.\n\n**Alternative: Payload Admin Provider (vanilla starter pattern)**\n\nIf you're using the vanilla Payload starter structure, you can register the provider via the admin config instead:\n\n```typescript\n// src/payload.config.ts\nexport default buildConfig({\n  admin: {\n    components: {\n      providers: ['@/components/admin/PuckProvider'],\n    },\n  },\n  // ...\n})\n```\n\n```typescript\n// src/components/admin/PuckProvider.tsx\n'use client'\n\nimport { PuckConfigProvider } from '@delmaredigital/payload-puck/client'\nimport { editorConfig } from '@delmaredigital/payload-puck/config/editor'\n\nexport default function PuckProvider({ children }: { children: React.ReactNode }) {\n  return \u003cPuckConfigProvider config={editorConfig}\u003e{children}\u003c/PuckConfigProvider\u003e\n}\n```\n\n### Step 3: Create a Frontend Route\n\nThe plugin can't auto-create frontend routes (Next.js App Router is file-based), but here's copy-paste ready code:\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e📄 app/(frontend)/[[...slug]]/page.tsx\u003c/strong\u003e (click to expand)\u003c/summary\u003e\n\n```typescript\nimport { getPayload } from 'payload'\nimport config from '@payload-config'\nimport { PageRenderer } from '@delmaredigital/payload-puck/render'\nimport { baseConfig } from '@delmaredigital/payload-puck/config'\nimport { notFound } from 'next/navigation'\nimport type { Metadata } from 'next'\n\n// Fetch page by slug (or homepage if no slug)\n// Only returns published pages - unpublished pages will 404\nasync function getPage(slug?: string[]) {\n  const payload = await getPayload({ config })\n  const slugPath = slug?.join('/') || ''\n\n  // Try to find by slug, or find homepage\n  // Filter for published pages only (_status: 'published')\n  const { docs } = await payload.find({\n    collection: 'pages',\n    where: {\n      and: [\n        { _status: { equals: 'published' } },\n        slugPath\n          ? { slug: { equals: slugPath } }\n          : { isHomepage: { equals: true } },\n      ],\n    },\n    limit: 1,\n  })\n\n  return docs[0] || null\n}\n\n// Generate metadata from page SEO fields\nexport async function generateMetadata({\n  params\n}: {\n  params: Promise\u003c{ slug?: string[] }\u003e\n}): Promise\u003cMetadata\u003e {\n  const { slug } = await params\n  const page = await getPage(slug)\n\n  if (!page) return {}\n\n  return {\n    title: page.meta?.title || page.title,\n    description: page.meta?.description,\n  }\n}\n\n// Render the page\nexport default async function Page({\n  params\n}: {\n  params: Promise\u003c{ slug?: string[] }\u003e\n}) {\n  const { slug } = await params\n  const page = await getPage(slug)\n\n  if (!page) notFound()\n\n  return \u003cPageRenderer config={baseConfig} data={page.puckData} /\u003e\n}\n```\n\n\u003c/details\u003e\n\n\u003e **Note:** The `[[...slug]]` pattern with double brackets makes the slug optional, so this handles both `/` (homepage) and `/any/path`.\n\n### That's It!\n\n- The plugin registers the editor view at `/admin/puck-editor/:collection/:id`\n- \"Edit with Puck\" buttons appear in the collection list view\n- The editor runs inside Payload's admin UI with full navigation\n- API endpoints are handled automatically via Payload's endpoint system\n\n### Adding to Existing Projects\n\n\u003e **⚠️ Important:** If you're adding Puck to a project with existing frontend routes, you must update those routes to render Puck content.\n\nWhen adding Puck to an existing Payload project:\n\n1. ✅ Add the plugin to `payload.config.ts`\n2. ✅ Add `PuckConfigProvider` to your admin layout\n3. ⚠️ **Update your frontend page templates** to render `puckData`\n\nWithout step 3, Puck pages will render blank because your existing routes only look for legacy block fields like `layout` or `hero`.\n\n**Option A: Hybrid Rendering (recommended)**\n\nUse `HybridPageRenderer` to render Puck pages. For new projects, this is all you need:\n\n```typescript\nimport { HybridPageRenderer } from '@delmaredigital/payload-puck/render'\nimport { baseConfig } from '@delmaredigital/payload-puck/config'\n\nexport default async function Page({ params }) {\n  const page = await getPage(params.slug)\n  return \u003cHybridPageRenderer page={page} config={baseConfig} /\u003e\n}\n```\n\nIf you're migrating an existing site with legacy Payload blocks, provide a `legacyRenderer`:\n\n```typescript\nimport { HybridPageRenderer } from '@delmaredigital/payload-puck/render'\nimport { baseConfig } from '@delmaredigital/payload-puck/config'\nimport { LegacyBlockRenderer } from '@/components/LegacyBlockRenderer'\n\nexport default async function Page({ params }) {\n  const page = await getPage(params.slug)\n\n  return (\n    \u003cHybridPageRenderer\n      page={page}\n      config={baseConfig}\n      legacyRenderer={(blocks) =\u003e \u003cLegacyBlockRenderer blocks={blocks} /\u003e}\n    /\u003e\n  )\n}\n```\n\n**Option B: Manual Detection**\n\nAdd conditional logic to check `editorVersion`:\n\n```typescript\n// Check if page was created with Puck\nconst isPuckPage = page.editorVersion === 'puck' \u0026\u0026 page.puckData?.content?.length \u003e 0\n\nif (isPuckPage) {\n  return \u003cPageRenderer config={baseConfig} data={page.puckData} /\u003e\n}\n\n// Fall back to legacy rendering\nreturn \u003cLegacyBlockRenderer blocks={page.layout} /\u003e\n```\n\n**Option C: Custom Components**\n\nIf you have custom Puck components (not just the built-in ones), create a client wrapper:\n\n```typescript\n// components/PuckPageRenderer.tsx\n'use client'\n\nimport { Render } from '@puckeditor/core'\nimport { myCustomConfig } from '@/puck/config'\n\nexport function PuckPageRenderer({ data }) {\n  return \u003cRender config={myCustomConfig} data={data} /\u003e\n}\n```\n\nThen use this wrapper in your page template instead of `PageRenderer`.\n\n---\n\n## Styling Setup\n\n### Tailwind Typography (Required)\n\n\u003e Required only if using the RichText component.\n\nThe RichText component uses `@tailwindcss/typography`:\n\n```bash\npnpm add @tailwindcss/typography\n```\n\n**Tailwind v4:**\n```css\n@import \"tailwindcss\";\n@plugin \"@tailwindcss/typography\";\n```\n\n**Tailwind v3:**\n```javascript\n// tailwind.config.js\nmodule.exports = {\n  plugins: [require('@tailwindcss/typography')],\n}\n```\n\n### Package Scanning (Required)\n\n\u003e Required if your project uses Tailwind CSS. Ensures component classes are included in your build.\n\nTell Tailwind to scan the plugin's components:\n\n**Tailwind v4:**\n```css\n/* Adjust path relative to your CSS file */\n@source \"../node_modules/@delmaredigital/payload-puck\";\n```\n\n**Tailwind v3:**\n```javascript\n// tailwind.config.js\nmodule.exports = {\n  content: [\n    './src/**/*.{js,ts,jsx,tsx}',\n    './node_modules/@delmaredigital/payload-puck/**/*.{js,mjs,jsx,tsx}',\n  ],\n}\n```\n\n### Theme CSS Variables (Optional)\n\n\u003e Optional - the plugin includes sensible defaults. Define these only to customize colors in rendered content (links, borders, etc).\n\nThe plugin uses [shadcn/ui](https://ui.shadcn.com)-style CSS variables. If you don't use shadcn/ui and want to customize colors, define these in your CSS:\n\n```css\n:root {\n  --background: 0 0% 100%;\n  --foreground: 222.2 84% 4.9%;\n  --primary: 222.2 47.4% 11.2%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 222.2 84% 4.9%;\n  --radius: 0.5rem;\n}\n```\n\n---\n\n## Core Concepts\n\n### Server vs Client Configuration\n\nThe plugin provides two configurations for React Server Components:\n\n| Config | Import | Use Case |\n|--------|--------|----------|\n| `baseConfig` | `@delmaredigital/payload-puck/config` | Server-safe rendering with `PageRenderer` |\n| `editorConfig` | `@delmaredigital/payload-puck/config/editor` | Client-side editing with full interactivity |\n\n```typescript\n// Server component - use baseConfig\nimport { baseConfig } from '@delmaredigital/payload-puck/config'\n\u003cPageRenderer config={baseConfig} data={page.puckData} /\u003e\n\n// PuckConfigProvider - use editorConfig\nimport { editorConfig } from '@delmaredigital/payload-puck/config/editor'\n\u003cPuckConfigProvider config={editorConfig}\u003e\n```\n\n### Draft System\n\nThe editor uses Payload's native draft system. The plugin automatically enables drafts on the pages collection. You can also enable it manually:\n\n```typescript\n{\n  slug: 'pages',\n  versions: {\n    drafts: true,\n  },\n}\n```\n\nThe editor header provides:\n- **Save** - Saves as draft without publishing\n- **Publish** - Publishes the page (sets `_status: 'published'`)\n- **Unpublish** - Reverts a published page to draft status (appears only when published)\n\n---\n\n## Components\n\n### Layout\n\n| Component | Description |\n|-----------|-------------|\n| **Container** | Content wrapper with max-width and background |\n| **Flex** | Flexible box layout with direction and alignment |\n| **Grid** | CSS Grid layout with responsive columns |\n| **Section** | Two-layer: full-bleed section + constrained content area |\n| **Spacer** | Vertical/horizontal spacing element |\n| **Template** | Save/load reusable component arrangements |\n\n### Typography\n\n| Component | Description |\n|-----------|-------------|\n| **Heading** | H1-H6 headings with size and alignment |\n| **Text** | Paragraph text with styling options |\n| **RichText** | Puck's native richtext editor with enhancements: font sizes, text colors with opacity, highlights, superscript/subscript, and inline editing on canvas |\n\n### Media \u0026 Interactive\n\n| Component | Description |\n|-----------|-------------|\n| **Image** | Responsive image with alt text |\n| **Button** | Styled button/link with variants |\n| **Card** | Content card with optional image |\n| **Divider** | Horizontal rule with styles |\n| **Accordion** | Expandable content sections (first item opens by default) |\n\n### Semantic HTML Elements\n\nLayout components (Section, Flex, Container, Grid) support semantic HTML output for better SEO and accessibility:\n\n| Component | Available Elements |\n|-----------|-------------------|\n| **Section** | `section`, `article`, `aside`, `nav`, `header`, `footer`, `main`, `div` |\n| **Flex** | `div`, `nav`, `ul`, `ol`, `aside`, `section` |\n| **Container** | `div`, `article`, `aside`, `section` |\n| **Grid** | `div`, `ul`, `ol` |\n\nSelect the appropriate HTML element in the component's sidebar to output semantic markup.\n\n### Responsive Controls\n\nLayout components support per-breakpoint customization:\n- **Dimensions** - Width, max-width, height constraints\n- **Padding/Margin** - Spacing per breakpoint\n- **Visibility** - Show/hide at specific breakpoints\n- **Viewport Preview** - Mobile, Tablet, Desktop, and Full Width options\n\n---\n\n## Custom Fields\n\nAll fields are imported from `@delmaredigital/payload-puck/fields`.\n\n### Field Reference\n\n| Field | Description |\n|-------|-------------|\n| **MediaField** | Payload media library integration |\n| **RichTextField** | Puck's native richtext with enhancements (colors, font sizes, highlights) |\n| **ColorPickerField** | Color picker with opacity and presets |\n| **BackgroundField** | Solid colors, gradients, images |\n| **PaddingField / MarginField** | Visual spacing editors |\n| **BorderField** | Border width, style, color, radius |\n| **DimensionsField** | Width/height with constraints |\n| **AlignmentField** | Text alignment (left, center, right) |\n| **ContentAlignmentField** | Visual 3x3 grid selector for positioning (d-pad style) |\n| **SizeField** | Preset sizes (sm, default, lg) with custom mode |\n| **AnimationField** | Entrance animations |\n| **ResponsiveVisibilityField** | Show/hide per breakpoint |\n| **FolderPickerField** | Hierarchical folder selection (page-tree) |\n| **PageSegmentField** | URL segment with slugification (page-tree) |\n| **SlugPreviewField** | Read-only computed slug (page-tree) |\n\n### Usage Example\n\n```typescript\nimport { createMediaField, createBackgroundField, backgroundValueToCSS } from '@delmaredigital/payload-puck/fields'\n\nconst HeroConfig = {\n  fields: {\n    image: createMediaField({ label: 'Background Image' }),\n    background: createBackgroundField({ label: 'Overlay' }),\n  },\n  render: ({ image, background }) =\u003e (\n    \u003csection style={{ background: backgroundValueToCSS(background) }}\u003e\n      {/* content */}\n    \u003c/section\u003e\n  ),\n}\n```\n\n### CSS Helper Functions\n\n```typescript\nimport {\n  backgroundValueToCSS,\n  dimensionsValueToCSS,\n  animationValueToCSS,\n  visibilityValueToCSS,\n  alignmentToFlexCSS,\n  alignmentToGridCSS,\n  sizeValueToCSS,\n  getSizeClasses,\n} from '@delmaredigital/payload-puck/fields'\n```\n\n### ContentAlignmentField Example\n\nThe ContentAlignmentField provides a visual 3x3 grid selector for content positioning:\n\n```typescript\nimport {\n  createContentAlignmentField,\n  alignmentToFlexCSS,\n  alignmentToGridCSS,\n} from '@delmaredigital/payload-puck/fields'\n\nconst BannerConfig = {\n  fields: {\n    contentPosition: createContentAlignmentField({ label: 'Content Position' }),\n  },\n  render: ({ contentPosition }) =\u003e (\n    \u003cdiv style={{\n      display: 'flex',\n      minHeight: '400px',\n      ...alignmentToFlexCSS(contentPosition), // Converts to justify-content + align-items\n    }}\u003e\n      \u003cdiv\u003ePositioned content\u003c/div\u003e\n    \u003c/div\u003e\n  ),\n}\n```\n\nHelper functions:\n- `alignmentToFlexCSS()` - For Flexbox containers (`justify-content` + `align-items`)\n- `alignmentToGridCSS()` - For Grid containers (`justify-content` + `align-content`)\n- `alignmentToPlaceSelfCSS()` - For individual grid items (`place-self`)\n- `alignmentToTailwind()` - Returns Tailwind classes (`justify-* items-*`)\n\n---\n\n## Building Custom Components\n\nThe plugin exports individual component configs and field factories for building custom Puck configurations.\n\n### Cherry-Picking Components\n\nImport only the components you need:\n\n```typescript\nimport {\n  SectionConfig,\n  HeadingConfig,\n  TextConfig,\n  ImageConfig,\n  ButtonConfig,\n} from '@delmaredigital/payload-puck/components'\n\nexport const puckConfig: Config = {\n  components: {\n    Section: SectionConfig,\n    Heading: HeadingConfig,\n    Text: TextConfig,\n    Image: ImageConfig,\n    Button: ButtonConfig,\n  },\n  categories: {\n    layout: { components: ['Section'] },\n    content: { components: ['Heading', 'Text', 'Image', 'Button'] },\n  },\n}\n```\n\n### Using Field Factories\n\nBuild custom components with pre-built fields:\n\n```typescript\nimport type { ComponentConfig } from '@puckeditor/core'\nimport {\n  createMediaField,\n  createBackgroundField,\n  createPaddingField,\n  backgroundValueToCSS,\n  paddingValueToCSS,\n} from '@delmaredigital/payload-puck/fields'\n\nexport const HeroConfig: ComponentConfig = {\n  label: 'Hero',\n  fields: {\n    image: createMediaField({ label: 'Background Image' }),\n    overlay: createBackgroundField({ label: 'Overlay' }),\n    padding: createPaddingField({ label: 'Padding' }),\n  },\n  defaultProps: {\n    image: null,\n    overlay: null,\n    padding: { top: 80, bottom: 80, left: 24, right: 24, unit: 'px', linked: false },\n  },\n  render: ({ image, overlay, padding }) =\u003e (\n    \u003csection\n      style={{\n        background: backgroundValueToCSS(overlay),\n        padding: paddingValueToCSS(padding),\n      }}\n    \u003e\n      {/* Hero content */}\n    \u003c/section\u003e\n  ),\n}\n```\n\n### Server vs Editor Variants\n\nFor `PageRenderer` (frontend), components need server-safe configs without React hooks:\n\n```typescript\n// Import server variants for PageRenderer\nimport {\n  SectionServerConfig,\n  HeadingServerConfig,\n  TextServerConfig,\n} from '@delmaredigital/payload-puck/components'\n\n\u003cPageRenderer config={{ components: { Section: SectionServerConfig, ... } }} data={page.puckData} /\u003e\n```\n\nFor custom components, create two files:\n- `MyComponent.tsx` - Full editor version with fields and interactivity\n- `MyComponent.server.tsx` - Server-safe version (no hooks, no 'use client')\n\n### Extending Built-in Configs\n\nUse `extendConfig()` to add custom components:\n\n```typescript\nimport { extendConfig, fullConfig } from '@delmaredigital/payload-puck/config/editor'\nimport { HeroConfig } from './components/Hero'\n\nexport const puckConfig = extendConfig({\n  base: fullConfig,\n  components: {\n    Hero: HeroConfig,\n  },\n  categories: {\n    custom: { title: 'Custom', components: ['Hero'] },\n  },\n})\n```\n\n\u003e **Note:** Use `fullConfig` from `/config/editor` for extending the editor. For server-side rendering, use `baseConfig` from `/config`.\n\n### Using Custom Config with Provider\n\nAfter creating your custom config, pass it to `PuckConfigProvider`:\n\n```typescript\n// components/admin/PuckProvider.tsx\n'use client'\nimport { PuckConfigProvider } from '@delmaredigital/payload-puck/client'\nimport { puckConfig } from '@/puck/config.editor'\nimport { siteLayouts } from '@/lib/puck-layouts'\n\nexport default function PuckProvider({ children }: { children: React.ReactNode }) {\n  return (\n    \u003cPuckConfigProvider config={puckConfig} layouts={siteLayouts}\u003e\n      {children}\n    \u003c/PuckConfigProvider\u003e\n  )\n}\n```\n\n**For Payload admin**, register the provider in your Payload config:\n\n```typescript\n// payload.config.ts\nexport default buildConfig({\n  admin: {\n    components: {\n      providers: ['@/components/admin/PuckProvider'],\n    },\n  },\n  // ...\n})\n```\n\nThis is the recommended pattern for Payload apps. The provider wraps only the admin UI, keeping your frontend layout separate.\n\n### Available Field Factories\n\n| Factory | Description |\n|---------|-------------|\n| `createMediaField()` | Payload media library picker |\n| `createBackgroundField()` | Solid, gradient, or image backgrounds |\n| `createColorPickerField()` | Color picker with opacity |\n| `createPaddingField()` | Visual padding editor |\n| `createMarginField()` | Visual margin editor |\n| `createBorderField()` | Border styling |\n| `createDimensionsField()` | Width/height constraints |\n| `createAnimationField()` | Entrance animations |\n| `createAlignmentField()` | Text alignment (left, center, right) |\n| `createContentAlignmentField()` | Visual 3x3 grid positioning selector |\n| `createSizeField()` | Size presets with custom mode |\n| `createRichTextField()` | Puck's native richtext with colors, font sizes, highlights |\n| `createResponsiveVisibilityField()` | Show/hide per breakpoint |\n\n### CSS Helper Functions\n\nConvert field values to CSS:\n\n```typescript\nimport {\n  backgroundValueToCSS,\n  paddingValueToCSS,\n  marginValueToCSS,\n  borderValueToCSS,\n  dimensionsValueToCSS,\n  colorValueToCSS,\n  alignmentToFlexCSS,\n  alignmentToGridCSS,\n  sizeValueToCSS,\n} from '@delmaredigital/payload-puck/fields'\n\nconst style = {\n  background: backgroundValueToCSS(props.background),\n  padding: paddingValueToCSS(props.padding),\n  ...dimensionsValueToCSS(props.dimensions),\n  ...alignmentToFlexCSS(props.contentAlignment),\n  ...sizeValueToCSS(props.size),\n}\n```\n\n---\n\n## Theming\n\nCustomize button styles, color presets, and focus rings:\n\n```typescript\nimport { PageRenderer } from '@delmaredigital/payload-puck/render'\nimport { ThemeProvider } from '@delmaredigital/payload-puck/theme'\n\n\u003cThemeProvider theme={{\n  buttonVariants: {\n    default: { classes: 'bg-primary text-white hover:bg-primary/90' },\n    secondary: { classes: 'bg-secondary text-foreground hover:bg-secondary/90' },\n  },\n  focusRingColor: 'focus:ring-primary',\n  colorPresets: [\n    { hex: '#3b82f6', label: 'Brand Blue' },\n    { hex: '#10b981', label: 'Success' },\n  ],\n}}\u003e\n  \u003cPageRenderer config={baseConfig} data={page.puckData} /\u003e\n\u003c/ThemeProvider\u003e\n```\n\nAccess theme values in custom components with `useTheme()`:\n\n```typescript\nimport { useTheme } from '@delmaredigital/payload-puck/theme'\n\nfunction CustomButton({ variant }) {\n  const theme = useTheme()\n  const classes = theme.buttonVariants[variant]?.classes\n  return \u003cbutton className={classes}\u003e...\u003c/button\u003e\n}\n```\n\n---\n\n## Layouts\n\nDefine page layouts with headers, footers, and styling:\n\n```typescript\n// lib/puck-layouts.ts\nimport type { LayoutDefinition } from '@delmaredigital/payload-puck/layouts'\nimport { SiteHeader } from '@/components/header'\nimport { SiteFooter } from '@/components/footer'\n\nexport const siteLayouts: LayoutDefinition[] = [\n  {\n    value: 'default',\n    label: 'Default',\n    description: 'Standard page with header and footer',\n    maxWidth: '1200px',\n    header: SiteHeader,\n    footer: SiteFooter,\n    stickyHeaderHeight: 80,\n  },\n  {\n    value: 'landing',\n    label: 'Landing',\n    description: 'Full-width landing page',\n    fullWidth: true,\n  },\n]\n```\n\nPass layouts to the `PuckConfigProvider`:\n\n```typescript\n\u003cPuckConfigProvider config={editorConfig} layouts={siteLayouts}\u003e\n  {children}\n\u003c/PuckConfigProvider\u003e\n```\n\nAnd use them with `PageRenderer`:\n\n```typescript\nimport { LayoutWrapper } from '@delmaredigital/payload-puck/layouts'\n\nconst layout = siteLayouts.find(l =\u003e l.value === page.puckData?.root?.props?.pageLayout)\n\n\u003cLayoutWrapper layout={layout}\u003e\n  \u003cPageRenderer config={baseConfig} data={page.puckData} /\u003e\n\u003c/LayoutWrapper\u003e\n```\n\n### Avoiding Double Headers/Footers\n\nWhen your host app already provides a global header/footer via its root layout (e.g., Next.js `layout.tsx`), use `createRenderLayouts()` to strip them from Puck layouts:\n\n```typescript\nimport { HybridPageRenderer, createRenderLayouts } from '@delmaredigital/payload-puck/render'\nimport { siteLayouts } from '@/lib/puck-layouts' // layouts with header/footer for editor\n\n// Strip header/footer for rendering (host app layout provides them)\nconst renderLayouts = createRenderLayouts(siteLayouts)\n\nexport function PageRenderer({ page }) {\n  const layout = renderLayouts.find(l =\u003e l.value === page.puckData?.root?.props?.pageLayout)\n\n  return (\n    \u003cLayoutWrapper layout={layout}\u003e\n      \u003cHybridPageRenderer page={page} config={baseConfig} /\u003e\n    \u003c/LayoutWrapper\u003e\n  )\n}\n```\n\nThis pattern keeps header/footer in your editor layouts for realistic preview, but avoids double headers when rendering.\n\n---\n\n## Dark Mode Support\n\nThe Puck editor automatically detects PayloadCMS dark mode and applies CSS overrides to ensure visibility. It also provides a preview toggle to test how pages look in both light and dark modes.\n\n### How It Works\n\n1. **Editor UI**: Automatically detects dark mode via `.dark` class (PayloadCMS) or `prefers-color-scheme` (OS preference), then injects Puck CSS variable overrides\n2. **Preview Iframe**: A sun/moon toggle lets you switch the preview content between light and dark modes independently from the editor UI\n\n### Configuration\n\nDark mode is enabled by default. You can customize via props on `PuckEditor`:\n\n```typescript\n\u003cPuckEditor\n  autoDetectDarkMode={true}           // Auto-detect PayloadCMS dark mode (default: true)\n  showPreviewDarkModeToggle={true}    // Show light/dark toggle in header (default: true)\n  initialPreviewDarkMode={false}      // Start preview in light mode (default: false)\n/\u003e\n```\n\n### Using Components Directly\n\nFor custom editor implementations:\n\n```typescript\nimport {\n  DarkModeStyles,\n  PreviewModeToggle,\n  useDarkMode,\n} from '@delmaredigital/payload-puck/editor'\n\nfunction CustomEditor() {\n  const { isDarkMode, source } = useDarkMode()\n  const [previewDark, setPreviewDark] = useState(false)\n\n  return (\n    \u003c\u003e\n      {/* Inject dark mode CSS overrides when detected */}\n      \u003cDarkModeStyles /\u003e\n\n      {/* Toggle for preview iframe */}\n      \u003cPreviewModeToggle\n        isDarkMode={previewDark}\n        onToggle={setPreviewDark}\n      /\u003e\n\n      \u003cPuck ... /\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### Detecting Theme in Puck Components\n\nIf your Puck components need to dynamically adjust JavaScript-controlled styles based on the preview theme (not just CSS), use the `usePuckPreviewTheme()` hook:\n\n```typescript\nimport { usePuckPreviewTheme } from '@delmaredigital/payload-puck/editor'\nimport { useEffect, useState } from 'react'\n\nfunction useDetectTheme() {\n  const puckTheme = usePuckPreviewTheme()\n\n  // For frontend (non-editor), read from DOM\n  const [domTheme, setDomTheme] = useState(() =\u003e\n    typeof document !== 'undefined'\n      ? document.documentElement.getAttribute('data-theme') === 'dark'\n      : false\n  )\n\n  useEffect(() =\u003e {\n    const observer = new MutationObserver((mutations) =\u003e {\n      for (const mutation of mutations) {\n        if (mutation.attributeName === 'data-theme') {\n          setDomTheme(document.documentElement.getAttribute('data-theme') === 'dark')\n        }\n      }\n    })\n    observer.observe(document.documentElement, { attributes: true })\n    return () =\u003e observer.disconnect()\n  }, [])\n\n  // In editor: use context. On frontend: use DOM.\n  return puckTheme !== null ? puckTheme : domTheme\n}\n```\n\n**Why this is needed:** CSS dark mode variants (like Tailwind's `dark:` classes) work automatically via the `data-theme` attribute. However, if you need to conditionally render different JavaScript values (like overlay colors), those won't update reactively when the preview toggle changes. The context provides reactive updates.\n\n---\n\n## Page-Tree Integration\n\nWhen `@delmaredigital/payload-page-tree` is detected, the plugin automatically adds folder management to the Puck sidebar.\n\n\u003e **⚠️ Plugin Order:** When using both plugins with `autoGenerateCollection: true`, Puck must run BEFORE page-tree. See [Plugin Order](#plugin-order).\n\n### How It Works\n\nThe plugin checks if your collection has a `pageSegment` field (page-tree's signature). When detected:\n\n1. **Folder Picker** - Select a folder from the hierarchy\n2. **Page Segment** - Edit the page's URL segment\n3. **Slug Preview** - See the computed slug (folder path + segment)\n\n### Plugin Configuration\n\n```typescript\ncreatePuckPlugin({\n  // Auto-detect (default)\n  pageTreeIntegration: undefined,\n\n  // Explicitly enable with custom config\n  pageTreeIntegration: {\n    folderSlug: 'payload-folders',\n    pageSegmentFieldName: 'pageSegment',\n  },\n\n  // Explicitly disable\n  pageTreeIntegration: false,\n})\n```\n\n### Custom Editor UI\n\nFor custom editor implementations outside Payload admin, use the `hasPageTree` prop:\n\n```typescript\nimport { PuckEditor } from '@delmaredigital/payload-puck/client'\nimport { editorConfig } from '@delmaredigital/payload-puck/config/editor'\n\n\u003cPuckEditor\n  config={editorConfig}\n  pageId={page.id}\n  initialData={page.puckData}\n  pageTitle={page.title}\n  pageSlug={page.slug}\n  apiEndpoint=\"/api/puck/pages\"\n  hasPageTree={true}\n  folder={page.folder}\n  pageSegment={page.pageSegment}\n/\u003e\n```\n\n### Performance\n\nDetection is instant - it reads the in-memory collection config, no database queries.\n\n---\n\n## Hybrid Integration\n\nAdd Puck to existing collections with legacy blocks.\n\n### Automatic (Recommended)\n\nIf you already have a `pages` collection, the plugin adds only the Puck-specific fields:\n\n```typescript\n// payload.config.ts\nexport default buildConfig({\n  collections: [\n    {\n      slug: 'pages',\n      fields: [\n        { name: 'title', type: 'text', required: true },\n        { name: 'layout', type: 'blocks', blocks: [HeroBlock, CTABlock] },\n      ],\n    },\n  ],\n  plugins: [\n    createPuckPlugin({ pagesCollection: 'pages' }),\n  ],\n})\n```\n\nThe `editorVersion` field auto-detects whether pages use legacy blocks or Puck.\n\n### Manual with `getPuckCollectionConfig()` (Recommended)\n\nWhen you need the `isHomepage` field, use `getPuckCollectionConfig()` which returns both fields AND hooks. This ensures the homepage uniqueness validation is included:\n\n```typescript\nimport { getPuckCollectionConfig } from '@delmaredigital/payload-puck'\n\nconst { fields: puckFields, hooks: puckHooks } = getPuckCollectionConfig({\n  includeSEO: true,\n  includeEditorVersion: true,\n  includePageLayout: true,\n  includeIsHomepage: true, // Includes uniqueness hook automatically\n})\n\nexport const Pages: CollectionConfig = {\n  slug: 'pages',\n  hooks: {\n    beforeChange: [\n      ...(puckHooks.beforeChange ?? []),\n      // Your other beforeChange hooks...\n    ],\n    afterChange: [\n      // Your afterChange hooks...\n    ],\n  },\n  fields: [\n    { name: 'title', type: 'text' },\n    { name: 'layout', type: 'blocks', blocks: [...] },\n    ...puckFields,\n  ],\n}\n```\n\n### Manual with `getPuckFields()` (Fields Only)\n\nIf you don't need `isHomepage` or want to configure hooks manually:\n\n```typescript\nimport { getPuckFields, createIsHomepageUniqueHook } from '@delmaredigital/payload-puck'\n\nexport const Pages: CollectionConfig = {\n  slug: 'pages',\n  hooks: {\n    // Required if using includeIsHomepage: true\n    beforeChange: [createIsHomepageUniqueHook()],\n  },\n  fields: [\n    { name: 'title', type: 'text' },\n    { name: 'layout', type: 'blocks', blocks: [...] },\n    ...getPuckFields({\n      includeSEO: true,\n      includeEditorVersion: true,\n      includePageLayout: true,\n      includeIsHomepage: true, // Note: requires hook above for uniqueness\n    }),\n  ],\n}\n```\n\n\u003e **Note:** The `isHomepage` field allows marking one page as the homepage. The `createIsHomepageUniqueHook()` ensures only one page can be marked as homepage at a time, prompting users to swap if a homepage already exists.\n\n### Rendering Hybrid Pages\n\n```typescript\nimport { HybridPageRenderer } from '@delmaredigital/payload-puck/render'\nimport { LegacyBlockRenderer } from '@/components/LegacyBlockRenderer'\n\n\u003cHybridPageRenderer\n  page={page}\n  config={puckConfig}\n  legacyRenderer={(blocks) =\u003e \u003cLegacyBlockRenderer blocks={blocks} /\u003e}\n/\u003e\n```\n\n---\n\n## AI Integration\n\n\u003e **Early Preview:** While Puck's AI features are powerful, this plugin's implementation is still in early stages and under active development. Expect changes as we refine the integration.\n\nThe plugin integrates with [Puck AI](https://puckeditor.com/docs/integrating-puck/ai) to enable AI-assisted page generation. Users can describe what they want in natural language, and the AI builds complete page layouts using your components.\n\n### Requirements\n\n- `PUCK_API_KEY` environment variable (from [Puck Cloud](https://puckeditor.com))\n- AI features require `@puckeditor/plugin-ai` and `@puckeditor/cloud-client` (bundled with the plugin)\n\n### Quick Start\n\nEnable AI in your plugin configuration:\n\n```typescript\ncreatePuckPlugin({\n  pagesCollection: 'pages',\n  ai: {\n    enabled: true,\n    context: 'We are Acme Corp, a B2B SaaS company. Use professional language.',\n  },\n})\n```\n\nThis automatically:\n- Registers the AI chat endpoint at `/api/puck/ai`\n- Adds the AI chat plugin to the editor\n- Applies comprehensive component instructions for better generation quality\n\n### Dynamic Business Context\n\nInstead of hardcoding context in your config, you can manage it through Payload admin:\n\n```typescript\ncreatePuckPlugin({\n  ai: {\n    enabled: true,\n    contextCollection: true,  // Creates puck-ai-context collection\n  },\n})\n```\n\nThis creates a `puck-ai-context` collection where you can add entries for:\n- **Brand Guidelines** - Colors, fonts, brand voice\n- **Tone of Voice** - How to communicate\n- **Product Information** - What you sell/offer\n- **Industry Context** - Your market and audience\n- **Technical Requirements** - Specific constraints\n- **Page Patterns** - Common layout structures\n\nContext entries can be enabled/disabled and ordered. The AI receives all enabled entries sorted by order.\n\n### Context Editor Plugin\n\nWhen `contextCollection: true`, a \"Context\" panel appears in the Puck plugin rail. Users can view, create, edit, and toggle context entries directly in the editor without visiting Payload admin.\n\n### Prompt Management\n\nStore reusable prompts in Payload:\n\n```typescript\ncreatePuckPlugin({\n  ai: {\n    enabled: true,\n    promptsCollection: true,  // Creates puck-ai-prompts collection\n    examplePrompts: [\n      { label: 'Landing page', prompt: 'Create a landing page for...' },\n    ],\n  },\n})\n```\n\nPrompts from the collection appear in the AI chat interface. A \"Prompts\" panel in the plugin rail allows in-editor prompt management.\n\n### Custom Tools\n\nEnable the AI to query your data:\n\n```typescript\nimport { z } from 'zod'\n\ncreatePuckPlugin({\n  ai: {\n    enabled: true,\n    tools: {\n      getProducts: {\n        description: 'Get products from the database',\n        inputSchema: z.object({ category: z.string() }),\n        execute: async ({ category }, { payload }) =\u003e {\n          return await payload.find({\n            collection: 'products',\n            where: { category: { equals: category } },\n          })\n        },\n      },\n    },\n  },\n})\n```\n\nTools receive a context object with the Payload instance and authenticated user.\n\n### AI Configuration Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `enabled` | `false` | Enable AI features |\n| `context` | `undefined` | Static system context for the AI |\n| `contextCollection` | `false` | Create `puck-ai-context` collection for dynamic context |\n| `promptsCollection` | `false` | Create `puck-ai-prompts` collection for reusable prompts |\n| `examplePrompts` | `[]` | Static example prompts for the chat interface |\n| `tools` | `undefined` | Custom tools for AI to query your system |\n| `componentInstructions` | `undefined` | Override default component AI instructions |\n\n### Component Instructions\n\nThe plugin includes comprehensive instructions for all built-in components, teaching the AI:\n- Correct field names and values\n- Component composition patterns\n- Page structure best practices (Hero → Features → CTA flow)\n- Semantic HTML usage\n\nTo customize or extend:\n\n```typescript\ncreatePuckPlugin({\n  ai: {\n    enabled: true,\n    componentInstructions: {\n      Heading: {\n        ai: { instructions: 'Use our brand voice: professional but approachable' },\n        fields: {\n          text: { ai: { instructions: 'Keep under 8 words' } },\n        },\n      },\n    },\n  },\n})\n```\n\n### Standalone API Routes\n\nFor custom implementations outside the plugin:\n\n```typescript\n// app/api/puck/[...all]/route.ts\nimport { createPuckAiApiRoutes } from '@delmaredigital/payload-puck/ai'\nimport config from '@payload-config'\n\nexport const POST = createPuckAiApiRoutes({\n  payloadConfig: config,\n  auth: {\n    authenticate: async (request) =\u003e {\n      // Your auth implementation\n      return { user: { id: '...' } }\n    },\n  },\n  ai: {\n    context: 'Your business context...',\n  },\n})\n```\n\n### AI Exports\n\n```typescript\nimport {\n  // Plugins\n  createAiPlugin,\n  createPromptEditorPlugin,\n  createContextEditorPlugin,\n\n  // Hooks\n  useAiPrompts,\n  useAiContext,\n\n  // Config utilities\n  injectAiConfig,\n  comprehensiveComponentAiConfig,\n  pagePatternSystemContext,\n\n  // API routes\n  createPuckAiApiRoutes,\n  createAiGenerate,\n} from '@delmaredigital/payload-puck/ai'\n```\n\n---\n\n## Plugin Order\n\nWhen using `autoGenerateCollection: true` (the default) with `@delmaredigital/payload-page-tree`, **plugin order matters**.\n\n### The Issue\n\nThe page-tree plugin validates configured collections when it initializes. If Puck hasn't created the collection yet, page-tree won't see it and will skip adding its fields (folder relationships, slug generation, etc.).\n\n### Correct Order\n\n```typescript\n// ✅ CORRECT: Puck creates the collection before page-tree runs\nexport const plugins = [\n  createPuckPlugin({ pagesCollection: 'pages' }),  // Creates Pages first\n  pageTreePlugin({ collections: ['pages'] }),      // Now sees Pages\n]\n\n// ❌ WRONG: page-tree runs before Pages exists\nexport const plugins = [\n  pageTreePlugin({ collections: ['pages'] }),      // Pages doesn't exist!\n  createPuckPlugin({ pagesCollection: 'pages' }),  // Creates Pages too late\n]\n```\n\n### When Order Doesn't Matter\n\nIf you define your collection manually (with `autoGenerateCollection: false`), order doesn't matter because the collection already exists in your config:\n\n```typescript\nexport default buildConfig({\n  collections: [Pages],  // Collection exists before plugins run\n  plugins: [\n    pageTreePlugin({ collections: ['pages'] }),\n    createPuckPlugin({ pagesCollection: 'pages', autoGenerateCollection: false }),\n  ],\n})\n```\n\nSee also: [payload-page-tree Plugin Order documentation](https://github.com/delmaredigital/payload-page-tree#plugin-order-critical)\n\n---\n\n## Advanced Configuration\n\n### Plugin Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `pagesCollection` | `'pages'` | Collection slug to use for pages |\n| `autoGenerateCollection` | `true` | Create the collection if it doesn't exist, or add Puck fields to existing (see [Plugin Order](#plugin-order)) |\n| `enableEndpoints` | `true` | Register API endpoints at `/api/puck/:collection` for the editor |\n| `enableAdminView` | `true` | Register the Puck editor view in Payload admin |\n| `adminViewPath` | `'/puck-editor'` | Path for the editor (full path: `/admin/puck-editor/:collection/:id`) |\n| `pageTreeIntegration` | auto-detect | Integration with `@delmaredigital/payload-page-tree` |\n| `layouts` | `undefined` | Layout definitions for page templates |\n| `editorStylesheet` | `undefined` | Path to CSS file for editor iframe styling (e.g., `'src/app/globals.css'`) |\n| `editorStylesheetCompiled` | `undefined` | Path to pre-compiled CSS for production (e.g., `'/puck-editor-styles.css'`) |\n| `editorStylesheetUrls` | `[]` | Additional stylesheet URLs for the editor (e.g., Google Fonts) |\n| `previewUrl` | `undefined` | URL for \"View\" button - string or function receiving page data |\n\n```typescript\ncreatePuckPlugin({\n  pagesCollection: 'pages',\n  autoGenerateCollection: true,\n  enableEndpoints: true,\n  enableAdminView: true,\n  adminViewPath: '/puck-editor',\n  pageTreeIntegration: undefined, // auto-detects\n\n  // Collection overrides (merged with generated collection)\n  collectionOverrides: {\n    admin: {\n      defaultColumns: ['title', 'slug', 'updatedAt'],\n    },\n  },\n\n  // Access control\n  access: {\n    read: () =\u003e true,\n    create: ({ req }) =\u003e !!req.user,\n    update: ({ req }) =\u003e !!req.user,\n    delete: ({ req }) =\u003e !!req.user,\n  },\n})\n```\n\n### Preview URL (View Button)\n\nThe \"View\" button in the editor opens the published page in a new tab. By default, it navigates to `/{slug}` (or `/` for homepage). Use the `previewUrl` option to customize this behavior.\n\n```typescript\n// Simple static URL pattern\ncreatePuckPlugin({\n  previewUrl: '/preview',\n})\n\n// Dynamic prefix based on page data\ncreatePuckPlugin({\n  previewUrl: (page) =\u003e `/${page.slug || ''}`,\n})\n\n// Organization-scoped pages (multi-tenant)\n// The function receives the full page document with relationships populated\ncreatePuckPlugin({\n  previewUrl: (page) =\u003e {\n    const orgSlug = page.organization?.slug || 'default'\n    // Return a function that handles homepage vs regular pages\n    return (slug) =\u003e slug ? `/${orgSlug}/${slug}` : `/${orgSlug}`\n  },\n})\n```\n\nWhen `previewUrl` is a function, the page document is fetched with `depth: 1` so relationship fields (like `organization`) are populated with their full data.\n\n### Editor Stylesheet (Iframe Styling)\n\nThe Puck editor renders page content in an iframe. By default, this iframe doesn't have access to your frontend's CSS (Tailwind utilities, CSS variables, fonts). The `editorStylesheet` option solves this by compiling and serving your CSS.\n\n#### Development (Runtime Compilation)\n\nIn development, CSS is compiled at runtime for hot reload support:\n\n```typescript\ncreatePuckPlugin({\n  pagesCollection: 'pages',\n  editorStylesheet: 'src/app/(frontend)/globals.css',\n  editorStylesheetUrls: [\n    'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700\u0026display=swap'\n  ],\n})\n```\n\n**How it works:**\n1. You specify your CSS file path in the plugin config\n2. The plugin creates an endpoint at `/api/puck/styles`\n3. On first request, the CSS is compiled with PostCSS/Tailwind and cached\n4. The iframe loads this compiled CSS\n\n#### Production (Build-Time Compilation)\n\nRuntime compilation fails on serverless platforms (Vercel, Netlify, etc.) because source CSS files aren't deployed—only compiled `.next` output is included. Use `withPuckCSS()` to compile CSS at build time:\n\n**Step 1: Wrap your Next.js config**\n\n```javascript\n// next.config.js\nimport { withPuckCSS } from '@delmaredigital/payload-puck/next'\nimport { withPayload } from '@payloadcms/next/withPayload'\n\nconst nextConfig = {\n  // your config...\n}\n\nexport default withPuckCSS({\n  cssInput: 'src/app/(frontend)/globals.css',\n})(withPayload(nextConfig))\n```\n\n**Step 2: Add the compiled path to your plugin config**\n\n```typescript\ncreatePuckPlugin({\n  pagesCollection: 'pages',\n  editorStylesheet: 'src/app/(frontend)/globals.css',      // For dev (runtime)\n  editorStylesheetCompiled: '/puck-editor-styles.css',     // For prod (static)\n  editorStylesheetUrls: [\n    'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700\u0026display=swap'\n  ],\n})\n```\n\n**How it works:**\n1. During `next build`, the wrapper compiles your CSS to `public/puck-editor-styles.css`\n2. In production (`NODE_ENV=production`), the plugin serves the static file\n3. In development, runtime compilation continues working for hot reload\n\n**`withPuckCSS` options:**\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `cssInput` | (required) | Path to source CSS file |\n| `cssOutput` | `'puck-editor-styles.css'` | Output filename in `public/` |\n| `skipInDev` | `true` | Skip compilation in development |\n\n#### Requirements\n\n- `postcss` must be installed in your project\n- For Tailwind v4: `@tailwindcss/postcss`\n- For Tailwind v3: `tailwindcss`\n\n---\n\n### Custom API Routes (Advanced)\n\nThe built-in endpoints handle most use cases. Only disable them if you need custom authentication or middleware.\n\nIf needed, three route factories are available:\n\n| Factory | Route Pattern | Methods |\n|---------|---------------|---------|\n| `createPuckApiRoutes` | `/api/puck/[collection]` | GET (list), POST (create) |\n| `createPuckApiRoutesWithId` | `/api/puck/[collection]/[id]` | GET, PATCH, DELETE |\n| `createPuckApiRoutesVersions` | `/api/puck/[collection]/[id]/versions` | GET, POST (restore) |\n\nSee the JSDoc in `@delmaredigital/payload-puck/api` for usage examples.\n\n---\n\n## Export Reference\n\n| Export Path | Description |\n|-------------|-------------|\n| `@delmaredigital/payload-puck` | Plugin creation, field utilities |\n| `@delmaredigital/payload-puck/plugin` | `createPuckPlugin` |\n| `@delmaredigital/payload-puck/config` | `baseConfig`, `createConfig()`, `extendConfig()` |\n| `@delmaredigital/payload-puck/config/editor` | `editorConfig` for editing |\n| `@delmaredigital/payload-puck/client` | `PuckEditor`, `PuckConfigProvider`, page-tree utilities |\n| `@delmaredigital/payload-puck/editor` | `PuckEditor`, `HeaderActions`, editor hooks |\n| `@delmaredigital/payload-puck/rsc` | `PuckEditorView` for Payload admin views |\n| `@delmaredigital/payload-puck/render` | `PageRenderer`, `HybridPageRenderer` |\n| `@delmaredigital/payload-puck/fields` | Custom Puck fields and CSS helpers |\n| `@delmaredigital/payload-puck/components` | Component configs for custom configurations |\n| `@delmaredigital/payload-puck/theme` | `ThemeProvider`, theme utilities |\n| `@delmaredigital/payload-puck/layouts` | Layout definitions, `LayoutWrapper` |\n| `@delmaredigital/payload-puck/api` | API route factories (for custom implementations) |\n| `@delmaredigital/payload-puck/ai` | AI plugins, hooks, config utilities, API routes |\n| `@delmaredigital/payload-puck/next` | `withPuckCSS` Next.js config wrapper for build-time CSS |\n| `@delmaredigital/payload-puck/admin/client` | `EditWithPuckButton`, `EditWithPuckCell` |\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdelmaredigital%2Fpayload-puck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdelmaredigital%2Fpayload-puck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdelmaredigital%2Fpayload-puck/lists"}