Cybo Advanced Field EngineCyboweb Technologies · Documentation · v1.0.2

Hooks & Filters

CAFE uses standard WordPress actions and filters. Every hook starts with cafe_. Hooks that concern a value always receive the object ID and the object type ('post', 'term', 'user', 'comment', 'option') as their last two arguments.

Hooks from before the rename. Until 0.15.0 the plugin was called Advanced Field Core (AFC) and these hooks started with afc_. Since 0.16.0 only the cafe_ names run: rename afc_ hooks in your code (add_filter( 'afc_field_value', … ) becomes add_filter( 'cafe_field_value', … )).

$field arguments are Cybo\AdvancedFieldEngine\Fields\Field objects. They work like arrays too: $field['name'], $field['label'], $field['type'], and settings such as $field['max_length'].

Order of events

Reading (cafe_get_field()):

  1. Stored value, or the field's Default Value
  2. Field type formatting
  3. cafe_field_value_type_{type} → cafe_field_value → cafe_field_value_{name}

Saving (cafe_update_field()):

  1. Field type validation (+ Required when enforced)
  2. cafe_validate_field
  3. Field type sanitization
  4. cafe_pre_save_value
  5. cafe_before_save_field
  6. Stored in the database (in a transaction), cache cleared
  7. cafe_after_save_field

Filters

cafe_field_value

Filters every value returned by cafe_get_field(), cafe_get_fields() and cafe_get_option().

apply_filters( 'cafe_field_value', mixed $value, Field $field, int $object_id, string $object_type );
add_filter( 'cafe_field_value', function ( $value, $field, $object_id, $object_type ) {
	if ( 'text' === $field['type'] && is_string( $value ) ) {
		return trim( $value );
	}
	return $value;
}, 10, 4 );

cafe_field_value_{name}

Same as cafe_field_value, only for one field name. Runs last.

add_filter(
	'cafe_field_value_price',
	function ( $value ) {
		return number_format( (float) $value, 2 );
	}
);

cafe_field_value_type_{type}

Same as cafe_field_value, only for one field type (e.g. cafe_field_value_type_text). Runs first.

The $default_value you pass to cafe_get_field() is returned as-is and is not filtered. A field's own Default Value setting is filtered.

cafe_validate_field

Accept or reject a value before it is saved. Receives the raw (unsanitized) value.

apply_filters( 'cafe_validate_field', true|WP_Error $valid, Field $field, mixed $value, int $object_id, string $object_type );

Return true to accept, or a WP_Error / an error message string to reject.

add_filter( 'cafe_validate_field', function ( $valid, $field, $value ) {
	if ( true === $valid && 'price' === $field['name'] && (float) $value < 0 ) {
		return new WP_Error( 'negative_price', 'Prices cannot be negative.' );
	}
	return $valid;
}, 10, 3 );

cafe_pre_save_value

Change a sanitized value right before it is stored.

apply_filters( 'cafe_pre_save_value', mixed $value, Field $field, int $object_id, string $object_type );
add_filter( 'cafe_pre_save_value', function ( $value, $field ) {
	return 'sku' === $field['name'] ? strtoupper( $value ) : $value;
}, 10, 2 );

cafe_capability

The capability required to manage CAFE: field groups, fields, settings, import/export and tools. Default manage_options. Invalid values fall back to the default. Use it together with cafe()->capabilities()->can_manage( $user_id ) in your own entry points.

apply_filters( 'cafe_capability', string $capability );
// Let editors manage field groups.
add_filter( 'cafe_capability', fn () => 'edit_others_posts' );

cafe_video_providers

Known video providers, detected from the host of external Video URLs. Subdomains match too (www.youtube.com → youtube).

apply_filters( 'cafe_video_providers', array $providers ); // [ 'youtube' => [ 'youtube.com', 'youtu.be', … ], 'vimeo' => [ 'vimeo.com' ] ]
add_filter( 'cafe_video_providers', function ( $providers ) {
	$providers['wistia'] = [ 'wistia.com', 'wistia.net' ];
	return $providers;
} );

