The GutenFields manual
Everything the plugin can do, in one page: how a field group becomes a block, every field type and its configuration, the template helpers, and the commands. Written for the developer building the theme — and structured so an AI assistant can read it too.
Getting started
Install the plugin, define a block, write its template. There is nothing to compile and nothing to configure.
Install
- Download the zip from your account. Sign in with the address you bought with — we email a one-time code, there's no password.
- Upload it under Plugins → Add New → Upload Plugin, or unzip the folder into
wp-content/plugins/gutenfields. Keep the folder name: WordPress identifies the plugin by it, and a renamed folder installs as a separate copy rather than an update. - Activate it. The editor bundle ships prebuilt, so the plugin works the moment it is switched on.
- Enter your licence key under GutenFields → Licence. Optional — the plugin runs unlicensed; the key is what unlocks one-click updates and priority support.
Your first block
Put this in your theme's functions.php, an inc/blocks.php it
includes, or an mu-plugin. Two rules matter: register on init at
priority 5 (the plugin boots blocks at 10), and guard on
function_exists() so the theme degrades gracefully if the plugin is off.
add_action( 'init', function () {
if ( ! function_exists( 'gutenfields_register_block' ) ) {
return;
}
gutenfields_register_block( [
'name' => 'callout', // → block gutenfields/callout
'title' => 'Callout',
'fields' => [
[ 'name' => 'heading', 'label' => 'Heading', 'type' => 'text' ],
[ 'name' => 'body', 'label' => 'Body', 'type' => 'rich' ],
[ 'name' => 'cta', 'label' => 'Button', 'type' => 'link' ],
],
] );
}, 5 ); // before the priority-10 init where blocks are booted
Then write the one file you hand-write per block —
yourtheme/gutenfields/callout.php:
<div <?php echo get_block_wrapper_attributes(); ?>>
<h2><?php gf_the_field( 'heading' ); ?></h2>
<?php echo wp_kses_post( gf_field( 'body' ) ); ?>
<?php echo gf_link_html( 'cta', [ 'class' => 'btn' ] ); ?>
</div>
Reload the block editor and Callout is in the inserter, with a Heading, Body and Button to fill in. If you skip the template the block still renders — see template resolution.
Don't want to write the template by hand?
wp gutenfields scaffold callout generates a starter template from the
block's fields, into the active theme. Refine the markup from there.
The model
A GutenFields block is a field definition plus a render template. That is the
whole thing. There is no block.json per block, no React component per
block, and no build step.
gutenfields_register_block([...]) PHP config (version-controlled)
│
▼
Registry::boot() ── register_block_type() with a shared render_callback
│ │
▼ ▼
schema localized to JS Renderer::render() resolves a PHP template:
│ 1. child theme /gutenfields/{slug}.php
▼ 2. parent theme /gutenfields/{slug}.php
one generic <Edit> 3. config 'template' path
maps field → core control 4. generic auto-render
The editor never gets a hand-written React component: a single generic
Edit reads the field schema and maps each type to a control from
@wordpress/components and @wordpress/block-editor. The front
end is pure PHP you control.
Field values are stored in the block's attributes, in post content — not in post meta. The data travels with the block: copy the block to another post and its content comes with it.
Defining blocks
There are three ways in, and they all feed the same registration pipeline — once registered, a code block and a database block are indistinguishable.
Config-as-code (recommended for themes)
gutenfields_register_block() from a theme or mu-plugin. Version-controlled,
no database rows, and the right home for anything you ship. The full config:
gutenfields_register_block( [
'name' => 'callout', // required — becomes gutenfields/callout
'title' => 'Callout', // shown in the inserter
'category' => 'widgets', // inserter category
'description' => 'A heading with a button.',
'icon' => 'megaphone', // any dashicon slug (code blocks only)
'supports' => [ 'align' => [ 'wide', 'full' ] ],
'template' => '/path/to/callout.php', // optional fallback template
'fields' => [ /* … */ ],
] );
The admin field builder (database)
Build a block visually under wp-admin → GutenFields. Good for quick work and for people who don't want to touch PHP. More on the builder below.
JSON import
A portable definitions file, matching the published JSON Schema. This is the format an AI assistant emits, and the format the admin's Export/Import buttons move between environments. Imported blocks are database-backed, exactly like builder blocks.
A file may carry blocks, optionsPages, or both — either key is
enough on its own.
{
"$schema": "https://guten-fields.com/schema/gutenfields-blocks.schema.json",
"gutenfields": "1.0",
"blocks": [
{
"name": "callout",
"title": "Callout",
"category": "widgets",
"fields": [
{ "name": "heading", "label": "Heading", "type": "text" },
{ "name": "body", "label": "Body", "type": "rich" },
{ "name": "cta", "label": "Button", "type": "link" }
]
}
],
"optionsPages": [
{
"slug": "site-settings",
"title": "Site settings",
"fields": [
{ "name": "phone", "label": "Phone", "type": "text" }
]
}
]
}
$ wp gutenfields import blocks.json # add --dry-run to preview
Two differences from code blocks: the icon is auto-derived from the first field's
type (a supplied icon is ignored), and supports is limited
to align and anchor.
Code wins on a name conflict
If a saved block and a gutenfields_register_block() call share a name, the
code block is the one that registers, and the admin flags the saved one as inactive.
That makes graduating a block from the builder into version control safe: export the
PHP, paste it in, and the database copy steps aside.
Naming
- Block slugs are lowercase and hyphenated —
hero-banner. - Field keys are lowercase snake_case —
hero_heading. - The template file name must match the slug exactly:
hero-banner.php.
The field builder
wp-admin → GutenFields. Create a block, give it a title, slug and category, add
fields, save. Reload the block editor and it is in the inserter. The screen requires the
manage_options capability.
- Each field shows a type icon and collapses to a one-line summary (label, key, type), so a long block stays scannable.
- The field key auto-fills from the label and stays editable — type “Hero heading” and the key becomes
hero_heading. - The block icon is automatic, derived from the first field's type. There is no icon picker to manage.
- Drag to reorder fields and repeater subfields with the handle; a blue insertion line shows where the field will land. Works with mouse and touch, and the up/down arrows remain for keyboard use.
- Duplicate clones a block with a fresh unique slug.
- Export PHP generates a ready-to-paste
gutenfields_register_block()call, so a block built by clicking can graduate into version-controlled code. - An Options pages tab sits beside the blocks, building site-wide field groups with the same field editor.
Moving a field setup between environments
Export and Import in the sidebar move your whole admin-built setup from
local to staging to production. Export downloads a gutenfields-blocks.json;
Import loads one back. Import is an upsert matched by slug:
- a block whose slug already exists is updated in place;
- a new slug is added;
- blocks not named in the file are left untouched — importing never wipes your setup.
Import first shows a review step listing exactly what will be updated, added or
skipped, and writes nothing until you confirm. It is the same JSON format as
wp gutenfields import/export, so the two are interchangeable.
Field types
Twenty-two types. name (snake_case key) and type are required on
every field; label is recommended. The stored value column is what the
template helper hands you.
| Type | Extra config | Stored value | Read in a template with |
|---|---|---|---|
text | default | string | gf_the_field('x') |
textarea | default | string | gf_the_field('x') |
rich | default | HTML string | echo wp_kses_post( gf_field('x') ) |
number | default | number | gf_the_field('x') |
range | min, max, step, default | number | gf_the_field('x') |
email | default | string | gf_field('x') |
url | default | string | gf_field('x') |
date | default | string YYYY-MM-DD | gf_field('x') |
color | default | string (hex) | gf_field('x') |
toggle | default (bool) | boolean | if ( gf_field('x') ) |
select | options, default | string | gf_the_field('x') |
radio | options, default | string | gf_the_field('x') |
checkbox | options | array of strings | foreach ( gf_field('x', []) … ) |
image | — | {id,url,alt} or null | gf_image('x') |
gallery Pro | min, max | array of {id,url,alt} | gf_gallery('x') |
file | — | {id,url,filename,mime} or null | gf_file('x') |
link | — | {url,label,opensInNewTab} or null | gf_link('x') · gf_link_html('x') |
repeater Pro | subfields, rowLabel, min, max, layout | array of rows | gf_repeater('x') |
flexible Pro | layouts, min, max | array of rows | gf_flexible('x') |
post | postTypes, multiple | int, or int[] if multiple | gf_post('x') · gf_posts('x') |
taxonomy | taxonomy, multiple | int, or int[] if multiple | gf_term('x') · gf_terms('x') |
user | multiple | int, or int[] if multiple | gf_user('x') · gf_users('x') |
Options
select, radio and checkbox take an
options array of value/label pairs:
[ 'name' => 'tone', 'label' => 'Tone', 'type' => 'select', 'default' => 'info',
'options' => [
[ 'value' => 'info', 'label' => 'Information' ],
[ 'value' => 'warning', 'label' => 'Warning' ],
] ],
Gallery Pro
A gallery holds several images in one field, picked in the media modal and
reordered in place. Set min and max to bound how many:
[ 'name' => 'shots', 'label' => 'Screenshots', 'type' => 'gallery', 'max' => 6 ],
gf_gallery() hands back a plain list of {id,url,alt}, in the
chosen order. Images whose attachment has since been deleted are dropped from that
list, so you can loop it without guarding each item:
<?php foreach ( gf_gallery( 'shots' ) as $image ) : ?>
<img src="<?php echo esc_url( $image['url'] ); ?>"
alt="<?php echo esc_attr( $image['alt'] ); ?>" />
<?php endforeach; ?>
Relational fields
post, taxonomy and user store IDs and search live
in the editor. A post field can span several post types; set
multiple to collect a list rather than one:
[ 'name' => 'related', 'label' => 'Related', 'type' => 'post',
'postTypes' => [ 'post', 'case-study' ],
'multiple' => true ],
In the template, gf_posts() hands you resolved WP_Post objects
(and gf_terms() / gf_users() the term and user equivalents), so
you never write the lookup yourself.
Repeater & flexible content Pro
Repeater and flexible content are paid-licence features. The free edition on WordPress.org has every other field type; see pricing for what a licence adds and what it costs.
Repeater
A list of rows where every row has the same subfields. It takes the same
field-type vocabulary you'd use at the top level.
[
'name' => 'bullets',
'label' => 'Bullets',
'type' => 'repeater',
'rowLabel' => 'Bullet', // used in the "Add Bullet" button
'min' => 0, // optional
'max' => null, // optional
'subfields' => [
[ 'name' => 'icon', 'label' => 'Icon', 'type' => 'image' ],
[ 'name' => 'text', 'label' => 'Text', 'type' => 'text' ],
],
],
Iterate with gf_repeater() and read each row with the
gf_sub_* helpers:
<?php if ( gf_repeater_count( 'bullets' ) ) : ?>
<ul class="gf-callout__bullets">
<?php foreach ( gf_repeater( 'bullets' ) as $bullet ) : ?>
<li>
<?php if ( $icon = gf_sub_image( 'icon' ) ) : ?>
<img src="<?php echo esc_url( $icon['url'] ); ?>"
alt="<?php echo esc_attr( $icon['alt'] ); ?>" />
<?php endif; ?>
<span><?php gf_the_sub_field( 'text' ); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
Or skip the helpers entirely — gf_field( 'bullets', [] ) gives you a plain
array<int, array<string,mixed>> to walk however you like.
Flexible content
A flexible field holds rows that each pick one of several named
layouts, and each layout has its own subfields. It is how you let an editor
assemble a page section by section without you predicting the order.
[
'name' => 'sections',
'label' => 'Sections',
'type' => 'flexible',
'layouts' => [
[ 'name' => 'hero', 'label' => 'Hero', 'subfields' => [
[ 'name' => 'heading', 'label' => 'Heading', 'type' => 'text' ],
] ],
[ 'name' => 'quote', 'label' => 'Quote', 'subfields' => [
[ 'name' => 'text', 'label' => 'Text', 'type' => 'textarea' ],
] ],
],
],
The chosen layout is stored on the row as _layout. Iterating gives you the
layout name as the key, so you branch on it:
<?php foreach ( gf_flexible( 'sections' ) as $layout => $row ) : ?>
<?php if ( $layout === 'hero' ) : ?>
<h2><?php gf_the_sub_field( 'heading' ); ?></h2>
<?php elseif ( $layout === 'quote' ) : ?>
<blockquote><?php gf_the_sub_field( 'text' ); ?></blockquote>
<?php endif; ?>
<?php endforeach; ?>
Nesting containers
A repeater row or a flexible layout can hold another repeater or
flexible, up to five levels deep. A nested container reads its rows
from the row it sits in rather than from the block, so inside a loop you use the
row-scoped iterators — gf_sub_repeater() and
gf_sub_flexible() — alongside the gf_sub_* value helpers:
<?php foreach ( gf_repeater( 'sections' ) as $row ) : ?>
<h3><?php gf_the_sub_field( 'title' ); ?></h3>
<?php foreach ( gf_sub_repeater( 'links' ) as $row2 ) : ?>
<?php if ( $link = gf_sub_link( 'url' ) ) : ?>
<a href="<?php echo esc_url( $link['url'] ); ?>">
<?php gf_the_sub_field( 'label' ); ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
<?php endforeach; ?>
gf_sub_repeater_count() and gf_sub_flexible_count() are there
too, and gf_layout() always reports the innermost flexible row's layout, at
any depth.
Five levels is a limit, not a target
The cap exists to bound recursion on an imported file, not to describe a sensible field setup. Two or three levels is already a lot to work in — past that, an editor is scrolling through nested cards looking for the one row they came to change. A container deeper than the cap is imported as a plain text field rather than breaking the block.
Conditional logic
Every field can be shown or hidden based on a sibling field's value. Rules apply in the editor — like ACF, they hide the input; they do not strip stored values.
[ 'name' => 'cta', 'label' => 'Button', 'type' => 'link',
'conditions' => [
[ 'field' => 'has_button', 'operator' => '==', 'value' => '1' ],
],
'conditionsLogic' => 'all' ], // 'all' | 'any'
Operators: ==, !=, contains, >,
<, empty, notEmpty. Inside a repeater, each row
evaluates its own rules independently — a rule in row 3 looks at row 3's values.
Rule groups
One all/any across a flat list runs out quickly. An entry in
conditions can instead be a group — its own list of rules with its own
logic — which is how you write [A and B] or C without splitting
the field in two:
[ 'name' => 'cta', 'label' => 'Button', 'type' => 'link',
'conditionsLogic' => 'any', // OR between the entries below
'conditions' => [
[ 'logic' => 'all', 'conditions' => [ // [ A and B ]
[ 'field' => 'style', 'operator' => '==', 'value' => 'hero' ],
[ 'field' => 'heading', 'operator' => 'notEmpty' ],
] ],
[ 'field' => 'force_cta', 'operator' => '==', 'value' => '1' ], // or C
] ],
An entry is a rule when it names a field and a group when it carries its own
conditions — there is no type flag to set. Groups nest five levels deep, and
the field builder has Add rule and Add group buttons at every level.
Flat rule lists written before groups existed still mean exactly what they meant, so there is nothing to migrate.
Options pages Pro
Options pages are a paid-licence feature. Everything they are built from — the field types, the builder, the helpers — is in the free edition; what a licence adds is the page itself. See pricing.
Some fields don't belong to a block. An options page is a field group that belongs to the site, with its own screen in wp-admin.
Contact details, social links, a footer notice, the fallback share image — things a site has exactly one of, that a block would be the wrong home for. Same field types, same builder, same helpers; what differs is that the values are edited on an admin screen and can be read anywhere in your theme, not only inside a block template.
Defining one
In code, registered on init at priority 5 exactly like a block:
add_action( 'init', function () {
if ( ! function_exists( 'gutenfields_register_options_page' ) ) {
return;
}
gutenfields_register_options_page( [
'slug' => 'site-settings', // how gf_option() names it
'title' => 'Site settings',
'menuTitle' => 'Settings', // optional, defaults to the title
'parent' => 'gutenfields', // parent menu; '' gives it its own
'capability' => 'manage_options', // who may edit it
'fields' => [
[ 'name' => 'phone', 'label' => 'Phone', 'type' => 'text' ],
[ 'name' => 'socials', 'label' => 'Social links', 'type' => 'repeater',
'subfields' => [
[ 'name' => 'label', 'label' => 'Label', 'type' => 'text' ],
[ 'name' => 'url', 'label' => 'URL', 'type' => 'link' ],
] ],
],
] );
}, 5 );
Or build one in the admin: the GutenFields screen has an Options pages tab beside
the blocks, with the same field builder. Either way it can be exported to PHP, and pages
travel in the same import/export file as blocks under an optionsPages key.
Reading the values
Values are keyed by page slug, so every helper takes the page first. Because they read a
stored option rather than the block being rendered, they work in
header.php, a widget, or anywhere else a theme runs:
| Helper | Returns |
|---|---|
gf_option($page, $name, $default = null) | the raw value |
gf_the_option($page, $name) | echoes it, HTML-escaped |
gf_options($page) | every value on the page |
gf_option_image($page, $name) | {id,url,alt} or null |
gf_option_file($page, $name) | {id,url,filename,mime} or null |
gf_option_link($page, $name) | {url,label,opensInNewTab} or null |
gf_option_gallery($page, $name) | list of {id,url,alt} |
gf_option_repeater($page, $name) | iterable of rows (sets the row context) |
gf_option_repeater_count($page, $name) | int |
gf_option_flexible($page, $name) | iterable of layoutName => row |
gf_option_flexible_count($page, $name, $layout = '') | int |
Inside gf_option_repeater() the ordinary row helpers apply, so a repeater on
an options page reads exactly like one in a block:
<?php if ( $phone = gf_option( 'site-settings', 'phone' ) ) : ?>
<a href="<?php echo esc_attr( 'tel:' . $phone ); ?>">
<?php echo esc_html( $phone ); ?>
</a>
<?php endif; ?>
<?php foreach ( gf_option_repeater( 'site-settings', 'socials' ) as $row ) : ?>
<?php if ( $link = gf_sub_link( 'url' ) ) : ?>
<a href="<?php echo esc_url( $link['url'] ); ?>">
<?php gf_the_sub_field( 'label' ); ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
Where the values live
One WordPress option per page, named gutenfields_options_{slug}. Reading a
whole page costs a single autoloaded option rather than one lookup per field, and the
values are plain option data you can export, migrate or write to yourself.
Deleting a page keeps its values
Removing an options page removes the form, not the content. Deleting a definition is usually a step in rebuilding it, and a site's settings disappearing halfway through that is not a recoverable mistake — recreate the page with the same slug and the values are still there.
Templates & helpers
Where the file goes
The plugin resolves a block's template in this order:
wp-content/themes/{child}/gutenfields/{slug}.phpwp-content/themes/{parent}/gutenfields/{slug}.php- the
templatepath in the block config (code blocks only) - otherwise a generic auto-render — one BEM-classed element per field, so the block displays immediately while you build
So for gutenfields/callout, write
theme/gutenfields/callout.php. Drop that file in at any point to take over
from the auto-renderer.
What's in scope
Inside a template you have three variables — $gf_attributes (all field
values), $gf_content and $gf_block (the WP_Block)
— and these helpers. They are valid only inside a rendering template.
| Helper | Returns |
|---|---|
gf_field($name, $default = null) | the raw value |
gf_the_field($name) | echoes it, HTML-escaped |
gf_image($name) | {id,url,alt} or null |
gf_gallery($name) | list of {id,url,alt} |
gf_gallery_count($name) | int |
gf_file($name) | {id,url,filename,mime} or null |
gf_link($name) | {url,label,opensInNewTab} or null |
gf_link_html($name, $attr = []) | a safe <a> element string |
gf_repeater($name) | iterable of rows (sets the row context) |
gf_repeater_count($name) | int |
gf_flexible($name) | iterable of layoutName => row |
gf_flexible_count($name, $layout = '') | int |
gf_post($name) · gf_posts($name) | resolved WP_Post object(s) |
gf_term($name) · gf_terms($name) | resolved WP_Term object(s) |
gf_user($name) · gf_users($name) | resolved WP_User object(s) |
Row-scoped helpers
Valid inside a gf_repeater() or gf_flexible() loop, where
they read the current row: gf_sub_field, gf_the_sub_field,
gf_sub_image, gf_sub_gallery,
gf_sub_gallery_count, gf_sub_file, gf_sub_link,
gf_sub_posts, gf_sub_terms, gf_sub_users, and
gf_layout (the innermost flexible layout's name).
For a container inside a row there are gf_sub_repeater,
gf_sub_repeater_count, gf_sub_flexible and
gf_sub_flexible_count — see
nesting containers.
A complete template
<?php
defined( 'ABSPATH' ) || exit;
?>
<div <?php echo get_block_wrapper_attributes( [ 'class' => 'gf-callout' ] ); ?>>
<h2 class="gf-callout__heading"><?php gf_the_field( 'heading' ); ?></h2>
<?php if ( $body = gf_field( 'body' ) ) : ?>
<div class="gf-callout__body"><?php echo wp_kses_post( $body ); ?></div>
<?php endif; ?>
<?php echo gf_link_html( 'cta', [ 'class' => 'gf-callout__cta' ] ); ?>
</div>
House style for class names is BEM off a gf-{slug} base, which is also what
the scaffolder and the auto-renderer emit — so hand-written and generated templates look
the same.
Escape everything
You are writing front-end HTML from editor input. gf_the_field() and
gf_the_sub_field() escape for you; raw gf_field() output must be
wrapped in esc_html(), esc_attr() or esc_url().
Rich text goes through wp_kses_post() — never echo it raw.
AI developer kit
GutenFields is built to be driven by an AI coding assistant. Not a chatbot bolted onto wp-admin — the plugin ships the material an assistant already reading your repo needs, so it writes against the real API instead of guessing it. It works with Claude Code, Cursor, Copilot, or anything else that reads files.
Three pieces make it reliable:
-
AGENTS.md, bundled in the plugin — the complete API written for an LLM: every field type and its config, every helper, the file-resolution rules and worked examples. Point your assistant atwp-content/plugins/gutenfields/AGENTS.md, or runwp gutenfields ai-guide --catto print it. - A JSON Schema for the portable block-definitions file, so an assistant can emit definitions and validate them before they ever reach WordPress.
- WP-CLI commands to import that file, export existing blocks, and scaffold templates.
A typical flow:
- Tell your assistant “add a testimonials block with a repeater of quotes”. It writes
blocks.jsonagainst the schema. wp gutenfields import blocks.json— add--dry-runfirst to validate without writing.wp gutenfields scaffold testimonialswrites a starter template into the active theme; refine the markup from there.- For a shipped theme,
wp gutenfields export testimonials --format=phpgraduates the block into version-controlled config-as-code.
WP-CLI & REST
wp gutenfields list # every block + source (code/db)
wp gutenfields options # every options page + its admin URL
wp gutenfields import blocks.json # load a JSON definitions file
wp gutenfields import blocks.json --dry-run # validate without writing
wp gutenfields export --format=json --out=x.json # dump all blocks as JSON
wp gutenfields export callout --format=php # one block as config-as-code PHP
wp gutenfields scaffold callout # write theme/gutenfields/callout.php
wp gutenfields scaffold callout --force # overwrite an existing template
wp gutenfields ai-guide --cat # print the AI guide
The same operations are available over REST under gutenfields/v1/ —
import, export, and blocks and
options-pages CRUD — all gated on the manage_options
capability, if you'd rather drive the plugin over HTTP than WP-CLI.
One route is gated differently on purpose: options/{slug}, which reads and
writes a page's values, checks that page's own capability instead.
That is the point of the setting — an options page can be handed to an editor without
handing over the field builder along with it.
Licence & updates
GutenFields is a paid, GPL-2.0 plugin. An unlicensed site keeps working exactly as installed — the key is what unlocks one-click automatic updates and priority support, not the functionality. Enter it under GutenFields → Licence. See pricing.
Development and staging copies are free
Only production sites count against a licence, on every tier — so a single-site licence covers your live site plus your laptop, your staging server and a client preview.
A site's environment comes from core's wp_get_environment_type(), so one line
in wp-config.php is enough to keep a copy off the meter:
define( 'WP_ENVIRONMENT_TYPE', 'staging' ); // or 'local' / 'development'
Obvious hostnames — *.test, localhost,
staging.example.com, the managed hosts' staging domains — are recognised
without any of that. To override it for GutenFields alone:
define( 'GUTENFIELDS_ENVIRONMENT', 'development' );
// or
add_filter( 'gutenfields_license_environment', fn() => 'staging' );
You can also set a site's environment by hand in your account, which is where you move a key between sites — including sites you no longer have wp-admin access to.
What the plugin sends
Editing and rendering your fields make no external requests: your content never leaves your site. The plugin contacts our server only for licence activation, a once-daily entitlement re-check and update checks, and even then it sends just your site URL, your licence key, this install's environment type and — on an update check — the version you already have. Nothing else is transmitted, and the privacy policy lists those fields one by one.
Limits
Worth knowing before you plan around them.
- Containers nest five levels deep, and a container past that is imported as a plain text field. The cap is a recursion guard on imported files rather than a judgement about your fields — but see the note under nesting containers on why two or three is usually the real limit.
- Conditional rules compare against siblings only — other fields in the same block, or other subfields in the same row. A rule cannot reach up out of a repeater row, or across to another block.
- Rules hide inputs; they do not gate rendering. A hidden field keeps whatever value it had, and your template still receives it. Treat conditional logic as an editing affordance, and branch in the template if the front end should differ.
- Rich text on an options page is a plain textarea. The rich editor belongs to the block editor and has no block to attach to on an admin screen; HTML you type there is saved and rendered as written.
- The editor previews fields, not your template. The canvas shows the field inputs rather than running the PHP template — the front end is the preview.
The plugin is stable at v1.2.0, built and tested against WordPress 6.5–7.0 on PHP 8.0+.
Questions
01 Do I need to know React?
02 Do I need a build step?
wp.* objects, so it runs the moment you activate the plugin. The JSX
source is included if you would rather build it yourself with
@wordpress/scripts — npm install && npm run build
overwrites build/ from src/.
03 Where is my block's data stored?
04 Can I move a block I built in the admin into code?
gutenfields_register_block() call, ready to paste into your theme. The
code version then takes precedence over the saved one.
05 What happens if I deactivate the plugin?
06 Can I add my own field type?
case in src/controls.js,
a matching attribute type in Registry::attribute_schema() and
attrSchema() in src/index.js, and a helper in
template-functions.php if the value needs shaping. Mirror the control
into build/index.js as well, or run the build — the shipped bundle is
hand-written, so an edit to src/ alone changes nothing on a live site. Note that a modified
copy is yours to maintain — it will not survive an update.
Getting help
Email support@guten-fields.com. Licence holders get priority; include your key, your WordPress and PHP versions, and the block definition you're working with — it saves a round trip.
For anything about keys, seats or which site is on which licence, the account portal answers it faster than we can, and the pricing FAQ covers renewals, with the refund policy and the terms of service alongside it. The plugin is GPL-2.0-or-later, and every copy ships its full source — there is no build step and nothing minified.