Conditional logic controls which fields are visible in the admin based on the values of other fields. This section explains how to set up conditional logic programmatically.
Conditional Logic Structure
Each field can have a conditional_logic property — an array of OR groups, where each group is an array of AND conditions.
FIELDFORGE_Field_Groups::instance()->create( [
'title' => 'Event Settings',
'fields' => [
[
'key' => 'field_evt_type',
'label' => 'Event Type',
'name' => 'event_type',
'type' => 'select',
'choices' => [ 'in_person' => 'In Person', 'virtual' => 'Virtual', 'hybrid' => 'Hybrid' ],
],
[
'key' => 'field_evt_venue',
'label' => 'Venue Address',
'name' => 'venue_address',
'type' => 'text',
'conditional_logic' => [
// Show when event_type is 'in_person' OR 'hybrid'
[ [ 'field' => 'field_evt_type', 'operator' => '==', 'value' => 'in_person' ] ],
[ [ 'field' => 'field_evt_type', 'operator' => '==', 'value' => 'hybrid' ] ],
],
],
[
'key' => 'field_evt_url',
'label' => 'Stream URL',
'name' => 'stream_url',
'type' => 'url',
'conditional_logic' => [
// Show when event_type is 'virtual' OR 'hybrid'
[ [ 'field' => 'field_evt_type', 'operator' => '==', 'value' => 'virtual' ] ],
[ [ 'field' => 'field_evt_type', 'operator' => '==', 'value' => 'hybrid' ] ],
],
],
[
'key' => 'field_evt_capacity',
'label' => 'Max Capacity',
'name' => 'max_capacity',
'type' => 'number',
'conditional_logic' => [
// Show when in_person AND has a venue address
[
[ 'field' => 'field_evt_type', 'operator' => '==', 'value' => 'in_person' ],
[ 'field' => 'field_evt_venue', 'operator' => '!=empty', 'value' => '' ],
],
],
],
],
'location_rules' => [
[ [ 'param' => 'post_type', 'operator' => '==', 'value' => 'event' ] ],
],
] );Canonical schema shape
The runtime evaluator reads the canonical shape { enabled: true, rules: [...] }. Field Forge also accepts the legacy ACF bare-array shape (just the outer rules array) and normalizes it on save:
'conditional_logic' => [
'enabled' => true,
'rules' => [ // outer = OR
[ // inner = AND
[ 'field' => 'event_type', 'operator' => '==', 'value' => 'in_person' ],
[ 'field' => 'venue', 'operator' => '!=empty', 'value' => '' ],
],
],
]The field key inside each condition matches by field name (not key). Use the same name you would pass to get_field().
Available Operators
All operators are mirrored between PHP (FIELDFORGE_Conditional_Logic::compare()) and JS (assets/js/conditional-logic.js). Keep both lists in sync if you fork.
| Operator | Description | Example |
|---|---|---|
== | Equals (loose, string-coerced; for arrays, true if expected is in the array) | 'value' => 'in_person' |
!= | Not equals | 'value' => 'draft' |
==contains | Substring match | 'value' => 'admin' |
!=contains | Inverse substring match | 'value' => 'spam' |
==pattern | Regex match. Auto-wraps with /.../ if no delimiters provided. | 'value' => '/^vd+/i' |
==empty | True when value is null, ”, ‘0’, false, or empty array | 'value' => '' |
!=empty | Inverse of ==empty | 'value' => '' |
> < >= <= | Numeric (floatval both sides) | 'value' => '50' |
Runtime evaluator
- PHP-side (
FIELDFORGE_Field_Renderer::render_field) does an initial server-side evaluation so fields whose rules are false are emitted withstyle="display:none"— no flash of all-fields on page load. - JS-side (
assets/js/conditional-logic.js) re-evaluates on everychange/inputevent inside the metabox. Each metabox carries adata-fieldforge-cond-rulesJSON map; each conditional target carriesdata-fieldforge-cond-target="". - Hidden trigger short-circuit: if a trigger field is itself hidden (by its own conditional rule), conditions on it count as false. Chains (A controls B controls C) cascade automatically — when A hides B, the JS re-runs until convergence, so C also hides.
- Required attribute: when a field hides, any descendant
[required]attribute is stashed asdata-fieldforge-cond-required="1"andrequiredis removed so the browser does not block submit. It is restored when the field becomes visible again. - Sub-field scope: v1 only evaluates rules on top-level fields (direct children of the field group). Rules placed on sub-fields inside Repeater / Group / Flexible Content are stored and round-tripped through export/import but the metabox always renders them.
Programmatic evaluation
FIELDFORGE_Conditional_Logic::evaluate( $logic, $values ) is exposed as the same evaluator the renderer uses, so headless callers can ask “would this field show given these values?” without DOM:
$logic = $field['conditional_logic']; // { enabled, rules }
$values = [
'event_type' => 'hybrid',
'venue' => '',
];
$visible = FIELDFORGE_Conditional_Logic::evaluate( $logic, $values );Conditional Logic in Templates
Conditional logic is a UI concern only — it does not affect get_field() output. A hidden field still retains its stored value. Always check for empty values in templates:
$venue = get_field( 'venue_address' );
if ( $venue ) {
echo '<p>Venue: ' . esc_html( $venue ) . '</p>';
}—