cafe_auto_prime

Filters whether CAFE batches the objects of a query result (WP_Query posts and get_terms() terms) for bulk loading. Return false to load each object's fields on its own. See Performance.

add_filter( 'cafe_auto_prime', function ( bool $enabled, string $object_type, array $ids ) {
	return 'term' === $object_type ? false : $enabled;
}, 10, 3 );

cafe_value_context

Filters the object a top-level field's value is read from or written to. The multilingual integration uses it to read "Copy" fields of a translation from the original, and Settings Page values from the current language's scope.

apply_filters( 'cafe_value_context', Cybo\AdvancedFieldEngine\Values\ObjectContext $context, Cybo\AdvancedFieldEngine\Fields\Field $field, string $operation ); // "read" or "write"

cafe_sub_value_source

Filters the object and row a sub-field's value is read from. The multilingual integration points a sub-field set to "Copy (locked)" at the same item of the original (same position, same section kind). A row below 0 reads as empty.

apply_filters( 'cafe_sub_value_source', ?array $source, Cybo\AdvancedFieldEngine\Fields\Field $field, Cybo\AdvancedFieldEngine\Values\ObjectContext $context, int $row_id ); // [ ObjectContext, int $row_id ] or null

cafe_field_view

Filters how a top-level field appears on an edit screen: state ("editable", "readonly" or "hidden"), context (object the shown value comes from) and note (text shown above a read-only field). Read-only and hidden fields are never saved from the form.

apply_filters( 'cafe_field_view', array $view, Cybo\AdvancedFieldEngine\Fields\Field $field, Cybo\AdvancedFieldEngine\Values\ObjectContext $context );

cafe_prime_object_ids

Filters the objects loaded together in one bulk-loading batch. Integrations add objects the batch reads from (e.g. the originals of translations) so they load in the same queries.

apply_filters( 'cafe_prime_object_ids', array $object_ids, string $object_type );

cafe_display_object_id

Filters the ID of an object shown by a field: attachments (kind attachment), and posts and terms of reference fields (kind = the post type or taxonomy). The WPML integration swaps them for their translation in the current language. The stored ID never changes.

apply_filters( 'cafe_display_object_id', int $id, string $kind ); // "attachment", "page", "category"…

cafe_google_maps_api_key

Filters the Google Maps API key used by map pickers of Google Map fields on edit screens (default: the key in CAFE → Settings; '' = manual entry). Keys must match [A-Za-z0-9_-]{20,100}. Front-end pages never call Google.

add_filter( 'cafe_google_maps_api_key', static fn () => MY_MAPS_BROWSER_KEY );

cafe_prime_references

Fires after the posts, terms and users referenced by the reference fields of a batch (a loop, a REST collection, cafe_prime_fields()) were loaded: one WordPress call per object type. Integrations prepare their own data for all of them at once; the WPML integration loads the translations of those posts and terms here.

do_action( 'cafe_prime_references', array $by_type ); // [ 'post' => [ 12, 15 ], 'term' => [ 3 ], 'user' => [ 1 ] ]

cafe_language_provider

Filters the language provider CAFE uses: WPML's when detected, otherwise null. Return an Cybo\AdvancedFieldEngine\Multilingual\LanguageProviderInterface to connect another multilingual plugin. See WPML.

apply_filters( 'cafe_language_provider', ?Cybo\AdvancedFieldEngine\Multilingual\LanguageProviderInterface $provider );

cafe_field_label

Filters a field label shown on edit screens (WPML String Translation uses it). The field name never changes.

apply_filters( 'cafe_field_label', string $label, Cybo\AdvancedFieldEngine\Fields\Field $field );

cafe_field_instructions

Filters the instructions shown with a field on edit screens.

apply_filters( 'cafe_field_instructions', string $instructions, Cybo\AdvancedFieldEngine\Fields\Field $field );

cafe_field_group_title

Filters a field group title shown on edit screens and Settings Pages.

