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