@delmaredigital/payload-puck 0.6.14 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,62 +2,51 @@
2
2
 
3
3
  A 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.
4
4
 
5
- [![Demo](https://img.shields.io/badge/Demo-Live-blue)](https://demo.delmaredigital.com)
6
- [![Starter Template](https://img.shields.io/badge/Starter-Template-green)](https://github.com/delmaredigital/dd-starter)
7
- [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdelmaredigital%2Fdd-starter&project-name=my-payload-site&build-command=pnpm%20run%20ci&env=PAYLOAD_SECRET,BETTER_AUTH_SECRET&stores=%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)
5
+ <p align="center">
6
+ <a href="https://demo.delmaredigital.com"><img src="https://img.shields.io/badge/Live_Demo-Try_It_Now-2ea44f?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo - Try It Now"></a>
7
+ &nbsp;&nbsp;
8
+ <a href="https://github.com/delmaredigital/dd-starter"><img src="https://img.shields.io/badge/Starter_Template-Use_This-blue?style=for-the-badge&logo=github&logoColor=white" alt="Starter Template - Use This"></a>
9
+ </p>
8
10
 
9
- ---
10
-
11
- ## Table of Contents
12
-
13
- - [Installation](#installation)
14
- - [Quick Start](#quick-start)
15
- - [Adding to Existing Projects](#adding-to-existing-projects)
16
- - [Styling Setup](#styling-setup)
17
- - [Core Concepts](#core-concepts)
18
- - [Components](#components)
19
- - [Custom Fields](#custom-fields)
20
- - [Building Custom Components](#building-custom-components)
21
- - [Theming](#theming)
22
- - [Layouts](#layouts)
23
- - [Dark Mode Support](#dark-mode-support)
24
- - [Page-Tree Integration](#page-tree-integration)
25
- - [Hybrid Integration](#hybrid-integration)
26
- - [AI Integration](#ai-integration)
27
- - [Plugin Order](#plugin-order)
28
- - [Advanced Configuration](#advanced-configuration)
29
- - [License](#license)
11
+ <p align="center">
12
+ <a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdelmaredigital%2Fdd-starter&project-name=my-payload-site&build-command=pnpm%20run%20ci&env=PAYLOAD_SECRET,BETTER_AUTH_SECRET&stores=%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"><img src="https://vercel.com/button" alt="Deploy with Vercel" height="32"></a>
13
+ </p>
30
14
 
31
15
  ---
32
16
 
33
- ## Installation
17
+ ## Documentation
34
18
 
35
- ### Requirements
19
+ **[Full documentation &rarr;](https://delmaredigital.github.io/payload-puck/)**
36
20
 
37
- | Dependency | Version | Purpose |
38
- |------------|---------|---------|
39
- | `@puckeditor/core` | >= 0.21.0 | Visual editor core |
40
- | `payload` | >= 3.69.0 | CMS backend |
41
- | `@payloadcms/next` | >= 3.69.0 | Payload Next.js integration |
42
- | `next` | >= 15.4.8 | React framework |
43
- | `react` | >= 19.2.1 | UI library |
44
- | `@tailwindcss/typography` | >= 0.5.0 | RichText component styling |
21
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/delmaredigital/payload-puck)
45
22
 
46
- > **Note:** Puck 0.21+ moved from `@measured/puck` to `@puckeditor/core`. This plugin requires the new package scope.
23
+ Covers installation, configuration, components, custom fields, theming, layouts, dark mode, page-tree integration, hybrid integration, AI integration, and more.
24
+
25
+ ---
47
26
 
48
- ### Install
27
+ ## Install
49
28
 
50
29
  ```bash
51
30
  pnpm add @delmaredigital/payload-puck @puckeditor/core
52
31
  ```
53
32
 
33
+ ### Requirements
34
+
35
+ | Dependency | Version |
36
+ |------------|---------|
37
+ | `@puckeditor/core` | >= 0.21.0 |
38
+ | `payload` | >= 3.69.0 |
39
+ | `@payloadcms/next` | >= 3.69.0 |
40
+ | `next` | >= 15.4.8 |
41
+ | `react` | >= 19.2.1 |
42
+
43
+ > **Note:** Puck 0.21+ moved from `@measured/puck` to `@puckeditor/core`. This plugin requires the new package scope.
44
+
54
45
  ---
55
46
 
56
47
  ## Quick Start
57
48
 
58
- The plugin integrates directly into Payload's admin UI with minimal configuration. API endpoints and admin views are registered automatically.
59
-
60
- ### Step 1: Add the Plugin
49
+ ### 1. Add the Plugin
61
50
 
62
51
  ```typescript
63
52
  // src/payload.config.ts
@@ -67,25 +56,16 @@ import { createPuckPlugin } from '@delmaredigital/payload-puck/plugin'
67
56
  export default buildConfig({
68
57
  plugins: [
69
58
  createPuckPlugin({
70
- pagesCollection: 'pages', // Collection slug (default: 'pages')
59
+ pagesCollection: 'pages',
71
60
  }),
72
61
  ],
73
- // ...
74
62
  })
75
63
  ```
76
64
 
77
- This automatically:
78
- - Creates a `pages` collection with Puck fields (or adds fields to your existing collection)
79
- - Registers API endpoints at `/api/puck/:collection`
80
- - Adds the Puck editor view at `/admin/puck-editor/:collection/:id`
81
- - Adds "Edit with Puck" buttons to the admin UI
82
-
83
- ### Step 2: Provide Puck Configuration
84
-
85
- Wrap your app with `PuckConfigProvider` to supply the Puck configuration. This makes the config available to the editor via React context.
65
+ ### 2. Provide Puck Configuration
86
66
 
87
67
  ```typescript
88
- // app/(app)/layout.tsx (covers both admin and frontend)
68
+ // app/(app)/layout.tsx
89
69
  import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
90
70
  import { editorConfig } from '@delmaredigital/payload-puck/config/editor'
91
71
 
@@ -102,61 +82,19 @@ export default function RootLayout({ children }: { children: React.ReactNode })
102
82
  }
103
83
  ```
104
84
 
105
- > **Tip:** `PuckConfigProvider` also accepts `layouts` and `theme` props. See [Layouts](#layouts) and [Theming](#theming) sections.
106
-
107
- > **Note:** For custom editor UIs (outside Payload admin), you can also pass the config directly to `PuckEditor` instead of using the context provider.
108
-
109
- **Alternative: Payload Admin Provider (vanilla starter pattern)**
110
-
111
- If you're using the vanilla Payload starter structure, you can register the provider via the admin config instead:
112
-
113
- ```typescript
114
- // src/payload.config.ts
115
- export default buildConfig({
116
- admin: {
117
- components: {
118
- providers: ['@/components/admin/PuckProvider'],
119
- },
120
- },
121
- // ...
122
- })
123
- ```
124
-
125
- ```typescript
126
- // src/components/admin/PuckProvider.tsx
127
- 'use client'
128
-
129
- import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
130
- import { editorConfig } from '@delmaredigital/payload-puck/config/editor'
131
-
132
- export default function PuckProvider({ children }: { children: React.ReactNode }) {
133
- return <PuckConfigProvider config={editorConfig}>{children}</PuckConfigProvider>
134
- }
135
- ```
136
-
137
- ### Step 3: Create a Frontend Route
138
-
139
- The plugin can't auto-create frontend routes (Next.js App Router is file-based), but here's copy-paste ready code:
140
-
141
- <details>
142
- <summary><strong>📄 app/(frontend)/[[...slug]]/page.tsx</strong> (click to expand)</summary>
85
+ ### 3. Create a Frontend Route
143
86
 
144
87
  ```typescript
88
+ // app/(frontend)/[[...slug]]/page.tsx
145
89
  import { getPayload } from 'payload'
146
90
  import config from '@payload-config'
147
91
  import { PageRenderer } from '@delmaredigital/payload-puck/render'
148
92
  import { baseConfig } from '@delmaredigital/payload-puck/config'
149
93
  import { notFound } from 'next/navigation'
150
- import type { Metadata } from 'next'
151
94
 
152
- // Fetch page by slug (or homepage if no slug)
153
- // Only returns published pages - unpublished pages will 404
154
95
  async function getPage(slug?: string[]) {
155
96
  const payload = await getPayload({ config })
156
97
  const slugPath = slug?.join('/') || ''
157
-
158
- // Try to find by slug, or find homepage
159
- // Filter for published pages only (_status: 'published')
160
98
  const { docs } = await payload.find({
161
99
  collection: 'pages',
162
100
  where: {
@@ -169,1378 +107,24 @@ async function getPage(slug?: string[]) {
169
107
  },
170
108
  limit: 1,
171
109
  })
172
-
173
110
  return docs[0] || null
174
111
  }
175
112
 
176
- // Generate metadata from page SEO fields
177
- export async function generateMetadata({
178
- params
179
- }: {
180
- params: Promise<{ slug?: string[] }>
181
- }): Promise<Metadata> {
182
- const { slug } = await params
183
- const page = await getPage(slug)
184
-
185
- if (!page) return {}
186
-
187
- return {
188
- title: page.meta?.title || page.title,
189
- description: page.meta?.description,
190
- }
191
- }
192
-
193
- // Render the page
194
- export default async function Page({
195
- params
196
- }: {
197
- params: Promise<{ slug?: string[] }>
198
- }) {
113
+ export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
199
114
  const { slug } = await params
200
115
  const page = await getPage(slug)
201
-
202
116
  if (!page) notFound()
203
-
204
- return <PageRenderer config={baseConfig} data={page.puckData} />
205
- }
206
- ```
207
-
208
- </details>
209
-
210
- > **Note:** The `[[...slug]]` pattern with double brackets makes the slug optional, so this handles both `/` (homepage) and `/any/path`.
211
-
212
- ### That's It!
213
-
214
- - The plugin registers the editor view at `/admin/puck-editor/:collection/:id`
215
- - "Edit with Puck" buttons appear in the collection list view
216
- - The editor runs inside Payload's admin UI with full navigation
217
- - API endpoints are handled automatically via Payload's endpoint system
218
-
219
- ### Adding to Existing Projects
220
-
221
- > **⚠️ Important:** If you're adding Puck to a project with existing frontend routes, you must update those routes to render Puck content.
222
-
223
- When adding Puck to an existing Payload project:
224
-
225
- 1. ✅ Add the plugin to `payload.config.ts`
226
- 2. ✅ Add `PuckConfigProvider` to your admin layout
227
- 3. ⚠️ **Update your frontend page templates** to render `puckData`
228
-
229
- Without step 3, Puck pages will render blank because your existing routes only look for legacy block fields like `layout` or `hero`.
230
-
231
- **Option A: Hybrid Rendering (recommended)**
232
-
233
- Use `HybridPageRenderer` to render Puck pages. For new projects, this is all you need:
234
-
235
- ```typescript
236
- import { HybridPageRenderer } from '@delmaredigital/payload-puck/render'
237
- import { baseConfig } from '@delmaredigital/payload-puck/config'
238
-
239
- export default async function Page({ params }) {
240
- const page = await getPage(params.slug)
241
- return <HybridPageRenderer page={page} config={baseConfig} />
242
- }
243
- ```
244
-
245
- If you're migrating an existing site with legacy Payload blocks, provide a `legacyRenderer`:
246
-
247
- ```typescript
248
- import { HybridPageRenderer } from '@delmaredigital/payload-puck/render'
249
- import { baseConfig } from '@delmaredigital/payload-puck/config'
250
- import { LegacyBlockRenderer } from '@/components/LegacyBlockRenderer'
251
-
252
- export default async function Page({ params }) {
253
- const page = await getPage(params.slug)
254
-
255
- return (
256
- <HybridPageRenderer
257
- page={page}
258
- config={baseConfig}
259
- legacyRenderer={(blocks) => <LegacyBlockRenderer blocks={blocks} />}
260
- />
261
- )
262
- }
263
- ```
264
-
265
- **Option B: Manual Detection**
266
-
267
- Add conditional logic to check `editorVersion`:
268
-
269
- ```typescript
270
- // Check if page was created with Puck
271
- const isPuckPage = page.editorVersion === 'puck' && page.puckData?.content?.length > 0
272
-
273
- if (isPuckPage) {
274
117
  return <PageRenderer config={baseConfig} data={page.puckData} />
275
118
  }
276
-
277
- // Fall back to legacy rendering
278
- return <LegacyBlockRenderer blocks={page.layout} />
279
- ```
280
-
281
- **Option C: Custom Components**
282
-
283
- If you have custom Puck components (not just the built-in ones), create a client wrapper:
284
-
285
- ```typescript
286
- // components/PuckPageRenderer.tsx
287
- 'use client'
288
-
289
- import { Render } from '@puckeditor/core'
290
- import { myCustomConfig } from '@/puck/config'
291
-
292
- export function PuckPageRenderer({ data }) {
293
- return <Render config={myCustomConfig} data={data} />
294
- }
295
- ```
296
-
297
- Then use this wrapper in your page template instead of `PageRenderer`.
298
-
299
- ---
300
-
301
- ## Styling Setup
302
-
303
- ### Tailwind Typography (Required)
304
-
305
- > Required only if using the RichText component.
306
-
307
- The RichText component uses `@tailwindcss/typography`:
308
-
309
- ```bash
310
- pnpm add @tailwindcss/typography
311
- ```
312
-
313
- **Tailwind v4:**
314
- ```css
315
- @import "tailwindcss";
316
- @plugin "@tailwindcss/typography";
317
- ```
318
-
319
- **Tailwind v3:**
320
- ```javascript
321
- // tailwind.config.js
322
- module.exports = {
323
- plugins: [require('@tailwindcss/typography')],
324
- }
325
- ```
326
-
327
- ### Package Scanning (Required)
328
-
329
- > Required if your project uses Tailwind CSS. Ensures component classes are included in your build.
330
-
331
- Tell Tailwind to scan the plugin's components:
332
-
333
- **Tailwind v4:**
334
- ```css
335
- /* Adjust path relative to your CSS file */
336
- @source "../node_modules/@delmaredigital/payload-puck";
337
- ```
338
-
339
- **Tailwind v3:**
340
- ```javascript
341
- // tailwind.config.js
342
- module.exports = {
343
- content: [
344
- './src/**/*.{js,ts,jsx,tsx}',
345
- './node_modules/@delmaredigital/payload-puck/**/*.{js,mjs,jsx,tsx}',
346
- ],
347
- }
348
- ```
349
-
350
- ### Theme CSS Variables (Optional)
351
-
352
- > Optional - the plugin includes sensible defaults. Define these only to customize colors in rendered content (links, borders, etc).
353
-
354
- The 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:
355
-
356
- ```css
357
- :root {
358
- --background: 0 0% 100%;
359
- --foreground: 222.2 84% 4.9%;
360
- --primary: 222.2 47.4% 11.2%;
361
- --primary-foreground: 210 40% 98%;
362
- --secondary: 210 40% 96%;
363
- --secondary-foreground: 222.2 47.4% 11.2%;
364
- --muted: 210 40% 96%;
365
- --muted-foreground: 215.4 16.3% 46.9%;
366
- --accent: 210 40% 96%;
367
- --accent-foreground: 222.2 47.4% 11.2%;
368
- --destructive: 0 84.2% 60.2%;
369
- --destructive-foreground: 210 40% 98%;
370
- --border: 214.3 31.8% 91.4%;
371
- --input: 214.3 31.8% 91.4%;
372
- --ring: 222.2 84% 4.9%;
373
- --radius: 0.5rem;
374
- }
375
- ```
376
-
377
- ---
378
-
379
- ## Core Concepts
380
-
381
- ### Server vs Client Configuration
382
-
383
- The plugin provides two configurations for React Server Components:
384
-
385
- | Config | Import | Use Case |
386
- |--------|--------|----------|
387
- | `baseConfig` | `@delmaredigital/payload-puck/config` | Server-safe rendering with `PageRenderer` |
388
- | `editorConfig` | `@delmaredigital/payload-puck/config/editor` | Client-side editing with full interactivity |
389
-
390
- ```typescript
391
- // Server component - use baseConfig
392
- import { baseConfig } from '@delmaredigital/payload-puck/config'
393
- <PageRenderer config={baseConfig} data={page.puckData} />
394
-
395
- // PuckConfigProvider - use editorConfig
396
- import { editorConfig } from '@delmaredigital/payload-puck/config/editor'
397
- <PuckConfigProvider config={editorConfig}>
398
- ```
399
-
400
- ### Draft System
401
-
402
- The editor uses Payload's native draft system. The plugin automatically enables drafts on the pages collection. You can also enable it manually:
403
-
404
- ```typescript
405
- {
406
- slug: 'pages',
407
- versions: {
408
- drafts: true,
409
- },
410
- }
411
- ```
412
-
413
- The editor header provides:
414
- - **Save** - Saves as draft without publishing
415
- - **Publish** - Publishes the page (sets `_status: 'published'`)
416
- - **Unpublish** - Reverts a published page to draft status (appears only when published)
417
-
418
- ---
419
-
420
- ## Components
421
-
422
- ### Layout
423
-
424
- | Component | Description |
425
- |-----------|-------------|
426
- | **Container** | Content wrapper with max-width and background |
427
- | **Flex** | Flexible box layout with direction and alignment |
428
- | **Grid** | CSS Grid layout with responsive columns |
429
- | **Section** | Two-layer: full-bleed section + constrained content area |
430
- | **Spacer** | Vertical/horizontal spacing element |
431
- | **Template** | Save/load reusable component arrangements |
432
-
433
- ### Typography
434
-
435
- | Component | Description |
436
- |-----------|-------------|
437
- | **Heading** | H1-H6 headings with size and alignment |
438
- | **Text** | Paragraph text with styling options |
439
- | **RichText** | Puck's native richtext editor with enhancements: font sizes, text colors with opacity, highlights, superscript/subscript, and inline editing on canvas |
440
-
441
- ### Media & Interactive
442
-
443
- | Component | Description |
444
- |-----------|-------------|
445
- | **Image** | Responsive image with alt text |
446
- | **Button** | Styled button/link with variants |
447
- | **Card** | Content card with optional image |
448
- | **Divider** | Horizontal rule with styles |
449
- | **Accordion** | Expandable content sections (first item opens by default) |
450
-
451
- ### Semantic HTML Elements
452
-
453
- Layout components (Section, Flex, Container, Grid) support semantic HTML output for better SEO and accessibility:
454
-
455
- | Component | Available Elements |
456
- |-----------|-------------------|
457
- | **Section** | `section`, `article`, `aside`, `nav`, `header`, `footer`, `main`, `div` |
458
- | **Flex** | `div`, `nav`, `ul`, `ol`, `aside`, `section` |
459
- | **Container** | `div`, `article`, `aside`, `section` |
460
- | **Grid** | `div`, `ul`, `ol` |
461
-
462
- Select the appropriate HTML element in the component's sidebar to output semantic markup.
463
-
464
- ### Responsive Controls
465
-
466
- Layout components support per-breakpoint customization:
467
- - **Dimensions** - Width, max-width, height constraints
468
- - **Padding/Margin** - Spacing per breakpoint
469
- - **Visibility** - Show/hide at specific breakpoints
470
- - **Viewport Preview** - Mobile, Tablet, Desktop, and Full Width options
471
-
472
- ---
473
-
474
- ## Custom Fields
475
-
476
- All fields are imported from `@delmaredigital/payload-puck/fields`.
477
-
478
- ### Field Reference
479
-
480
- | Field | Description |
481
- |-------|-------------|
482
- | **MediaField** | Payload media library integration |
483
- | **RichTextField** | Puck's native richtext with enhancements (colors, font sizes, highlights) |
484
- | **ColorPickerField** | Color picker with opacity and presets |
485
- | **BackgroundField** | Solid colors, gradients, images |
486
- | **PaddingField / MarginField** | Visual spacing editors |
487
- | **BorderField** | Border width, style, color, radius |
488
- | **DimensionsField** | Width/height with constraints |
489
- | **AlignmentField** | Text alignment (left, center, right) |
490
- | **ContentAlignmentField** | Visual 3x3 grid selector for positioning (d-pad style) |
491
- | **SizeField** | Preset sizes (sm, default, lg) with custom mode |
492
- | **AnimationField** | Entrance animations |
493
- | **ResponsiveVisibilityField** | Show/hide per breakpoint |
494
- | **FolderPickerField** | Hierarchical folder selection (page-tree) |
495
- | **PageSegmentField** | URL segment with slugification (page-tree) |
496
- | **SlugPreviewField** | Read-only computed slug (page-tree) |
497
-
498
- ### Usage Example
499
-
500
- ```typescript
501
- import { createMediaField, createBackgroundField, backgroundValueToCSS } from '@delmaredigital/payload-puck/fields'
502
-
503
- const HeroConfig = {
504
- fields: {
505
- image: createMediaField({ label: 'Background Image' }),
506
- background: createBackgroundField({ label: 'Overlay' }),
507
- },
508
- render: ({ image, background }) => (
509
- <section style={{ background: backgroundValueToCSS(background) }}>
510
- {/* content */}
511
- </section>
512
- ),
513
- }
514
- ```
515
-
516
- ### CSS Helper Functions
517
-
518
- ```typescript
519
- import {
520
- backgroundValueToCSS,
521
- dimensionsValueToCSS,
522
- animationValueToCSS,
523
- visibilityValueToCSS,
524
- alignmentToFlexCSS,
525
- alignmentToGridCSS,
526
- sizeValueToCSS,
527
- getSizeClasses,
528
- } from '@delmaredigital/payload-puck/fields'
529
- ```
530
-
531
- ### ContentAlignmentField Example
532
-
533
- The ContentAlignmentField provides a visual 3x3 grid selector for content positioning:
534
-
535
- ```typescript
536
- import {
537
- createContentAlignmentField,
538
- alignmentToFlexCSS,
539
- alignmentToGridCSS,
540
- } from '@delmaredigital/payload-puck/fields'
541
-
542
- const BannerConfig = {
543
- fields: {
544
- contentPosition: createContentAlignmentField({ label: 'Content Position' }),
545
- },
546
- render: ({ contentPosition }) => (
547
- <div style={{
548
- display: 'flex',
549
- minHeight: '400px',
550
- ...alignmentToFlexCSS(contentPosition), // Converts to justify-content + align-items
551
- }}>
552
- <div>Positioned content</div>
553
- </div>
554
- ),
555
- }
556
- ```
557
-
558
- Helper functions:
559
- - `alignmentToFlexCSS()` - For Flexbox containers (`justify-content` + `align-items`)
560
- - `alignmentToGridCSS()` - For Grid containers (`justify-content` + `align-content`)
561
- - `alignmentToPlaceSelfCSS()` - For individual grid items (`place-self`)
562
- - `alignmentToTailwind()` - Returns Tailwind classes (`justify-* items-*`)
563
-
564
- ---
565
-
566
- ## Building Custom Components
567
-
568
- The plugin exports individual component configs and field factories for building custom Puck configurations.
569
-
570
- ### Cherry-Picking Components
571
-
572
- Import only the components you need:
573
-
574
- ```typescript
575
- import {
576
- SectionConfig,
577
- HeadingConfig,
578
- TextConfig,
579
- ImageConfig,
580
- ButtonConfig,
581
- } from '@delmaredigital/payload-puck/components'
582
-
583
- export const puckConfig: Config = {
584
- components: {
585
- Section: SectionConfig,
586
- Heading: HeadingConfig,
587
- Text: TextConfig,
588
- Image: ImageConfig,
589
- Button: ButtonConfig,
590
- },
591
- categories: {
592
- layout: { components: ['Section'] },
593
- content: { components: ['Heading', 'Text', 'Image', 'Button'] },
594
- },
595
- }
596
- ```
597
-
598
- ### Using Field Factories
599
-
600
- Build custom components with pre-built fields:
601
-
602
- ```typescript
603
- import type { ComponentConfig } from '@puckeditor/core'
604
- import {
605
- createMediaField,
606
- createBackgroundField,
607
- createPaddingField,
608
- backgroundValueToCSS,
609
- paddingValueToCSS,
610
- } from '@delmaredigital/payload-puck/fields'
611
-
612
- export const HeroConfig: ComponentConfig = {
613
- label: 'Hero',
614
- fields: {
615
- image: createMediaField({ label: 'Background Image' }),
616
- overlay: createBackgroundField({ label: 'Overlay' }),
617
- padding: createPaddingField({ label: 'Padding' }),
618
- },
619
- defaultProps: {
620
- image: null,
621
- overlay: null,
622
- padding: { top: 80, bottom: 80, left: 24, right: 24, unit: 'px', linked: false },
623
- },
624
- render: ({ image, overlay, padding }) => (
625
- <section
626
- style={{
627
- background: backgroundValueToCSS(overlay),
628
- padding: paddingValueToCSS(padding),
629
- }}
630
- >
631
- {/* Hero content */}
632
- </section>
633
- ),
634
- }
635
- ```
636
-
637
- ### Server vs Editor Variants
638
-
639
- For `PageRenderer` (frontend), components need server-safe configs without React hooks:
640
-
641
- ```typescript
642
- // Import server variants for PageRenderer
643
- import {
644
- SectionServerConfig,
645
- HeadingServerConfig,
646
- TextServerConfig,
647
- } from '@delmaredigital/payload-puck/components'
648
-
649
- <PageRenderer config={{ components: { Section: SectionServerConfig, ... } }} data={page.puckData} />
650
- ```
651
-
652
- For custom components, create two files:
653
- - `MyComponent.tsx` - Full editor version with fields and interactivity
654
- - `MyComponent.server.tsx` - Server-safe version (no hooks, no 'use client')
655
-
656
- ### Extending Built-in Configs
657
-
658
- Use `extendConfig()` to add custom components:
659
-
660
- ```typescript
661
- import { extendConfig, fullConfig } from '@delmaredigital/payload-puck/config/editor'
662
- import { HeroConfig } from './components/Hero'
663
-
664
- export const puckConfig = extendConfig({
665
- base: fullConfig,
666
- components: {
667
- Hero: HeroConfig,
668
- },
669
- categories: {
670
- custom: { title: 'Custom', components: ['Hero'] },
671
- },
672
- })
673
- ```
674
-
675
- > **Note:** Use `fullConfig` from `/config/editor` for extending the editor. For server-side rendering, use `baseConfig` from `/config`.
676
-
677
- ### Using Custom Config with Provider
678
-
679
- After creating your custom config, pass it to `PuckConfigProvider`:
680
-
681
- ```typescript
682
- // components/admin/PuckProvider.tsx
683
- 'use client'
684
- import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
685
- import { puckConfig } from '@/puck/config.editor'
686
- import { siteLayouts } from '@/lib/puck-layouts'
687
-
688
- export default function PuckProvider({ children }: { children: React.ReactNode }) {
689
- return (
690
- <PuckConfigProvider config={puckConfig} layouts={siteLayouts}>
691
- {children}
692
- </PuckConfigProvider>
693
- )
694
- }
695
- ```
696
-
697
- **For Payload admin**, register the provider in your Payload config:
698
-
699
- ```typescript
700
- // payload.config.ts
701
- export default buildConfig({
702
- admin: {
703
- components: {
704
- providers: ['@/components/admin/PuckProvider'],
705
- },
706
- },
707
- // ...
708
- })
709
- ```
710
-
711
- This is the recommended pattern for Payload apps. The provider wraps only the admin UI, keeping your frontend layout separate.
712
-
713
- ### Available Field Factories
714
-
715
- | Factory | Description |
716
- |---------|-------------|
717
- | `createMediaField()` | Payload media library picker |
718
- | `createBackgroundField()` | Solid, gradient, or image backgrounds |
719
- | `createColorPickerField()` | Color picker with opacity |
720
- | `createPaddingField()` | Visual padding editor |
721
- | `createMarginField()` | Visual margin editor |
722
- | `createBorderField()` | Border styling |
723
- | `createDimensionsField()` | Width/height constraints |
724
- | `createAnimationField()` | Entrance animations |
725
- | `createAlignmentField()` | Text alignment (left, center, right) |
726
- | `createContentAlignmentField()` | Visual 3x3 grid positioning selector |
727
- | `createSizeField()` | Size presets with custom mode |
728
- | `createRichTextField()` | Puck's native richtext with colors, font sizes, highlights |
729
- | `createResponsiveVisibilityField()` | Show/hide per breakpoint |
730
-
731
- ### CSS Helper Functions
732
-
733
- Convert field values to CSS:
734
-
735
- ```typescript
736
- import {
737
- backgroundValueToCSS,
738
- paddingValueToCSS,
739
- marginValueToCSS,
740
- borderValueToCSS,
741
- dimensionsValueToCSS,
742
- colorValueToCSS,
743
- alignmentToFlexCSS,
744
- alignmentToGridCSS,
745
- sizeValueToCSS,
746
- } from '@delmaredigital/payload-puck/fields'
747
-
748
- const style = {
749
- background: backgroundValueToCSS(props.background),
750
- padding: paddingValueToCSS(props.padding),
751
- ...dimensionsValueToCSS(props.dimensions),
752
- ...alignmentToFlexCSS(props.contentAlignment),
753
- ...sizeValueToCSS(props.size),
754
- }
755
- ```
756
-
757
- ---
758
-
759
- ## Theming
760
-
761
- Customize button styles, color presets, and focus rings:
762
-
763
- ```typescript
764
- import { PageRenderer } from '@delmaredigital/payload-puck/render'
765
- import { ThemeProvider } from '@delmaredigital/payload-puck/theme'
766
-
767
- <ThemeProvider theme={{
768
- buttonVariants: {
769
- default: { classes: 'bg-primary text-white hover:bg-primary/90' },
770
- secondary: { classes: 'bg-secondary text-foreground hover:bg-secondary/90' },
771
- },
772
- focusRingColor: 'focus:ring-primary',
773
- colorPresets: [
774
- { hex: '#3b82f6', label: 'Brand Blue' },
775
- { hex: '#10b981', label: 'Success' },
776
- ],
777
- }}>
778
- <PageRenderer config={baseConfig} data={page.puckData} />
779
- </ThemeProvider>
780
- ```
781
-
782
- Access theme values in custom components with `useTheme()`:
783
-
784
- ```typescript
785
- import { useTheme } from '@delmaredigital/payload-puck/theme'
786
-
787
- function CustomButton({ variant }) {
788
- const theme = useTheme()
789
- const classes = theme.buttonVariants[variant]?.classes
790
- return <button className={classes}>...</button>
791
- }
792
119
  ```
793
120
 
794
- ---
795
-
796
- ## Layouts
797
-
798
- Define page layouts with headers, footers, and styling:
799
-
800
- ```typescript
801
- // lib/puck-layouts.ts
802
- import type { LayoutDefinition } from '@delmaredigital/payload-puck/layouts'
803
- import { SiteHeader } from '@/components/header'
804
- import { SiteFooter } from '@/components/footer'
805
-
806
- export const siteLayouts: LayoutDefinition[] = [
807
- {
808
- value: 'default',
809
- label: 'Default',
810
- description: 'Standard page with header and footer',
811
- maxWidth: '1200px',
812
- header: SiteHeader,
813
- footer: SiteFooter,
814
- stickyHeaderHeight: 80,
815
- },
816
- {
817
- value: 'landing',
818
- label: 'Landing',
819
- description: 'Full-width landing page',
820
- fullWidth: true,
821
- },
822
- ]
823
- ```
824
-
825
- Pass layouts to the `PuckConfigProvider`:
826
-
827
- ```typescript
828
- <PuckConfigProvider config={editorConfig} layouts={siteLayouts}>
829
- {children}
830
- </PuckConfigProvider>
831
- ```
832
-
833
- And use them with `PageRenderer`:
834
-
835
- ```typescript
836
- import { LayoutWrapper } from '@delmaredigital/payload-puck/layouts'
837
-
838
- const layout = siteLayouts.find(l => l.value === page.puckData?.root?.props?.pageLayout)
839
-
840
- <LayoutWrapper layout={layout}>
841
- <PageRenderer config={baseConfig} data={page.puckData} />
842
- </LayoutWrapper>
843
- ```
844
-
845
- ### Avoiding Double Headers/Footers
846
-
847
- When 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:
848
-
849
- ```typescript
850
- import { HybridPageRenderer, createRenderLayouts } from '@delmaredigital/payload-puck/render'
851
- import { siteLayouts } from '@/lib/puck-layouts' // layouts with header/footer for editor
852
-
853
- // Strip header/footer for rendering (host app layout provides them)
854
- const renderLayouts = createRenderLayouts(siteLayouts)
855
-
856
- export function PageRenderer({ page }) {
857
- const layout = renderLayouts.find(l => l.value === page.puckData?.root?.props?.pageLayout)
858
-
859
- return (
860
- <LayoutWrapper layout={layout}>
861
- <HybridPageRenderer page={page} config={baseConfig} />
862
- </LayoutWrapper>
863
- )
864
- }
865
- ```
866
-
867
- This pattern keeps header/footer in your editor layouts for realistic preview, but avoids double headers when rendering.
868
-
869
- ---
870
-
871
- ## Dark Mode Support
872
-
873
- The 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.
874
-
875
- ### How It Works
876
-
877
- 1. **Editor UI**: Automatically detects dark mode via `.dark` class (PayloadCMS) or `prefers-color-scheme` (OS preference), then injects Puck CSS variable overrides
878
- 2. **Preview Iframe**: A sun/moon toggle lets you switch the preview content between light and dark modes independently from the editor UI
879
-
880
- ### Configuration
881
-
882
- Dark mode is enabled by default. You can customize via props on `PuckEditor`:
883
-
884
- ```typescript
885
- <PuckEditor
886
- autoDetectDarkMode={true} // Auto-detect PayloadCMS dark mode (default: true)
887
- showPreviewDarkModeToggle={true} // Show light/dark toggle in header (default: true)
888
- initialPreviewDarkMode={false} // Start preview in light mode (default: false)
889
- />
890
- ```
891
-
892
- ### Using Components Directly
893
-
894
- For custom editor implementations:
895
-
896
- ```typescript
897
- import {
898
- DarkModeStyles,
899
- PreviewModeToggle,
900
- useDarkMode,
901
- } from '@delmaredigital/payload-puck/editor'
902
-
903
- function CustomEditor() {
904
- const { isDarkMode, source } = useDarkMode()
905
- const [previewDark, setPreviewDark] = useState(false)
906
-
907
- return (
908
- <>
909
- {/* Inject dark mode CSS overrides when detected */}
910
- <DarkModeStyles />
911
-
912
- {/* Toggle for preview iframe */}
913
- <PreviewModeToggle
914
- isDarkMode={previewDark}
915
- onToggle={setPreviewDark}
916
- />
917
-
918
- <Puck ... />
919
- </>
920
- )
921
- }
922
- ```
923
-
924
- ### Detecting Theme in Puck Components
925
-
926
- If your Puck components need to dynamically adjust JavaScript-controlled styles based on the preview theme (not just CSS), use the `usePuckPreviewTheme()` hook:
927
-
928
- ```typescript
929
- import { usePuckPreviewTheme } from '@delmaredigital/payload-puck/editor'
930
- import { useEffect, useState } from 'react'
931
-
932
- function useDetectTheme() {
933
- const puckTheme = usePuckPreviewTheme()
934
-
935
- // For frontend (non-editor), read from DOM
936
- const [domTheme, setDomTheme] = useState(() =>
937
- typeof document !== 'undefined'
938
- ? document.documentElement.getAttribute('data-theme') === 'dark'
939
- : false
940
- )
941
-
942
- useEffect(() => {
943
- const observer = new MutationObserver((mutations) => {
944
- for (const mutation of mutations) {
945
- if (mutation.attributeName === 'data-theme') {
946
- setDomTheme(document.documentElement.getAttribute('data-theme') === 'dark')
947
- }
948
- }
949
- })
950
- observer.observe(document.documentElement, { attributes: true })
951
- return () => observer.disconnect()
952
- }, [])
953
-
954
- // In editor: use context. On frontend: use DOM.
955
- return puckTheme !== null ? puckTheme : domTheme
956
- }
957
- ```
958
-
959
- **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.
960
-
961
- ---
962
-
963
- ## Page-Tree Integration
964
-
965
- When `@delmaredigital/payload-page-tree` is detected, the plugin automatically adds folder management to the Puck sidebar.
966
-
967
- > **⚠️ Plugin Order:** When using both plugins with `autoGenerateCollection: true`, Puck must run BEFORE page-tree. See [Plugin Order](#plugin-order).
968
-
969
- ### How It Works
970
-
971
- The plugin checks if your collection has a `pageSegment` field (page-tree's signature). When detected:
972
-
973
- 1. **Folder Picker** - Select a folder from the hierarchy
974
- 2. **Page Segment** - Edit the page's URL segment
975
- 3. **Slug Preview** - See the computed slug (folder path + segment)
976
-
977
- ### Plugin Configuration
978
-
979
- ```typescript
980
- createPuckPlugin({
981
- // Auto-detect (default)
982
- pageTreeIntegration: undefined,
983
-
984
- // Explicitly enable with custom config
985
- pageTreeIntegration: {
986
- folderSlug: 'payload-folders',
987
- pageSegmentFieldName: 'pageSegment',
988
- },
989
-
990
- // Explicitly disable
991
- pageTreeIntegration: false,
992
- })
993
- ```
994
-
995
- ### Custom Editor UI
996
-
997
- For custom editor implementations outside Payload admin, use the `hasPageTree` prop:
998
-
999
- ```typescript
1000
- import { PuckEditor } from '@delmaredigital/payload-puck/client'
1001
- import { editorConfig } from '@delmaredigital/payload-puck/config/editor'
1002
-
1003
- <PuckEditor
1004
- config={editorConfig}
1005
- pageId={page.id}
1006
- initialData={page.puckData}
1007
- pageTitle={page.title}
1008
- pageSlug={page.slug}
1009
- apiEndpoint="/api/puck/pages"
1010
- hasPageTree={true}
1011
- folder={page.folder}
1012
- pageSegment={page.pageSegment}
1013
- />
1014
- ```
1015
-
1016
- ### Performance
1017
-
1018
- Detection is instant - it reads the in-memory collection config, no database queries.
1019
-
1020
- ---
1021
-
1022
- ## Hybrid Integration
1023
-
1024
- Add Puck to existing collections with legacy blocks.
1025
-
1026
- ### Automatic (Recommended)
1027
-
1028
- If you already have a `pages` collection, the plugin adds only the Puck-specific fields:
1029
-
1030
- ```typescript
1031
- // payload.config.ts
1032
- export default buildConfig({
1033
- collections: [
1034
- {
1035
- slug: 'pages',
1036
- fields: [
1037
- { name: 'title', type: 'text', required: true },
1038
- { name: 'layout', type: 'blocks', blocks: [HeroBlock, CTABlock] },
1039
- ],
1040
- },
1041
- ],
1042
- plugins: [
1043
- createPuckPlugin({ pagesCollection: 'pages' }),
1044
- ],
1045
- })
1046
- ```
1047
-
1048
- The `editorVersion` field auto-detects whether pages use legacy blocks or Puck.
1049
-
1050
- ### Manual with `getPuckCollectionConfig()` (Recommended)
1051
-
1052
- When you need the `isHomepage` field, use `getPuckCollectionConfig()` which returns both fields AND hooks. This ensures the homepage uniqueness validation is included:
1053
-
1054
- ```typescript
1055
- import { getPuckCollectionConfig } from '@delmaredigital/payload-puck'
1056
-
1057
- const { fields: puckFields, hooks: puckHooks } = getPuckCollectionConfig({
1058
- includeSEO: true,
1059
- includeEditorVersion: true,
1060
- includePageLayout: true,
1061
- includeIsHomepage: true, // Includes uniqueness hook automatically
1062
- })
1063
-
1064
- export const Pages: CollectionConfig = {
1065
- slug: 'pages',
1066
- hooks: {
1067
- beforeChange: [
1068
- ...(puckHooks.beforeChange ?? []),
1069
- // Your other beforeChange hooks...
1070
- ],
1071
- afterChange: [
1072
- // Your afterChange hooks...
1073
- ],
1074
- },
1075
- fields: [
1076
- { name: 'title', type: 'text' },
1077
- { name: 'layout', type: 'blocks', blocks: [...] },
1078
- ...puckFields,
1079
- ],
1080
- }
1081
- ```
1082
-
1083
- ### Manual with `getPuckFields()` (Fields Only)
1084
-
1085
- If you don't need `isHomepage` or want to configure hooks manually:
1086
-
1087
- ```typescript
1088
- import { getPuckFields, createIsHomepageUniqueHook } from '@delmaredigital/payload-puck'
1089
-
1090
- export const Pages: CollectionConfig = {
1091
- slug: 'pages',
1092
- hooks: {
1093
- // Required if using includeIsHomepage: true
1094
- beforeChange: [createIsHomepageUniqueHook()],
1095
- },
1096
- fields: [
1097
- { name: 'title', type: 'text' },
1098
- { name: 'layout', type: 'blocks', blocks: [...] },
1099
- ...getPuckFields({
1100
- includeSEO: true,
1101
- includeEditorVersion: true,
1102
- includePageLayout: true,
1103
- includeIsHomepage: true, // Note: requires hook above for uniqueness
1104
- }),
1105
- ],
1106
- }
1107
- ```
1108
-
1109
- > **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.
1110
-
1111
- ### Rendering Hybrid Pages
1112
-
1113
- ```typescript
1114
- import { HybridPageRenderer } from '@delmaredigital/payload-puck/render'
1115
- import { LegacyBlockRenderer } from '@/components/LegacyBlockRenderer'
1116
-
1117
- <HybridPageRenderer
1118
- page={page}
1119
- config={puckConfig}
1120
- legacyRenderer={(blocks) => <LegacyBlockRenderer blocks={blocks} />}
1121
- />
1122
- ```
1123
-
1124
- ---
1125
-
1126
- ## AI Integration
1127
-
1128
- > **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.
1129
-
1130
- The 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.
1131
-
1132
- ### Requirements
1133
-
1134
- - `PUCK_API_KEY` environment variable (from [Puck Cloud](https://puckeditor.com))
1135
- - AI features require `@puckeditor/plugin-ai` and `@puckeditor/cloud-client` (bundled with the plugin)
1136
-
1137
- ### Quick Start
1138
-
1139
- Enable AI in your plugin configuration:
1140
-
1141
- ```typescript
1142
- createPuckPlugin({
1143
- pagesCollection: 'pages',
1144
- ai: {
1145
- enabled: true,
1146
- context: 'We are Acme Corp, a B2B SaaS company. Use professional language.',
1147
- },
1148
- })
1149
- ```
1150
-
1151
- This automatically:
1152
- - Registers the AI chat endpoint at `/api/puck/ai`
1153
- - Adds the AI chat plugin to the editor
1154
- - Applies comprehensive component instructions for better generation quality
1155
-
1156
- ### Dynamic Business Context
1157
-
1158
- Instead of hardcoding context in your config, you can manage it through Payload admin:
1159
-
1160
- ```typescript
1161
- createPuckPlugin({
1162
- ai: {
1163
- enabled: true,
1164
- contextCollection: true, // Creates puck-ai-context collection
1165
- },
1166
- })
1167
- ```
1168
-
1169
- This creates a `puck-ai-context` collection where you can add entries for:
1170
- - **Brand Guidelines** - Colors, fonts, brand voice
1171
- - **Tone of Voice** - How to communicate
1172
- - **Product Information** - What you sell/offer
1173
- - **Industry Context** - Your market and audience
1174
- - **Technical Requirements** - Specific constraints
1175
- - **Page Patterns** - Common layout structures
1176
-
1177
- Context entries can be enabled/disabled and ordered. The AI receives all enabled entries sorted by order.
1178
-
1179
- ### Context Editor Plugin
1180
-
1181
- When `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.
1182
-
1183
- ### Prompt Management
1184
-
1185
- Store reusable prompts in Payload:
1186
-
1187
- ```typescript
1188
- createPuckPlugin({
1189
- ai: {
1190
- enabled: true,
1191
- promptsCollection: true, // Creates puck-ai-prompts collection
1192
- examplePrompts: [
1193
- { label: 'Landing page', prompt: 'Create a landing page for...' },
1194
- ],
1195
- },
1196
- })
1197
- ```
1198
-
1199
- Prompts from the collection appear in the AI chat interface. A "Prompts" panel in the plugin rail allows in-editor prompt management.
1200
-
1201
- ### Custom Tools
1202
-
1203
- Enable the AI to query your data:
1204
-
1205
- ```typescript
1206
- import { z } from 'zod'
1207
-
1208
- createPuckPlugin({
1209
- ai: {
1210
- enabled: true,
1211
- tools: {
1212
- getProducts: {
1213
- description: 'Get products from the database',
1214
- inputSchema: z.object({ category: z.string() }),
1215
- execute: async ({ category }, { payload }) => {
1216
- return await payload.find({
1217
- collection: 'products',
1218
- where: { category: { equals: category } },
1219
- })
1220
- },
1221
- },
1222
- },
1223
- },
1224
- })
1225
- ```
1226
-
1227
- Tools receive a context object with the Payload instance and authenticated user.
1228
-
1229
- ### AI Configuration Options
1230
-
1231
- | Option | Default | Description |
1232
- |--------|---------|-------------|
1233
- | `enabled` | `false` | Enable AI features |
1234
- | `context` | `undefined` | Static system context for the AI |
1235
- | `contextCollection` | `false` | Create `puck-ai-context` collection for dynamic context |
1236
- | `promptsCollection` | `false` | Create `puck-ai-prompts` collection for reusable prompts |
1237
- | `examplePrompts` | `[]` | Static example prompts for the chat interface |
1238
- | `tools` | `undefined` | Custom tools for AI to query your system |
1239
- | `componentInstructions` | `undefined` | Override default component AI instructions |
1240
-
1241
- ### Component Instructions
1242
-
1243
- The plugin includes comprehensive instructions for all built-in components, teaching the AI:
1244
- - Correct field names and values
1245
- - Component composition patterns
1246
- - Page structure best practices (Hero → Features → CTA flow)
1247
- - Semantic HTML usage
1248
-
1249
- To customize or extend:
1250
-
1251
- ```typescript
1252
- createPuckPlugin({
1253
- ai: {
1254
- enabled: true,
1255
- componentInstructions: {
1256
- Heading: {
1257
- ai: { instructions: 'Use our brand voice: professional but approachable' },
1258
- fields: {
1259
- text: { ai: { instructions: 'Keep under 8 words' } },
1260
- },
1261
- },
1262
- },
1263
- },
1264
- })
1265
- ```
1266
-
1267
- ### Standalone API Routes
1268
-
1269
- For custom implementations outside the plugin:
1270
-
1271
- ```typescript
1272
- // app/api/puck/[...all]/route.ts
1273
- import { createPuckAiApiRoutes } from '@delmaredigital/payload-puck/ai'
1274
- import config from '@payload-config'
1275
-
1276
- export const POST = createPuckAiApiRoutes({
1277
- payloadConfig: config,
1278
- auth: {
1279
- authenticate: async (request) => {
1280
- // Your auth implementation
1281
- return { user: { id: '...' } }
1282
- },
1283
- },
1284
- ai: {
1285
- context: 'Your business context...',
1286
- },
1287
- })
1288
- ```
1289
-
1290
- ### AI Exports
1291
-
1292
- ```typescript
1293
- import {
1294
- // Plugins
1295
- createAiPlugin,
1296
- createPromptEditorPlugin,
1297
- createContextEditorPlugin,
1298
-
1299
- // Hooks
1300
- useAiPrompts,
1301
- useAiContext,
1302
-
1303
- // Config utilities
1304
- injectAiConfig,
1305
- comprehensiveComponentAiConfig,
1306
- pagePatternSystemContext,
1307
-
1308
- // API routes
1309
- createPuckAiApiRoutes,
1310
- createAiGenerate,
1311
- } from '@delmaredigital/payload-puck/ai'
1312
- ```
1313
-
1314
- ---
1315
-
1316
- ## Plugin Order
1317
-
1318
- When using `autoGenerateCollection: true` (the default) with `@delmaredigital/payload-page-tree`, **plugin order matters**.
1319
-
1320
- ### The Issue
1321
-
1322
- The 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.).
1323
-
1324
- ### Correct Order
1325
-
1326
- ```typescript
1327
- // ✅ CORRECT: Puck creates the collection before page-tree runs
1328
- export const plugins = [
1329
- createPuckPlugin({ pagesCollection: 'pages' }), // Creates Pages first
1330
- pageTreePlugin({ collections: ['pages'] }), // Now sees Pages
1331
- ]
1332
-
1333
- // ❌ WRONG: page-tree runs before Pages exists
1334
- export const plugins = [
1335
- pageTreePlugin({ collections: ['pages'] }), // Pages doesn't exist!
1336
- createPuckPlugin({ pagesCollection: 'pages' }), // Creates Pages too late
1337
- ]
1338
- ```
1339
-
1340
- ### When Order Doesn't Matter
1341
-
1342
- If you define your collection manually (with `autoGenerateCollection: false`), order doesn't matter because the collection already exists in your config:
1343
-
1344
- ```typescript
1345
- export default buildConfig({
1346
- collections: [Pages], // Collection exists before plugins run
1347
- plugins: [
1348
- pageTreePlugin({ collections: ['pages'] }),
1349
- createPuckPlugin({ pagesCollection: 'pages', autoGenerateCollection: false }),
1350
- ],
1351
- })
1352
- ```
1353
-
1354
- See also: [payload-page-tree Plugin Order documentation](https://github.com/delmaredigital/payload-page-tree#plugin-order-critical)
1355
-
1356
- ---
1357
-
1358
- ## Advanced Configuration
1359
-
1360
- ### Plugin Options
1361
-
1362
- | Option | Default | Description |
1363
- |--------|---------|-------------|
1364
- | `pagesCollection` | `'pages'` | Collection slug to use for pages |
1365
- | `autoGenerateCollection` | `true` | Create the collection if it doesn't exist, or add Puck fields to existing (see [Plugin Order](#plugin-order)) |
1366
- | `enableEndpoints` | `true` | Register API endpoints at `/api/puck/:collection` for the editor |
1367
- | `enableAdminView` | `true` | Register the Puck editor view in Payload admin |
1368
- | `adminViewPath` | `'/puck-editor'` | Path for the editor (full path: `/admin/puck-editor/:collection/:id`) |
1369
- | `pageTreeIntegration` | auto-detect | Integration with `@delmaredigital/payload-page-tree` |
1370
- | `layouts` | `undefined` | Layout definitions for page templates |
1371
- | `editorStylesheet` | `undefined` | Path to CSS file for editor iframe styling (e.g., `'src/app/globals.css'`) |
1372
- | `editorStylesheetCompiled` | `undefined` | Path to pre-compiled CSS for production (e.g., `'/puck-editor-styles.css'`) |
1373
- | `editorStylesheetUrls` | `[]` | Additional stylesheet URLs for the editor (e.g., Google Fonts) |
1374
- | `previewUrl` | `undefined` | URL for "View" button - string or function receiving page data |
1375
-
1376
- ```typescript
1377
- createPuckPlugin({
1378
- pagesCollection: 'pages',
1379
- autoGenerateCollection: true,
1380
- enableEndpoints: true,
1381
- enableAdminView: true,
1382
- adminViewPath: '/puck-editor',
1383
- pageTreeIntegration: undefined, // auto-detects
1384
-
1385
- // Collection overrides (merged with generated collection)
1386
- collectionOverrides: {
1387
- admin: {
1388
- defaultColumns: ['title', 'slug', 'updatedAt'],
1389
- },
1390
- },
1391
-
1392
- // Access control
1393
- access: {
1394
- read: () => true,
1395
- create: ({ req }) => !!req.user,
1396
- update: ({ req }) => !!req.user,
1397
- delete: ({ req }) => !!req.user,
1398
- },
1399
- })
1400
- ```
1401
-
1402
- ### Preview URL (View Button)
1403
-
1404
- The "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.
1405
-
1406
- ```typescript
1407
- // Simple static URL pattern
1408
- createPuckPlugin({
1409
- previewUrl: '/preview',
1410
- })
1411
-
1412
- // Dynamic prefix based on page data
1413
- createPuckPlugin({
1414
- previewUrl: (page) => `/${page.slug || ''}`,
1415
- })
1416
-
1417
- // Organization-scoped pages (multi-tenant)
1418
- // The function receives the full page document with relationships populated
1419
- createPuckPlugin({
1420
- previewUrl: (page) => {
1421
- const orgSlug = page.organization?.slug || 'default'
1422
- // Return a function that handles homepage vs regular pages
1423
- return (slug) => slug ? `/${orgSlug}/${slug}` : `/${orgSlug}`
1424
- },
1425
- })
1426
- ```
1427
-
1428
- When `previewUrl` is a function, the page document is fetched with `depth: 1` so relationship fields (like `organization`) are populated with their full data.
1429
-
1430
- ### Editor Stylesheet (Iframe Styling)
1431
-
1432
- The 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.
1433
-
1434
- #### Development (Runtime Compilation)
1435
-
1436
- In development, CSS is compiled at runtime for hot reload support:
1437
-
1438
- ```typescript
1439
- createPuckPlugin({
1440
- pagesCollection: 'pages',
1441
- editorStylesheet: 'src/app/(frontend)/globals.css',
1442
- editorStylesheetUrls: [
1443
- 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'
1444
- ],
1445
- })
1446
- ```
1447
-
1448
- **How it works:**
1449
- 1. You specify your CSS file path in the plugin config
1450
- 2. The plugin creates an endpoint at `/api/puck/styles`
1451
- 3. On first request, the CSS is compiled with PostCSS/Tailwind and cached
1452
- 4. The iframe loads this compiled CSS
1453
-
1454
- #### Production (Build-Time Compilation)
1455
-
1456
- Runtime 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:
1457
-
1458
- **Step 1: Wrap your Next.js config**
1459
-
1460
- ```javascript
1461
- // next.config.js
1462
- import { withPuckCSS } from '@delmaredigital/payload-puck/next'
1463
- import { withPayload } from '@payloadcms/next/withPayload'
1464
-
1465
- const nextConfig = {
1466
- // your config...
1467
- }
1468
-
1469
- export default withPuckCSS({
1470
- cssInput: 'src/app/(frontend)/globals.css',
1471
- })(withPayload(nextConfig))
1472
- ```
1473
-
1474
- **Step 2: Add the compiled path to your plugin config**
1475
-
1476
- ```typescript
1477
- createPuckPlugin({
1478
- pagesCollection: 'pages',
1479
- editorStylesheet: 'src/app/(frontend)/globals.css', // For dev (runtime)
1480
- editorStylesheetCompiled: '/puck-editor-styles.css', // For prod (static)
1481
- editorStylesheetUrls: [
1482
- 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'
1483
- ],
1484
- })
1485
- ```
1486
-
1487
- **How it works:**
1488
- 1. During `next build`, the wrapper compiles your CSS to `public/puck-editor-styles.css`
1489
- 2. In production (`NODE_ENV=production`), the plugin serves the static file
1490
- 3. In development, runtime compilation continues working for hot reload
1491
-
1492
- **`withPuckCSS` options:**
1493
-
1494
- | Option | Default | Description |
1495
- |--------|---------|-------------|
1496
- | `cssInput` | (required) | Path to source CSS file |
1497
- | `cssOutput` | `'puck-editor-styles.css'` | Output filename in `public/` |
1498
- | `skipInDev` | `true` | Skip compilation in development |
1499
-
1500
- #### Requirements
1501
-
1502
- - `postcss` must be installed in your project
1503
- - For Tailwind v4: `@tailwindcss/postcss`
1504
- - For Tailwind v3: `tailwindcss`
1505
-
1506
- ---
1507
-
1508
- ### Custom API Routes (Advanced)
1509
-
1510
- The built-in endpoints handle most use cases. Only disable them if you need custom authentication or middleware.
1511
-
1512
- If needed, three route factories are available:
1513
-
1514
- | Factory | Route Pattern | Methods |
1515
- |---------|---------------|---------|
1516
- | `createPuckApiRoutes` | `/api/puck/[collection]` | GET (list), POST (create) |
1517
- | `createPuckApiRoutesWithId` | `/api/puck/[collection]/[id]` | GET, PATCH, DELETE |
1518
- | `createPuckApiRoutesVersions` | `/api/puck/[collection]/[id]/versions` | GET, POST (restore) |
1519
-
1520
- See the JSDoc in `@delmaredigital/payload-puck/api` for usage examples.
121
+ That's it! The plugin registers the editor view, API endpoints, and "Edit with Puck" buttons automatically.
1521
122
 
1522
123
  ---
1523
124
 
1524
- ## Export Reference
125
+ ## Documentation
1525
126
 
1526
- | Export Path | Description |
1527
- |-------------|-------------|
1528
- | `@delmaredigital/payload-puck` | Plugin creation, field utilities |
1529
- | `@delmaredigital/payload-puck/plugin` | `createPuckPlugin` |
1530
- | `@delmaredigital/payload-puck/config` | `baseConfig`, `createConfig()`, `extendConfig()` |
1531
- | `@delmaredigital/payload-puck/config/editor` | `editorConfig` for editing |
1532
- | `@delmaredigital/payload-puck/client` | `PuckEditor`, `PuckConfigProvider`, page-tree utilities |
1533
- | `@delmaredigital/payload-puck/editor` | `PuckEditor`, `HeaderActions`, editor hooks |
1534
- | `@delmaredigital/payload-puck/rsc` | `PuckEditorView` for Payload admin views |
1535
- | `@delmaredigital/payload-puck/render` | `PageRenderer`, `HybridPageRenderer` |
1536
- | `@delmaredigital/payload-puck/fields` | Custom Puck fields and CSS helpers |
1537
- | `@delmaredigital/payload-puck/components` | Component configs for custom configurations |
1538
- | `@delmaredigital/payload-puck/theme` | `ThemeProvider`, theme utilities |
1539
- | `@delmaredigital/payload-puck/layouts` | Layout definitions, `LayoutWrapper` |
1540
- | `@delmaredigital/payload-puck/api` | API route factories (for custom implementations) |
1541
- | `@delmaredigital/payload-puck/ai` | AI plugins, hooks, config utilities, API routes |
1542
- | `@delmaredigital/payload-puck/next` | `withPuckCSS` Next.js config wrapper for build-time CSS |
1543
- | `@delmaredigital/payload-puck/admin/client` | `EditWithPuckButton`, `EditWithPuckCell` |
127
+ For everything else components, custom fields, theming, layouts, dark mode, page-tree integration, hybrid integration, AI integration, advanced configuration, and the full export reference — see the [full documentation](https://delmaredigital.github.io/payload-puck/).
1544
128
 
1545
129
  ---
1546
130