apply_filters( 'cafe_field_group_title', string $title, Cybo\AdvancedFieldEngine\Fields\FieldGroup $group );

cafe_settings

Filters CAFE's effective settings (for example to force a value per environment).

apply_filters( 'cafe_settings', array $settings );

cafe_edit_screen_css

Adds your own CSS after CAFE's styles on post, page and term edit screens. HTML tags are stripped, so only CSS is kept. Guide: Styling.

add_filter( 'cafe_edit_screen_css', function ( string $css ) {
	return $css . '.cafe-scope { --cafe-input-radius: 4px; }';
} );

cafe_feedback_endpoint

The address CAFE sends rating and deactivation feedback to (only when someone presses Send in the feedback form). Default: https://www.cybowebtechnologies.com/api/v1/plugin-feedback. Only https:// addresses are used; anything else makes sending fail with a friendly message. Useful for a test server.

add_filter( 'cafe_feedback_endpoint', function ( string $endpoint ) {
	return 'https://staging.example.com/api/v1/plugin-feedback';
} );

cafe_location_rules

Adds or removes location rules (the "Show this field group if…" conditions). Each rule implements Cybo\AdvancedFieldEngine\Location\LocationRuleInterface and is keyed by its name. Rules with a wrong key, an invalid name or the wrong class are ignored. Guide: Location rules.

add_filter( 'cafe_location_rules', function ( array $rules ) {
	$rules['user_role'] = new My_User_Role_Rule();
	return $rules;
} );

cafe_migration_database_tables

Filter. The database tables CAFE → Migration may read with the Table of an old system source. By default: this site's tables (with its table prefix) that are not WordPress or CAFE tables. A filter can remove tables or add other existing tables, but never WordPress or CAFE tables (password hashes and settings stay out of reach).

// Only offer the tables of the old system.
add_filter( 'cafe_migration_database_tables', function ( array $tables ) {
	return array_values( array_filter( $tables, fn ( $table ) => str_contains( $table, '_old_' ) ) );
} );

See Connector: tables of an old system.

Actions

cafe_settings_page_saved

Fires after a Settings Page was saved (nonce and capability already checked).

add_action( 'cafe_settings_page_saved', function ( string $slug, array $errors ) {
	if ( 'contact-settings' === $slug && [] === $errors ) {
		delete_transient( 'my_theme_footer_html' );
	}
}, 10, 2 );

$errors holds messages of fields that were not saved, keyed by field key.

cafe_field_groups_imported

Fires after field group definitions were imported (CAFE → Tools or cafe_import_field_groups()). Does not fire for dry runs, failed imports or files with nothing to change.

add_action( 'cafe_field_groups_imported', function ( Cybo\AdvancedFieldEngine\Tools\ImportResult $result ) {
	$summary = $result->to_array()['summary'];
	error_log( sprintf( 'CAFE import: %d fields created, %d updated', $summary['created']['fields'], $summary['updated']['fields'] ) );
} );

cafe_settings_page_header

Fires under the heading of a Settings Page, e.g. to say which language is edited.

do_action( 'cafe_settings_page_header', Cybo\AdvancedFieldEngine\SettingsPages\SettingsPage $page );

cafe_before_save_field

do_action( 'cafe_before_save_field', Field $field, mixed $value, int $object_id, string $object_type );

Fires after validation and sanitization, before the value is stored. $value is the sanitized value.

cafe_after_save_field

do_action( 'cafe_after_save_field', Field $field, mixed $value, int $object_id, string $object_type );

Fires after the value is stored and the cache is cleared: cafe_get_field() already returns the new value.

add_action( 'cafe_after_save_field', function ( $field, $value, $object_id, $object_type ) {
	if ( 'post' === $object_type ) {
		clean_post_cache( $object_id );
	}
}, 10, 4 );

cafe_before_delete_field

do_action( 'cafe_before_delete_field', Field $field, int $object_id, string $object_type );

cafe_after_delete_field

do_action( 'cafe_after_delete_field', Field $field, int $object_id, string $object_type );

