Skip to content

Custom Field Types

The framework’s field registry is extensible. A custom field type is a PHP class implementing FieldInterface (usually via BaseField), registered through the wp-wireframe/field_types filter.

use Wireframe\Framework\Fields\BaseField;
class MyCustomField extends BaseField
{
public static function type(): string
{
return 'my_custom';
}
public static function sanitize(mixed $value, array $args): mixed
{
return sanitize_text_field((string) $value);
}
}

Then register:

add_filter('wp-wireframe/field_types', function (array $types): array {
$types['my_custom'] = MyCustomField::class;
return $types;
});

That’s enough for the server side — the field accepts and sanitises values. You’ll still need to tell the React side how to render it (see below).

BaseField provides defaults for everything except type() and sanitize(). Override the rest to customise behaviour:

MethodReturnsPurpose
static type(): stringstringThe slug used in config ('my_custom'). Required.
static sanitize(mixed $value, array $args): mixedsanitised valueCoerce the value into a safe canonical form. Required.
static isStateless(): boolbooltrue for display-only fields that don’t persist a value.
static defaultRules(array $args): stringstringDefault validation rules for the field.
static validate(mixed $value, array $args): ?string?stringReturn an error message, or null if valid.

Field types are matched to React components in js/components/fields/index.js:

export const customEditComponents = {
text: TextInputEdit,
select: SelectEdit,
toggle: ToggleEdit,
// …
my_custom: MyCustomEdit, // ← your new mapping
};

For a custom type you have three options, ranked by effort:

  1. Reuse an existing component — if your field is “a text input that stores something specific”, map my_custom: TextInputEdit and let the server’s sanitize() do the work.
  2. Wrap an existing component — wrap one of @wordpress/components’ primitives in a thin React component that reads field, value, onChange, and (if relevant) errors.
  3. Build from scratch — full control, but you’ll need to register the component in the bundle and ship the updated JS.

The simplest custom component shape:

import { BaseControl } from '@wordpress/components';
export default function MyCustomEdit({ field, value, onChange }) {
return (
<BaseControl
__nextHasNoMarginBottom
label={field.label}
help={field.description}
id={`my-custom-${field.id}`}
>
<input
value={value || ''}
onChange={(e) => onChange(e.target.value)}
disabled={field.readOnly}
/>
</BaseControl>
);
}

Two different responsibilities:

  • sanitize() turns whatever came in into a canonical form. Coerce types, strip dangerous markup, trim whitespace, normalise case. Always runs before validation, even on the save endpoint and action-field requests.
  • validate() decides whether the (already-sanitised) value is acceptable. Return null to accept, or an error string to reject. Only runs at save time.

Keep sanitize() cheap and total — never throw, never return WP_Error. Return something useful for any input, even an empty string for invalid input.