JetEngine Dynamic Visibility condition
Implement a condition as a deterministic predicate that follows JetEngine's show/hide contract. Do not use visibility as an authorization boundary: hidden markup, REST data, files, and mutations still need server-side access checks.
When to use this skill
- Add a condition to Dynamic Visibility in a JetEngine companion plugin.
- Diagnose reversed Show/Hide results or surprising AND/OR combinations.
- Read a listing object's post, term, user, comment, product, or custom field.
- Add condition-specific controls or a custom condition group.
- Audit render-time queries, global-state changes, or context-sensitive output.
Workflow
- Confirm JetEngine 3.8.14 and the Dynamic Visibility module are active.
- Register only on
jet-engine/modules/dynamic-visibility/conditions/register; do not instantiate the base class before JetEngine loads the module. - Give
get_id()a stable, vendor-prefixed ID. - Compute the positive match in
check(), then return its inverse forhide. - Use
get_current_value()when the condition consumes JetEngine's Field UI. - Read custom controls from
$args['condition_settings'][<control-key>]. - Keep
check()pure and cheap. Cache remote/expensive data outside the render loop with a key that includes every relevant user/object/site dimension. - Test show and hide, AND and OR, listing and non-listing context, empty values, logged-in/out users, AJAX/load-more, and repeated calls.
Minimal condition
Load the named class from the callback (or through a guarded autoloader), so its parent exists when PHP parses it.
add_action(
'jet-engine/modules/dynamic-visibility/conditions/register',
static function ($manager): void {
require_once __DIR__ . '/src/class-owned-by-current-user.php';
$manager->register_condition(new My_Plugin_Owned_By_Current_User());
}
);
use Jet_Engine\Modules\Dynamic_Visibility\Conditions\Base;
final class My_Plugin_Owned_By_Current_User extends Base {
public function get_id() {
return 'my_plugin_owned_by_current_user';
}
public function get_name() {
return __('Owned by current user', 'my-plugin');
}
public function get_group() {
return 'user';
}
public function is_for_fields() {
return false;
}
public function need_value_detect() {
return false;
}
public function check($args = array()) {
$object = jet_engine()->listings->data->get_current_object();
$match = $object instanceof WP_Post
&& (int) $object->post_author === get_current_user_id();
return 'hide' === ($args['type'] ?? 'show') ? ! $match : $match;
}
}
JetEngine's checker expects each condition to honor type. For a positive
predicate M, return M for show and ! M for hide. Do not invert again
for AND/OR; the checker combines each returned value.
Field-aware conditions
Base::get_current_value($args) resolves the current listing object:
WP_Postand exactWC_Product: post meta;WP_User: user meta;WP_Term: term meta;WP_Comment: comment meta;- other listing objects: JetEngine listing-data property;
- non-listing field context: current post meta;
- macro/dynamic Field input: JetEngine macro output.
Use adjust_values_type() for JetEngine-compatible numeric, date,
datetime, or string comparison. Treat empty, missing, 0, and '0'
explicitly; do not use empty() when zero is meaningful.
Custom controls
public function get_custom_controls() {
return array(
'my_plugin_roles' => array(
'label' => __('Allowed roles', 'my-plugin'),
'type' => 'select2',
'multiple' => true,
'default' => array(),
'options' => wp_roles()->get_names(),
),
);
}
public function check($args = array()) {
$settings = $args['condition_settings'] ?? array();
$roles = isset($settings['my_plugin_roles'])
? (array) $settings['my_plugin_roles']
: array();
$match = (bool) array_intersect($roles, wp_get_current_user()->roles);
return 'hide' === ($args['type'] ?? 'show') ? ! $match : $match;
}
Control keys are not copied to the top-level $args. Prefix them to avoid
collisions. Use a built-in group slug (general, jet-engine, user, posts,
date_time, listing) or add a label through
jet-engine/modules/dynamic-visibility/conditions/groups.
Security and reliability invariants
- Never grant access because an element is hidden or visible.
- Never change the current listing object, query globals, locale, or user from
check()without restoring state infinally. - Never emit output, redirect, mutate data, or call an unstable remote service.
- Avoid one database query per card in a Listing Grid; prefetch or cache.
- Do not bypass
jet-engine/modules/dynamic-visibility/condition/prevent-check. JetEngine 3.8.14 uses it while silently preloading listing assets. - Return a boolean on every path and choose a safe failure result deliberately.
Verification
Assert all four polarity/composition cases and run the same condition twice:
show + match => render
show + no match => suppress
hide + match => suppress
hide + no match => render
Also verify a Listing Grid with load more and a builder preview. Check query counts when the rule can appear on many cards.
Deeper implementation reference
Read implementation-reference.md when implementing field comparisons, custom groups, context handling, or tests.
References
- Official documentation: https://crocoblock.com/knowledge-base/plugins/jetengine/
- Crocoblock developer documentation: https://github.com/Crocoblock/developer-documentation/tree/main/01-jet-engine
- Verified source paths:
wp-content/plugins/jet-engine/includes/modules/dynamic-visibility/inc/conditions/base.phpwp-content/plugins/jet-engine/includes/modules/dynamic-visibility/inc/conditions/manager.phpwp-content/plugins/jet-engine/includes/modules/dynamic-visibility/inc/conditions-checker.phpwp-content/plugins/jet-engine/includes/modules/dynamic-visibility/inc/conditions/week-days.phpwp-content/plugins/jet-engine/includes/components/listings/frontend.php