cafe_corrupted_value

Fires when a stored value cannot be decoded (malformed JSON in the database). The read returns the field as empty; nothing is overwritten. Use it for logging.

do_action( 'cafe_corrupted_value', Field $field, int $object_id, string $object_type, string $error );

cafe_object_purged

Fires after CAFE removed all data of a post, term, user or comment that WordPress deleted.

do_action( 'cafe_object_purged', int $object_id, string $object_type );

cafe_field_group_saved

do_action( 'cafe_field_group_saved', FieldGroup $group, bool $created );

cafe_field_group_deleted

Fires after a field group, its fields and all their values were deleted.

do_action( 'cafe_field_group_deleted', FieldGroup $group );

cafe_schema_changed

Fires after any field group or field is created, updated or deleted. Caches are already cleared.

do_action( 'cafe_schema_changed' );

cafe_loaded

Fires once when CAFE has booted. Use it to call cafe() safely.

do_action( 'cafe_loaded', Cybo\AdvancedFieldEngine\Core\Plugin $plugin );

cafe_init

Fires on WordPress init (priority 1) once CAFE is ready.

do_action( 'cafe_init', Cybo\AdvancedFieldEngine\Core\Plugin $plugin );

cafe_register_field_types

Register custom field types (or call cafe_register_field_type()). Guide: Custom field types.

add_action( 'cafe_register_field_types', function ( $registry ) {
	$registry->register( My_Icon_Field::class );
} );

cafe_register_object_types

Register custom object types (implement Cybo\AdvancedFieldEngine\Values\ObjectTypeInterface).

do_action( 'cafe_register_object_types', Cybo\AdvancedFieldEngine\Values\ObjectTypeRegistry $registry );

cafe_register_migration_sources

Fires once, the first time CAFE → Migration needs its sources. Register your own source (an old CRM, an API export) with cafe_register_migration_source(). Guide: Custom sources.

add_action( 'cafe_register_migration_sources', function () {
	cafe_register_migration_source( new My_Crm_Source() );
} );

cafe_register_migration_transformers

Fires once, the first time migration transformations are needed. Add your own with cafe_register_migration_transformer(). Guide: Transformations.

add_action( 'cafe_register_migration_transformers', function () {
	cafe_register_migration_transformer( 'status_label', 'Old status → sale status', function ( $value ) {
		return '1' === (string) $value ? 'available' : 'sold';
	} );
} );

cafe_migrated

Fires after database migrations ran on the current site.

do_action( 'cafe_migrated', int $from_version, int $to_version );

cafe_storage_renamed

Fires once per site after the data stored under the old plugin name (Advanced Field Core: {prefix}cafe_* tables, cafe_* options, post types and meta keys) was moved to the CAFE names. See Database.

do_action( 'cafe_storage_renamed' );

cafe_hide_admin_notices

Filter. Whether CAFE admin pages hide the admin notices of other plugins and themes (for example "Welcome to …" or license banners). Default false: every notice shows. CAFE's own notices always show.

// Hide other plugins' notices on CAFE pages.
add_filter( 'cafe_hide_admin_notices', '__return_true' );

Dynamic forms

Hooks of CAFE Forms (cafe_dynamic_form()), in the order they run. $form is an Cybo\AdvancedFieldEngine\Forms\FormDefinition (read-only properties such as $form->id, $form->object_type, $form->object_id, $form->mode). More examples: Forms hooks.

cafe_dynamic_form_args

Filter. The arguments of a form before they are checked: on every render and submit. Use it to add defaults to all forms.

add_filter( 'cafe_dynamic_form_args', function ( array $args ) {
	return $args + [ 'label_placement' => 'left' ];
} );

cafe_dynamic_form_can_submit

Filter. Whether the current user may use a form (see and submit it). Receives true or a WP_Error; return either.

add_filter( 'cafe_dynamic_form_can_submit', function ( $allowed, $form, int $user_id ) {
	if ( 'new-event' === $form->id && ! get_option( 'events_open' ) ) {
		return new WP_Error( 'closed', 'Event submissions are closed.', [ 'status' => 403 ] );
	}
	return $allowed;
}, 10, 3 );

