Ajax Filters
The Filters block that ships with PorterWP Premium can work in two modes:
Static (default)
The page reloads and the next Query Loop with class .use-filters receives the updated query args.
AJAX
A JavaScript helper calls Porter_Filters_Ajax_Handler so only the results list is repainted, giving users a faster, app‑like experience.
Below is a step‑by‑step guide to bolt the AJAX version onto any archive or landing page.
1 – Create an output template
Add a PHP file inside porter/inc/templates.
The file name must follow the pattern filter-template-{slug}.php – the slug is what you will reference in the block settings.
<?php
/**
* Filter template: {slug}
*
* Receives:
* - $filters (array) Current filter values keyed by parameter
* - $query (WP_Query) The query that will be (re)run
* - $attrs (array) Block attributes
*
* Return a string of HTML.
*/
defined( 'ABSPATH' ) || exit;
// Example: build a list of taxonomy terms as checkboxes.
ob_start();
$terms = get_terms( [
'taxonomy' => 'sector',
'hide_empty' => false,
] );
?>
<form class="porter-filters" data-filters-form>
<?php foreach ( $terms as $term ) : ?>
<label>
<input type="checkbox"
name="sector[]"
value="<?php echo esc_attr( $term->slug ); ?>"
<?php checked( in_array( $term->slug, $filters['sector'] ?? [], true ) ); ?> />
<?php echo esc_html( $term->name ); ?>
</label>
<?php endforeach; ?>
<button type="submit">Apply</button>
</form>
<?php
return ob_get_clean();🔍 Tip: Keep all markup self‑contained – no wrapper divs are injected by the handler.
2 – Add the JavaScript
Create a file inside assets/src/js (or wherever you keep custom scripts) – e.g. filters-{slug}.js.
/**
* Minimal AJAX driver for PorterWP Filters
* Assumes there is ONE Filters block per page.
*/
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('[data-filters-form]');
if (!form) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const params = new URLSearchParams(new FormData(form));
// Tell Porter which template to use
params.append('template', '{slug}');
const res = await fetch(window.porterFiltersAjax || '/wp-json/porterwp/v1/filter', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: params.toString(),
});
if (!res.ok) return console.error('Filters AJAX failed');
const { html } = await res.json(); // HTML string for the Query Loop
const target = document.querySelector('.use-filters');
if (target) target.innerHTML = html;
});
});Compile & watch – the existing Gulp task compileJS will copy the minified file into assets/dist/js/.
3 – Reference the template inside the block
Insert a Filters block in the editor.
In the Block › Inspector Panel › Template tail field, enter just the slug:
{slug}PorterWP concatenates this with the
filter-template-prefix and.phpsuffix to locate the file you created in step 1.Add the Query Loop block you want to reload immediately after the Filters block and give it the class
use-filters.
4 – Enqueue the script
The simplest way is via your theme’s functions.php:
add_action( 'wp_enqueue_scripts', function () {
if ( is_archive() || is_post_type_archive( 'my_cpt' ) ) {
wp_enqueue_script(
'filters-{slug}',
get_theme_file_uri( 'assets/dist/js/filters-{slug}.js' ),
[ 'wp-element' ],
null,
true
);
}
} );When the page loads, your script intercepts form submits and talks to the REST endpoint provided by Porter_Filters_Ajax_Handler.
5 – Caching & CLS considerations
The HTML fragment returned by the REST endpoint is auto‑escaped and cacheable (if you’re on
productionand object‑cache is set up).To avoid layout shift, add a min‑height to the
.use-filterscontainer in CSS.
Summary
filter-template-{slug}.php
porter/inc/templates/
Renders the filter UI; returns string.
filters-{slug}.js
assets/src/js/ → compiled to assets/dist/js/
Handles submit, calls REST, injects HTML.
Template tail
ACF field inside Filters block
Tells PorterWP which template file to load.
.use-filters Query Loop
Editor
Container that receives the refreshed markup.
That’s all – you now have lightning‑fast, infinitely chainable faceted search 🎉
Last updated