Field Groups & Fields
A field group is a named set of fields. A field has a name (used in code), a label (shown to editors) and a type.
Until the visual builder ships, manage the schema in PHP with the schema manager. The same service will power the builder, the REST API and importers, so the rules below will never differ between them.
$schema = cafe()->schema_manager();
All methods return the saved object, true, or a WP_Error explaining the problem.
Field groups
$group = $schema->create_group( [
'key' => 'product_information', // Optional. Lowercase letters, digits, underscores.
'title' => 'Product Information', // Required.
'description' => 'Shown on products.',
'status' => 'active', // active | inactive | trash
'menu_order' => 0, // Lower = higher priority.
'settings' => [],
'location_rules' => [], // Where the group appears (see below).
] );
$schema->update_group( $group->id, [ 'title' => 'Product Info' ] ); // Only given keys change.
$schema->delete_group( $group->id ); // Also deletes its fields and ALL their values.
$schema->find_group( $id );
$schema->find_group_by_key( 'product_information' );
Group keys are unique. If you omit the key, one like group_3f9a1c2b7d4e5f is generated.
Fields
$field = $schema->create_field( $group->id, [
'name' => 'subtitle', // Required. Letters, digits, underscores; not starting with a digit.
'type' => 'text', // Required. A registered field type.
'label' => 'Subtitle', // Optional. Defaults to "Subtitle" from the name.
'key' => 'field_product_subtitle', // Optional, unique across all groups.
'required' => false,
'settings' => [ 'max_length' => 120 ],
'parent_id' => 0, // ID of the parent field (a Collection, Field Set …); 0 = top level.
] );
$schema->update_field( $field->id, [ 'label' => 'Product subtitle' ] );
$schema->delete_field( $field->id ); // Also deletes nested fields and ALL their values.
$schema->fields_for_group( $group->id );
Rules CAFE enforces
| Rule | Error code |
|---|---|
| Group keys are unique | cafe_duplicate_group_key |
| Field keys are unique across all groups | cafe_duplicate_field_key |
| Field names are unique among siblings | cafe_duplicate_field_name |
| The field type must be registered | cafe_unknown_field_type |
| A parent must be a container that accepts the child type | cafe_invalid_parent |
| A field cannot be nested inside itself | cafe_invalid_parent |
| A field cannot move to another group | cafe_field_group_immutable |
| A field with saved values cannot change to a type with different storage | cafe_incompatible_type_change |
| Location rules, conditional logic and settings must be well-formed | cafe_invalid_location_rules, cafe_invalid_conditional_logic, cafe_invalid_group_settings |
menu_order, sort_order, parent_id must be whole numbers |
cafe_invalid_menu_order, cafe_invalid_sort_order, cafe_invalid_parent_id |
| Unreadable stored JSON is not overwritten silently | cafe_corrupted_definition |
Fields inside tabs and collapsible sections share their parent's names, so
seo_title inside a "SEO" tab is still read with cafe_get_field( 'seo_title' ).
Location rules format
A list of OR groups; each group is a list of AND rules:
'location_rules' => [
[ // Group 1: post type is product AND taxonomy is not 7 …
[ 'rule' => 'post_type', 'operator' => '==', 'value' => 'product' ],
[ 'rule' => 'taxonomy', 'operator' => '!=', 'value' => 7 ],
],
[ // … OR group 2: the front page.
[ 'rule' => 'page_type', 'operator' => '==', 'value' => 'front_page' ],
],
],
rule: lowercase letters, digits, underscores.operator:==,!=,>,<,>=,<=,contains,not_contains,empty,not_empty.value: text (max 1000 characters), number, boolean or null.- No other keys are allowed. At most 100 groups and 100 rules per group.
Conditional logic format
A tree of AND/OR groups; conditions reference other fields by key:
'conditional_logic' => [
'relation' => 'OR',
'conditions' => [
[ 'field' => 'field_button_type', 'operator' => '==', 'value' => 'external' ],
[
'relation' => 'AND',
'conditions' => [
[ 'field' => 'field_show_cta', 'operator' => '==', 'value' => true ],
[ 'field' => 'field_cta_url', 'operator' => 'not_empty' ],
],
],
],
],
Relations are AND / OR (any case, stored uppercase). Operators and values follow the
same rules as location rules. Nesting is limited to 5 levels and 100 conditions per group.
Group settings
Group settings must be an object of named settings containing only text, numbers,
booleans, null and nested arrays (max 8 levels, 64 KB). Objects and closures are refused.
title_translations (the group title per language, e.g. {"nl": "Homepagina"}) must map
language codes to plain-text titles of at most 255 characters; see WPML.
show_description (true / false, default false) prints the group description at
the top of the group's box on edit screens. The description is plain text.
Edit screen
settings.hidden_elements lists standard parts of the post edit screen to hide
while the group is shown (post, page and custom post type screens; term and
Settings Page screens are not affected):
title, editor, permalink, featured_image, excerpt, categories, tags,
page_attributes, format, author, revisions, discussion, comments,
custom_fields
"settings": { "hidden_elements": [ "editor", "discussion", "comments" ] }
Unknown names and duplicates are refused (REST save and import). When several groups match a post, a part is hidden if any of them hides it.
Hiding never changes saved data:
| Editor | How parts are hidden |
|---|---|
| Classic | CSS only, so every input is still submitted (WordPress would close comments if the Discussion inputs were missing from a save). The matching Screen Options entries are hidden too. |
| Block | The post type supports are switched off for that page load only, and panels without a support (permalink, categories, tags) are removed with removeEditorPanel(), which is not stored as a user preference. The title is hidden with CSS, because without title support new posts are saved as "Auto Draft". |
editor hidden |
The editor support is switched off for that page load, so the post opens on the classic screen with no editor. The content is not submitted and stays exactly as saved, block markup included. |
Implementation: src/Admin/EditScreen.php.
Corrupted definitions
If a stored definition contains malformed JSON (for example after a manual database
edit), it still loads with safe defaults, and corrupted_columns lists the unreadable
columns. update_group() / update_field() return cafe_corrupted_definition until you
pass new values for those columns, so broken data is never overwritten silently.
Renaming is safe
Values are linked to the field's internal ID, not its name. Renaming subtitle to
tagline keeps every saved value, just update your templates.