Custom field types
You can add your own field types from a plugin or theme without touching CAFE. A custom type gets the same storage, caching, validation pipeline, hooks and (later) REST and builder support as the built-in types.
1. Write the class
Extend Cybo\AdvancedFieldEngine\Fields\AbstractFieldType and override what differs:
use Cybo\AdvancedFieldEngine\Fields\AbstractFieldType;
use Cybo\AdvancedFieldEngine\Fields\Field;
use Cybo\AdvancedFieldEngine\Fields\FieldCategory;
use Cybo\AdvancedFieldEngine\Fields\FormatContext;
use Cybo\AdvancedFieldEngine\Fields\StorageType;
final class Icon_Picker_Field extends AbstractFieldType {
private const ICONS = [ 'star', 'heart', 'bolt' ];
public function get_name(): string {
return 'icon_picker'; // Stable machine name.
}
public function get_label(): string {
return __( 'Icon Picker', 'my-plugin' );
}
public function get_category(): string {
return FieldCategory::ADVANCED; // Palette category (metadata only).
}
public function get_storage(): StorageType {
return StorageType::Text; // Which column CAFE uses.
}
protected function settings(): array { // Declarative settings → builder UI for free.
return [
'default_value' => [
'type' => 'select',
'label' => __( 'Default Icon', 'my-plugin' ),
'description' => __( 'Used when nothing is saved.', 'my-plugin' ),
'default' => 'star',
'options' => array_combine( self::ICONS, self::ICONS ),
],
];
}
public function validate( mixed $value, Field $field ): bool|\WP_Error {
if ( null === $value || '' === $value || in_array( $value, self::ICONS, true ) ) {
return true;
}
return new \WP_Error( 'my_invalid_icon', __( 'Unknown icon.', 'my-plugin' ) );
}
public function sanitize( mixed $value, Field $field ): mixed {
return is_string( $value ) ? sanitize_key( $value ) : '';
}
public function format( mixed $value, Field $field, FormatContext $context ): mixed {
return null === $value ? null : [ 'name' => $value, 'class' => 'icon-' . $value ];
}
}
2. Register it
add_action( 'cafe_register_field_types', function ( $registry ) {
$registry->register( Icon_Picker_Field::class );
} );
// Or, any time before your fields are read:
cafe_register_field_type( Icon_Picker_Field::class );
Core names (text, image, …) cannot be replaced; the name must be lowercase letters,
digits and underscores; the category must be one of the FieldCategory constants.
3. Use it
cafe()->schema_manager()->create_field( $group_id, [ 'name' => 'icon', 'type' => 'icon_picker' ] );
cafe_update_field( 'icon', 'heart', $post_id );
$icon = cafe_get_field( 'icon', $post_id ); // [ 'name' => 'heart', 'class' => 'icon-heart' ]
What a field type can define
| Method | Default in AbstractFieldType |
Purpose |
|---|---|---|
get_name(), get_label(), get_category(), get_storage() |
name/label/storage required; category basic |
Identity and storage column |
get_storage_for_field( $field ) |
get_storage() |
Storage that depends on settings |
settings() |
none (plus common Instructions) | Declarative settings: type (text, textarea, number, toggle, select, choices, list, color, colors), label, description, default, required, options, min, max, integer, max_length, pattern |
check_settings( $settings ) |
true |
Cross-setting checks |
validate( $raw, $field ) |
true |
Reject bad input with WP_Error (runs before sanitizing) |
sanitize( $raw, $field ) |
unchanged | Clean the value for storage |
is_empty( $value, $field ) |
null, '', [] |
Empty values remove the stored value |
to_storage() / from_storage() |
per storage type | Convert to/from the column |
format( $raw, $field, $context ) |
unchanged | What cafe_get_field() returns |
to_rest() / get_rest_schema() |
format() / derived from storage |
REST output and JSON Schema |
get_default_value( $field ) |
default_value setting |
Value when nothing is stored |
render_input( $field, $value, $context ) |
escaped text input | Admin input markup (must return escaped HTML) |
Optional interfaces add capabilities:
| Interface | Gives |
|---|---|
RelationFieldTypeInterface |
Store ordered links in the relationships table (to_relations() / from_relations()) |
MediaFieldTypeInterface |
Bulk-prime referenced attachments (attachment_ids()) |
SensitiveFieldTypeInterface |
Never in REST, cafe_get_fields(), admin markup or the persistent object cache |
ContainerFieldTypeInterface |
Child fields (accepts_child()) |
TransparentContainerInterface |
Layout-only container; children keep top-level names (tabs, accordion) |
RowFieldTypeInterface |
Repeating rows (repeater, flexible content: later phases) |
Rules for field types
Field types describe behaviour only. They must not run SQL, use repositories, check
capabilities, handle requests or contain JavaScript: CAFE's services do that. Never call
eval(), unserialize() or callbacks named in settings; settings are data.