Blockstudio
BlocksAttributes

Custom Field Types

Custom field types let a theme or plugin add new Blockstudio field controls. They are different from custom fields:

  • custom/{name} references a reusable group of existing fields.
  • namespace/type-name uses a registered field type with its own editor control and value shape.

Blockstudio ships the registration system. Your theme or plugin ships the control.

When To Use Them

Use a custom field type when a project needs a single purpose editing control that should behave like a native Blockstudio field everywhere:

  • block attributes
  • reusable custom/{name} field groups
  • groups, tabs, and repeaters
  • PHP, Twig, and Blade templates
  • post meta and option storage
  • extension templates such as {attributes.margin.top}

For example, a spacing control can store one object instead of four separate text fields:

block.json
{
  "blockstudio": {
    "attributes": [
      {
        "id": "margin",
        "type": "acme/dimensions",
        "label": "Margin",
        "sides": ["top", "right", "bottom", "left"],
        "default": {
          "top": "sm",
          "right": "md",
          "bottom": "sm",
          "left": "md"
        }
      }
    ]
  }
}

Naming

Custom field type names must be lowercase, namespaced, and dashcase:

namespace/type-name

Valid examples:

  • acme/dimensions
  • acme/text-options
  • theme/spacing-token

Reserved namespaces:

  • custom/ is reserved for reusable custom field groups.
  • blockstudio/ is reserved for Blockstudio core.

Invalid names are ignored by the filter API and rejected by the helper API.

PHP Registration

Register the field type metadata in PHP:

functions.php
add_action('init', function () {
    wp_register_script(
        'acme-field-dimensions',
        get_stylesheet_directory_uri() . '/fields/dimensions.js',
        ['blockstudio-blocks', 'wp-components', 'wp-element'],
        '1.0.0',
        true
    );

    bs_register_field_type('acme/dimensions', [
        'attribute'     => 'object',
        'default'       => [],
        'editor_script' => 'acme-field-dimensions',
        'storage'       => [
            'type'        => 'object',
            'rest_schema' => [
                'type'                 => 'object',
                'additionalProperties' => ['type' => 'string'],
            ],
        ],
    ]);
});

bs_register_field_type() returns true when registration succeeds and false when the name or definition is invalid. You can unregister a manual registration with bs_unregister_field_type().

You can also use the filter API:

functions.php
add_filter('blockstudio/field_types', function (array $types): array {
    $types['acme/dimensions'] = [
        'attribute'     => 'object',
        'default'       => [],
        'editor_script' => 'acme-field-dimensions',
    ];

    return $types;
});

Definition Keys

KeyTypeDescription
attributestring, array, nullWordPress attribute type. Use string, number, boolean, object, array, or an array of those types.
defaultmixedRegistry-level default when a field does not define its own default.
sourcestringWordPress attribute source for custom serialization cases.
produces_attributebooleanSet to false for display-only controls. Defaults to true.
editor_scriptstring or arrayRegistered script handle or handles to enqueue in the block editor when the type is used.
editor_stylestring or arrayRegistered style handle or handles to enqueue in the block editor when the type is used.
storagearrayStorage value type and REST schema hints for post meta and options.
supportsarrayOptional capability flags for your own control.

JavaScript Registration

The editor script registers the matching React control:

fields/dimensions.js
const { createElement: el } = wp.element;
const { TextControl } = wp.components;

window.blockstudio.registerFieldType('acme/dimensions', {
  component(props) {
    const value =
      props.value &&
      typeof props.value === 'object' &&
      !Array.isArray(props.value)
        ? props.value
        : {};
    const sides = props.sides || ['top', 'right', 'bottom', 'left'];

    return el(
      'div',
      {},
      sides.map((side) =>
        el(TextControl, {
          key: side,
          label: side,
          value: value[side] || '',
          onChange(nextValue) {
            props.onChange({
              ...value,
              [side]: nextValue,
            });
          },
        }),
      ),
    );
  },
});

registerFieldType() returns true when the registration is accepted. Remove a client registration with:

window.blockstudio.unregisterFieldType('acme/dimensions');

The component receives the field definition properties plus these stable props:

PropDescription
typeField type name.
idField ID.
fieldFull field definition from block.json.
valueCurrent saved value.
defaultValueField default value.
onChangeStore a new value directly.
attributesCurrent block attributes.
blockCurrent block type definition.
clientIdCurrent editor block client ID.
inRepeaterWhether the field is inside a repeater row.
repeaterIdCurrent repeater path, if any.
disabledWhether the field is disabled in the editor.

Custom field type values are stored directly. They do not pass through native select, radio, color, or gradient option normalization.

Rendering Values

Object values are available like any other Blockstudio attribute.

PHP

index.php
<div class="<?php echo esc_attr('mt-' . ($a['margin']['top'] ?? '')); ?>">
  <?php echo esc_html($a['title'] ?? ''); ?>
</div>

Twig

index.twig
<div class="mt-{{ a.margin.top }}">
  {{ a.title }}
</div>

Blade

index.blade.php
<div class="mt-{{ $a['margin']['top'] ?? '' }}">
  {{ $a['title'] ?? '' }}
</div>

Nested object paths also work in extension templates:

extend.json
{
  "blockstudio": {
    "extend": "core/group",
    "attributes": [
      {
        "id": "margin",
        "type": "acme/dimensions",
        "set": [
          {
            "attribute": "class",
            "value": "mt-{attributes.margin.top} mb-{attributes.margin.bottom}"
          }
        ]
      }
    ]
  }
}

Storage

When a custom field type is used with postMeta or option storage, Blockstudio uses the registered storage value type and REST schema.

block.json
{
  "id": "margin",
  "type": "acme/dimensions",
  "label": "Margin",
  "storage": {
    "type": "postMeta",
    "postMetaKey": "acme_margin"
  }
}

For array and object values, define storage.rest_schema on the field type registration. Repeater descendants keep using array-backed storage because a repeater stores rows.

Fallback Behavior

If PHP knows the field type but the editor script does not register a matching control, Blockstudio keeps the saved value and shows a non-editable notice in the editor. Frontend rendering still receives the saved value.

If a field type is not registered in PHP, Blockstudio leaves it out of the generated WordPress attributes. Register the PHP definition before block discovery runs, usually on init before Blockstudio builds blocks.

On this page