Documentation

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.

GutenFields 1.1.0 WordPress 6.5–7.0 PHP 8.0+ No build step

Getting started

Install the plugin, define a block, write its template. There is nothing to compile and nothing to configure.

Install

  1. Download the zip from your account. Sign in with the address you bought with — we email a one-time code, there's no password.
  2. 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.
  3. Activate it. The editor bundle ships prebuilt, so the plugin works the moment it is switched on.
  4. 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.

theme/inc/blocks.php
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:

theme/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( $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.

blocks.json
{
  "$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.

TypeExtra configStored valueRead in a template with
textdefaultstringgf_the_field('x')
textareadefaultstringgf_the_field('x')
richdefaultHTML stringecho wp_kses_post( gf_field('x') )
numberdefaultnumbergf_the_field('x')
rangemin, max, step, defaultnumbergf_the_field('x')
emaildefaultstringgf_field('x')
urldefaultstringgf_field('x')
datedefaultstring YYYY-MM-DDgf_field('x')
colordefaultstring (hex)gf_field('x')
toggledefault (bool)booleanif ( gf_field('x') )
selectoptions, defaultstringgf_the_field('x')
radiooptions, defaultstringgf_the_field('x')
checkboxoptionsarray of stringsforeach ( gf_field('x', []) … )
image{id,url,alt} or nullgf_image('x')
gallery Promin, maxarray of {id,url,alt}gf_gallery('x')
file{id,url,filename,mime} or nullgf_file('x')
link{url,label,opensInNewTab} or nullgf_link('x') · gf_link_html('x')
repeater Prosubfields, rowLabel, min, max, layoutarray of rowsgf_repeater('x')
flexible Prolayouts, min, maxarray of rowsgf_flexible('x')
postpostTypes, multipleint, or int[] if multiplegf_post('x') · gf_posts('x')
taxonomytaxonomy, multipleint, or int[] if multiplegf_term('x') · gf_terms('x')
usermultipleint, or int[] if multiplegf_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:

theme/gutenfields/callout.php
<?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:

functions.php
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:

HelperReturns
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:

theme/footer.php
<?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:

  1. wp-content/themes/{child}/gutenfields/{slug}.php
  2. wp-content/themes/{parent}/gutenfields/{slug}.php
  3. the template path in the block config (code blocks only)
  4. 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.

HelperReturns
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

theme/gutenfields/callout.php
<?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 at wp-content/plugins/gutenfields/AGENTS.md, or run wp gutenfields ai-guide --cat to 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:

  1. Tell your assistant “add a testimonials block with a repeater of quotes”. It writes blocks.json against the schema.
  2. wp gutenfields import blocks.json — add --dry-run first to validate without writing.
  3. wp gutenfields scaffold testimonials writes a starter template into the active theme; refine the markup from there.
  4. For a shipped theme, wp gutenfields export testimonials --format=php graduates the block into version-controlled config-as-code.

WP-CLI & REST

wp gutenfields
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?
No — that is the point of the plugin. The editing interface is generated from your field definitions, and the front end is an ordinary PHP template.
02 Do I need a build step?
No. The editor bundle ships prebuilt and is written against the global 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/scriptsnpm install && npm run build overwrites build/ from src/.
03 Where is my block's data stored?
In the block's attributes, in post content — not in post meta. Each field group is a real dynamic block, so the data travels with the block.
04 Can I move a block I built in the admin into code?
Yes. Open it under the GutenFields menu and choose Export PHP. It generates the 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?
Blocks stop rendering, because the plugin owns the render callback. Your content is not deleted — it stays in post content as block markup and comes back when the plugin is reactivated.
06 Can I add my own field type?
Yes, by extending the plugin: add a 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.