cafe_dynamic_form_fields

Filter. The fields a form shows and accepts, keyed by field key (Cybo\AdvancedFieldEngine\Fields\Field). Removing a field here removes it from the form and from what the server accepts.

apply_filters( 'cafe_dynamic_form_fields', array $fields, FormDefinition $form );

cafe_dynamic_form_before_fields

Filter. HTML inside the form, before the fields (escape it yourself).

apply_filters( 'cafe_dynamic_form_before_fields', string $html, FormDefinition $form );

cafe_dynamic_form_after_fields

Filter. HTML inside the form, after the fields and before the button.

apply_filters( 'cafe_dynamic_form_after_fields', string $html, FormDefinition $form );

cafe_dynamic_form_submit_button

Filter. The submit button HTML.

add_filter( 'cafe_dynamic_form_submit_button', function ( string $button, $form ) {
	return '<button type="submit" class="cafe-form__submit my-button">Send</button>';
}, 10, 2 );

cafe_dynamic_form_html

Filter. The whole form HTML.

apply_filters( 'cafe_dynamic_form_html', string $html, FormDefinition $form, ?FormResult $result );

cafe_dynamic_form_field_description

Filter. How one field is described to headless front ends (GET /cafe/v1/forms/{id}). Only display settings are included by default.

apply_filters( 'cafe_dynamic_form_field_description', array $item, Cybo\AdvancedFieldEngine\Fields\Field $field );

cafe_dynamic_form_before_validate

Action. Fires before a submission is validated. $values are the submitted values as plain text (sanitize_textarea_field()), not the values that are saved.

do_action( 'cafe_dynamic_form_before_validate', FormDefinition $form, array $values );

cafe_dynamic_form_validate

Filter. Validates a form as a whole: relations between fields. Receives the messages so far (keyed by field key) and the valid values keyed by field name. Add messages keyed by field name or key, or '' for a message about the whole form. Any message stops the save.

add_filter( 'cafe_dynamic_form_validate', function ( array $errors, array $values, $form ) {
	if ( 'new-event' === $form->id && ( $values['end_date'] ?? '' ) < ( $values['start_date'] ?? '' ) ) {
		$errors['end_date'] = 'The end date must be after the start date.';
	}
	return $errors;
}, 10, 3 );

cafe_dynamic_form_failed

Action. Fires when a submission was not saved (invalid, or refused while saving: everything was rolled back).

do_action( 'cafe_dynamic_form_failed', FormResult $result, FormDefinition $form );

cafe_dynamic_form_before_save

Action. Fires after validation, right before saving.

do_action( 'cafe_dynamic_form_before_save', FormDefinition $form, array $values );

cafe_dynamic_form_post_data

Filter. The WordPress post data a form writes (arguments of wp_insert_post() or wp_update_post()). $values are the submission's valid CAFE values, keyed by field name.

add_filter( 'cafe_dynamic_form_post_data', function ( array $data, $form, array $values ) {
	if ( 'new-event' === $form->id && empty( $data['post_title'] ) ) {
		$data['post_title'] = trim( ( $values['venue'] ?? '' ) . ' ' . ( $values['event_date'] ?? '' ) );
	}
	return $data;
}, 10, 3 );

cafe_dynamic_form_saved

Action. Fires after a form saved everything. $values are keyed by field key.

add_action( 'cafe_dynamic_form_saved', function ( int $object_id, $form, array $values ) {
	if ( 'new-event' === $form->id ) {
		wp_mail( get_option( 'admin_email' ), 'New event', get_edit_post_link( $object_id, 'raw' ) );
	}
}, 10, 3 );

cafe_dynamic_form_redirect

Filter. Where a form goes after saving ('' = stay on the page). The result is always checked with wp_validate_redirect().

apply_filters( 'cafe_dynamic_form_redirect', string $url, int $object_id, FormDefinition $form );