Cybo Advanced Field EngineCyboweb Technologies · Documentation · v1.0.2

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.