Fields Reference
Every Puck component declares a fields object: one entry per prop the editor should
expose as an input. This is the direct equivalent of an ACF field group in WordPress,
or Field UI in Drupal: it’s what turns a hardcoded value into something a content editor
can change without touching code.
Source of truth: @puckeditor/core’s type definitions (Field<...> union), verified
directly against the installed package. If a future Puck version adds/changes field
types, this page can drift; check node_modules/@puckeditor/core/dist/actions-*.d.ts
in a P1 project if something here looks wrong, or see Puck’s own
Fields API reference and
AI field configuration
docs.
The basic shape
export const headingBlock = {
label: "Heading",
fields: {
title: { type: "text", label: "Text" },
level: {
type: "select",
label: "Level",
options: [
{ label: "H1", value: "h1" },
{ label: "H2", value: "h2" },
],
},
},
defaultProps: { title: "Heading", level: "h1" },
render: ({ title, level }) => { /* ... */ },
};fields controls the editor sidebar. defaultProps is what a freshly-dropped instance
starts with. The two aren’t linked automatically: a key in fields with no matching
key in defaultProps just starts undefined.
Field types
| Type | Renders as | Notes |
|---|---|---|
text | Single-line input | Supports placeholder |
textarea | Multi-line plain text | Supports placeholder |
number | Numeric input | min, max, step |
select | Dropdown | Needs options: [{ label, value }] |
radio | Button group | Same options shape as select |
richtext | Inline WYSIWYG (Tiptap-based) | Use P1’s wrapper, not the raw type; see below |
array | Repeatable sub-items | Each item shaped by its own arrayFields |
object | A nested group of fields under one prop | Rarely needed; usually array is what you want |
external | Picker backed by an async data source | Powers things like the media library picker |
custom | Fully custom field, your own React renderer | Escape hatch when nothing else fits |
slot | A nested drop zone that lets a component contain other components | For “section with children” style components |
select / radio
align: {
type: "radio",
label: "Alignment",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
],
},options values aren’t limited to strings: the type allows string | number | boolean | undefined | null | object. Booleans are common for simple toggles (WordPress
developers: this is your closest thing to a checkbox field, since there’s no dedicated
checkbox type).
number
height: { type: "number", label: "Height (px)", min: 8, max: 240, step: 4 },array: the repeater field
This is the one WordPress/Drupal developers reach for constantly and won’t find by a familiar name: it’s Puck’s equivalent of an ACF Repeater field or a Drupal multi-value field.
items: {
type: "array",
min: 1,
max: 6,
defaultItemProps: { value: "100+", label: "Label" },
getItemSummary: (item) => item.value || "Stat",
arrayFields: {
value: { type: "text", label: "Number" },
label: { type: "text", label: "Label" },
},
},arrayFieldsis a nestedfields-shaped object describing one row’s shape.getItemSummarycontrols the collapsed-row label in the editor sidebar (so an editor sees “500+” instead of “Item 1”).defaultItemPropsis what a new row starts with when someone clicks “add.” Can be a function of the index instead of a static object.min/maxbound how many rows are allowed.
richtext: use P1’s wrapper, not the bare type
Puck’s own richtext field type exists, but P1 ships a wrapper around it,
richtextField from @pantheon-systems/puck-css/fields, that wires in P1’s
sanitization and {{ }} template-token handling for datasource bindings. Use the
wrapper, not { type: "richtext" } directly, or you’ll lose that behavior:
import { richtextField } from "@pantheon-systems/puck-css/fields";
export const paragraphBlock = {
label: "Paragraph",
fields: { text: richtextField },
defaultProps: { text: "Add your copy here." },
render: ({ text, id }) => { /* ... */ },
};slot: nesting components inside components
Not commonly used in the starter kit’s default components, but this is how you’d build something like a “Columns” or “Section” component that holds other components inside it: the direct equivalent of Gutenberg’s InnerBlocks, or a Drupal Paragraphs field that references other paragraphs.
children: { type: "slot", allow: ["HeadingBlock", "ButtonBlock"] },custom: building your own field UI when nothing else fits
For anything the built-in types can’t express (a color swatch picker, a linked-page
selector, whatever), custom gives you a fully custom React renderer with direct access
to the field’s value and change handler:
title: {
type: "custom",
render: ({ name, value, onChange, field }) => (
<input
defaultValue={value}
onChange={(e) => onChange(e.currentTarget.value)}
/>
),
},onChange(value, uiState?) updates both the field’s data and, optionally, editor UI
state (e.g. sidebar visibility). Puck also exports <FieldLabel> and <AutoField> so a
custom field can render a standard label or nest ordinary Puck fields inside itself,
rather than building everything from scratch. Full writeup, with more of the API:
Extending Puck: Custom Fields .
Field name must match the render prop
There’s no separate “prop mapping” step. Whatever key you use in fields (and
defaultProps) is exactly the prop name your render function receives, case-
sensitive, with no transformation.