Skip to Content
Fields Reference

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

TypeRenders asNotes
textSingle-line inputSupports placeholder
textareaMulti-line plain textSupports placeholder
numberNumeric inputmin, max, step
selectDropdownNeeds options: [{ label, value }]
radioButton groupSame options shape as select
richtextInline WYSIWYG (Tiptap-based)Use P1’s wrapper, not the raw type; see below
arrayRepeatable sub-itemsEach item shaped by its own arrayFields
objectA nested group of fields under one propRarely needed; usually array is what you want
externalPicker backed by an async data sourcePowers things like the media library picker
customFully custom field, your own React rendererEscape hatch when nothing else fits
slotA nested drop zone that lets a component contain other componentsFor “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" }, }, },
  • arrayFields is a nested fields-shaped object describing one row’s shape.
  • getItemSummary controls the collapsed-row label in the editor sidebar (so an editor sees “500+” instead of “Item 1”).
  • defaultItemProps is what a new row starts with when someone clicks “add.” Can be a function of the index instead of a static object.
  • min/max bound 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.

Last updated on