A WordPress plugin that connects Etch's native Query Loop block to two external query systems: JetSmartFilters and JetEngine Query Builder. Use either, both, or none — each bridge runs independently.
The plugin ships independent bridges:
- JSF bridge — registers an
Etch Loopcontent provider for JetSmartFilters. Filter, pagination, and sort blocks drive the Etch loop with AJAX, with full support for JetEngine Custom Meta Tables (filtering, sorting, indexer counts, dynamic range bounds, live recalculation), an author-controlled empty-results state, and a live[jsf_etch_count]shortcode. - JetEngine Query Builder bridge — lets a JE Query Builder query become the data source for an Etch Query Loop. The Etch preset's args are wholesale-replaced by the JE query, so the loop gets JE's full filter pipeline (including JetEngine's Custom Meta Tables and dynamic args).
- JE Data Store Button inside Etch loops (v1.2.0+) — drop the JetEngine Data Store Button block (favourites, recently-viewed, comparison) into any Etch loop card and each button automatically targets the correct loop item. No wrapper class needed.
Both bridges can layer on the same wrapper. JE provides the base query, JSF stacks user filters on top.
- Etch Loop as a JSF content provider — initial load + AJAX, pagination, sort.
- Multi-loop support per page via
jsf-etch-q-{slug}classes — each loop addressable by its own JSF Query ID. [jsf_etch_count]shortcode showing livefound_posts/max_num_pages/ current page (auto-updates on filter changes via the JSF event bus).- Per-option indexed counts on JSF filter dropdowns —
tax_queryandmeta_query, including JetEngine Custom Meta Tables. - Range filter dynamic min/max for JE Custom Meta Tables — sliders auto-resolve their bounds from the right table whether the user picks JSF's Get from Post Meta by query meta key or JE's native {Post Type}: Get from custom storage by query meta key in the filter dropdown.
- Range filter live recalculation (JSF 3.8.0+) — slider bounds re-resolve against the current filter context as the user changes other filters, scoped so a slider's own clause is excluded from its own bounds (so it can always widen back from a narrow selection).
- Author-controlled empty-results state — drop any Etch element (heading, card, CTA, dynamic data block) on the page with class
jsf-etch-empty-stateand the bridge auto-shows it when an AJAX filter yields zero results, hides it otherwise. Plus anis-emptyCSS hook on the loop wrapper for one-line CSS solutions. - Fast in-process AJAX render — no full-page HTTP loopback; mirrors JetEngine's listing-grid speed.
- Any JetEngine Query Builder query becomes the data source for an Etch Query Loop — by ID or slug.
- Supported types: Posts, Users, Terms, Merged Query, SQL Query, Data Stores Query.
- SQL queries auto-extract IDs from result rows (
WP_Post/WP_User/WP_Terminstances or rawstdClassrows withID/id/post_id/user_id/term_id). - Data Stores Query target type auto-detected from the store's post-vs-user setting.
- Wrapper class hints —
je-as-{posts|users|terms}(target-type override for SQL),je-jsf-stack(full JE result-set fetch for native pagination).
Drop the JE Data Store Button block (favourites, recently-viewed, comparison stores) into any Etch loop card and each button automatically binds to the correct loop item — frontend renders data-post="{loop-item-id}" per card with no wrapper class or extra configuration.
- Works for plain Etch posts loops, the JE Query Builder bridge above, and JSF-bridged loops.
- Works for post stores AND user stores (target follows the store's setting).
- Works for initial render and JSF AJAX re-renders.
- Other JE dynamic blocks (Dynamic Field / Image / Link) are unaffected — Etch resolves those via its own dynamic-data layer.
- Extend to third-party JE add-on blocks via the
jqbeb_loop_context_block_namesfilter.
Background: the Data Store Button resolves its target via jet_engine()->listings->data->get_current_object(), which JE updates on the the_post hook. Etch's loop block does not fire the_post (it uses its own DynamicContextProvider stack instead of setup_postdata()), so without this bridge every button on every card bound to the host page. JE's own Listing Grid was unaffected because it runs WP_Query the standard way.
When the loop's post type uses Custom Storage, the bridge routes ALL of these to the right table (none of it falls back to wp_postmeta):
- JE base
meta_queryandorderbyon CMT fields. - JSF user filtering and sorting on CMT fields.
- Filter Indexer per-option counts on CMT fields.
[jsf_etch_count]over CMT-filtered results.- Range filter dynamic min/max + live recalculation on CMT fields.
- JE provides the base query (post type, filters, custom args).
- JSF stacks user filters / pagination / sort on top.
- For Merged / SQL / Data Stores, opt in via
je-jsf-stackto enable JSF + native pagination over the JE result set.
| Plugin | Required for |
|---|---|
| Etch | Both bridges. Without Etch the plugin silently no-ops. |
| JetSmartFilters | JSF bridge only. |
| JetEngine | JE Query Builder bridge only (Custom Meta Tables and Query Builder). |
- WordPress 6.4+
- PHP 8.0+
- Download the latest release zip from Releases.
- WordPress admin → Plugins → Add New → Upload Plugin → upload zip → Activate.
- Open Settings → JSF Etch Bridge for the in-product setup guide.
Add wrapper classes to your Etch loop's immediate parent container. The most common combos:
<!-- Pure JSF, drives the loop with filters/pagination/sort -->
<div class="jsf-etch-loop">…</div>
<!-- JetEngine Query Builder query as data source -->
<div class="je-etch-loop je-q-42">…</div>
<!-- Both: JE base query + JSF user filters on top -->
<div class="jsf-etch-loop je-etch-loop je-q-partner-listings">…</div>
<!-- Multi-loop: disambiguate per loop -->
<div class="jsf-etch-loop jsf-etch-q-cars je-etch-loop je-q-cars">…</div>Reference for every wrapper class (je-as-{type}, je-jsf-stack, etc.) lives in Settings → JSF Etch Bridge under the Overview tab.
When a filter pass yields zero results, the bridge clears the loop wrapper and exposes two CSS hooks:
is-emptyis added to the.jsf-etch-loopwrapper itself. CSS-only fallback for a quick text:.jsf-etch-loop.is-empty::before { content: "No vehicles match these filters."; display: block; padding: 2rem; text-align: center; }
is-activeis added to any Etch element bearing the classjsf-etch-empty-statethat's paired with the wrapper. Drop a Container / card / CTA / dynamic-data block in the Etch builder, give it that class, and it auto-shows when the loop is empty (hidden by default via injecteddisplay:none !important).
Multi-loop pages: pair empty-states explicitly with data-for-query-id="<slug>" matching the loop's jsf-etch-q-<slug> class.
The admin page (Settings → JSF Etch Bridge) is the canonical reference. It has 5 conditional tabs (only the relevant ones for your active dependencies are shown):
- Overview — what the plugin does, dependency status, wrapper-class cheatsheet.
- JSF Bridge — setup steps,
[jsf_etch_count]shortcode, Filter Indexer, range filter dynamic min/max, empty-results state. - JE Bridge — setup steps, query-type matching, Merged / SQL / Data Stores /
je-jsf-stack. - Combined & CMT — hook ladder, full Custom Meta Tables support matrix, performance tips.
- Reference — limitations and per-topic troubleshooting.
For implementation notes that go deeper than the admin page (Etch internals, JE timing traps, hook priority ladder, JSF version-specific behaviours), see CLAUDE.md. Version history is in CHANGELOG.md.
apply_filters('jqbeb_loopback_sslverify', true)— set to__return_falsefor local-dev environments with self-signed certs.apply_filters('jqbeb_loopback_cache_enabled', true, $cache_user_id)— disable the per-user 60-second loopback cache (returnfalse) on sites with anonymous personalised content (cart, geo, A/B).apply_filters('jqbeb_range_cmt_override_enabled', true, $args, $instance)— opt out of the range filter dynamic min/max + live recalculation against JE CMT tables. Falls back to JSF's native behaviour (which yields empty bounds for CMT fields).apply_filters('jqbeb_empty_results_payload', '<!--jqbeb:empty-results-->', $inner)— substitute the sentinel comment that the bridge emits when the AJAX-rendered loop has zero results. Replace with a styled<div class="...">No vehicles match.</div>placeholder if you want a server-rendered empty-state UI instead of the JS-toggled.jsf-etch-empty-stateEtch element approach.
GPL-2.0-or-later. See plugin header for details.
Branislav Budzák