Groundworx Query Filters: Native Filtering Architecture for the WordPress Block Editor

Home / Resources / Groundworx Query Filters: Native Filtering Architecture for the WordPress Block Editor
Groundworx Query Filters

Most filter plugins are monolithic. WordPress moved to modular.

The WordPress Block Editor introduced a modular content architecture. Blocks are independent, composable units that respect a shared design system through theme.json, communicate through block context, and integrate into a template hierarchy that WordPress manages. Query Loop is part of that system. Pagination is part of that system. Post counts, “no results” templates, and template inheritance are all part of that system.

Most filter plugins were designed before this shift. They operate as self-contained systems with their own query engines, their own pagination, their own markup, and their own settings pages. They work alongside WordPress rather than within it. The result is a monolithic layer sitting on top of a platform that has moved in the opposite direction.

Groundworx Query Filters was designed from the ground up for the modular era. Every filter is a block. Taxonomy filters, text search, sorting, price ranges, active filter display — each is an independent block that can be placed inside any layout structure. Groups, columns, rows, any container block works. Styling flows from theme.json. Configuration lives in the block sidebar. There is no settings page, no shortcodes, and no configuration outside the editor.

Clients can edit labels, reorder sort options, toggle display modes, and adjust responsive behavior, all within the same editor they use for everything else. For agencies handing off sites, the learning curve is zero because the interface is the one their clients already know.

Every filter ships with the same design system. Your theme controls how it looks.

Filter plugins typically ship their own UI components with their own styling conventions. A dropdown looks one way, a checkbox list looks another, a price slider looks like it came from a different product entirely. Matching these to a theme means overriding dozens of CSS rules per component, and hoping the next plugin update doesn’t break your overrides.

Query Filters takes a different approach. Every interactive element renders through a shared form design system built on CSS custom properties. The native <select>, the custom dropdown trigger, checkboxes, radio buttons, range sliders — they all draw from the same set of variables for borders, padding, backgrounds, focus states, and typography. When the dropdown is closed, a custom select is visually indistinguishable from a native select. Same border, same padding, same chevron, same focus ring. This was a deliberate decision. The custom select exists because native <select> elements can’t support multi-select checkboxes, searchable option lists, hierarchical term display, or full keyboard navigation with ARIA announcements. But introducing a richer component shouldn’t mean introducing visual inconsistency. The custom select trigger renders through the same styling primitives as the native select, so switching a filter from native to custom — or placing both on the same page — never breaks the visual language. The difference only appears when the user interacts with it and gets the richer behavior.

WordPress supports some form input styling through theme.json, but coverage is incomplete. Checkbox and radio styling, select arrow customization, focus ring colors, and consistent sizing across input types still require manual CSS with no unified control surface. Groundworx Foundation (available free) fills that gap. It provides a visual UI for global form styling: input field colors and sizing, border width and radius with unit selectors, select dropdown arrow color, checkbox and radio check colors, focus ring color, progress bar styling, and label treatments. Every value is a CSS custom property. Change the focus color once, and every input on the site updates — contact forms, search fields, login forms, and every Query Filters component.

Query Filters inherits these values automatically. There is no separate styling step for filters. If a site already uses Foundation for consistent form styling, the filters match from the moment they’re placed. If a client adjusts the border radius in Foundation’s UI, the filter dropdowns, checkboxes, and range sliders update together with every other form element on the site.

The design system extends to every display mode. A taxonomy filter can render as a native <select>, a custom select dropdown, an inline list, or a stacked vertical list. Each mode uses the same underlying form primitives, so the visual language stays consistent regardless of which layout a designer or client chooses. The sort dropdown, the stock status filter, the rating filter — they all support the same four display modes, because they all use the same component layer.

Display decisions belong to the person building the page, not the developer who wrote the plugin.

Every filter block exposes its display configuration in the block sidebar. There is no settings page, no global configuration, no code required.

Label position can be hidden, stacked left, stacked center, stacked right, or inline-start — per block, not globally. A horizontal filter bar might use inline labels while a sidebar layout uses stacked. The same filter block, different placement, different label treatment.

Selection type is a per-block choice between single and multiple. Category as single-select with radio buttons and an “All” option. Tags as multi-select with checkboxes and a “Select options” trigger. Same underlying filter-terms block, different behavior configured in the sidebar.

Input UI offers four modes: native select for the lightest output, custom select for the richer dropdown with keyboard navigation and ARIA support, stacked for an always-visible vertical list, and inline for a horizontal option row. The decision is made per block, so a filter bar can mix a custom select dropdown for categories with an inline list for tags and a native select for sorting.

Responsive layout switching lets a stacked or inline layout collapse to a custom select dropdown at a configurable breakpoint. On desktop, users see all options laid out. On mobile, the same filter becomes a compact dropdown. One block, two layout modes, no duplicate markup. The transition preserves keyboard navigation and focus management because both modes use the same interactive component.

WooCommerce-specific display options follow the same pattern. The price filter offers slider or predefined tiers — configurable in the sidebar with an editable tier list. Stock status supports single or multiple selection. Rating can show visual star glyphs or plain text. The on-sale filter offers checkbox or toggle. Every option is a sidebar control, visible to anyone with editor access.

Sort options use human-readable labels by default — “Newest to oldest,” “A → Z” — not database column names. Labels are editable in the sidebar. Options can be reordered or removed. When a developer registers custom sort options via PHP, they appear automatically in the editor with appropriate labels.

Taxonomy variations are automatic. The plugin detects every public taxonomy registered on the site and generates a block variation for each one. The inserter shows “Filter – Category,” “Filter – Brand,” “Filter – Series” as separate blocks. No configuration, no registration step. Register a custom taxonomy and its filter block exists immediately.

The cumulative effect is that agencies can hand off filter configuration entirely. Clients adjust labels, swap display modes, toggle responsive behavior, and reorder sort options in the same editor they use for everything else. No developer ticket required for “can we change the category filter from a dropdown to a checkbox list.”

Extending Query Loop was the best architectural decision we made.

The central design decision behind Query Filters was to extend Query Loop rather than replace it. Filters modify the parameters of the query that Query Loop is already running. They don’t build a parallel query system. They don’t register their own handlers. They don’t output their own pagination.

This decision shaped everything that followed, and it paid off in ways that compound across every project.

Pagination works because it’s still Query Loop’s query being paginated. Template context is preserved because the main query is intact. Theme styles apply because the output is standard block markup rendered through standard templates. Other plugins that hook into WordPress’s query pipeline continue to function because the query they’re modifying is still the one producing results.

Server-side rendering stays the single source of truth. There is no client-side data store of posts, no JavaScript query engine running in the browser. When filters are applied, the Interactivity API router fetches the filtered page from the server and patches the DOM. The server renders results using your templates. Caching layers work normally. SEO output is consistent whether the page loads fresh or is filtered dynamically.

Filtered URLs are shareable and bookmarkable. Every filter state is represented as URL parameters. Users can share a filtered view, bookmark it, or hit the back button and return to their previous state.

The architectural risk is low because the plugin’s surface area is small. It uses the hooks WordPress provides for exactly this purpose. If WordPress changes how Query Loop works internally, the integration points remain stable because they’re the same ones WordPress itself relies on.

Why extend Query Loop? Two filterable queries on one page, each with their own pagination.

WordPress offers two ways to use Query Loop, and most developers don’t realize how powerful the second one is.

Inherit mode is the familiar one. It powers archive templates. The Query Loop inherits the main query that WordPress is already running for that page. Filtering in this mode modifies the main query before it executes, which means every block on the page that reads from the main query — pagination, post counts, “no results” — automatically reflects the filtered state.

Custom query mode is where things get interesting. Each Query Loop runs its own independent query with its own parameters. This means you can build a page with a filterable blog section on top and a filterable tutorials section below, each with their own filters, their own pagination, and their own results — completely independent of each other.

URL parameters are namespaced per query, so filtering posts in one section doesn’t affect tutorials in the other. Each filter group knows which Query Loop it belongs to. There are no conflicts, no shared state, no interference.

Supporting both modes properly was one of the harder engineering challenges. They operate through different WordPress hooks, handle URL parameters differently, and have different constraints around what can be configured. Every filter block detects the mode automatically and adapts. Developers don’t choose a mode or configure a setting. They place a Query Loop, place filters nearby, and the system figures out the rest.

The practical benefit for teams is significant. The same filter blocks, the same editor experience, and the same patterns work on archive templates and custom page layouts alike. You don’t build separate solutions for different use cases.

Filtering without page reloads is expected in modern interfaces.

Users expect instant feedback when they interact with filters. Selecting a category, typing a search term, changing the sort order — the results should update without a full page reload.

The common approach is to build a client-side query engine: fetch posts via an API, render them in JavaScript, manage state in the browser. This works, but it creates two rendering paths. The server renders the initial page one way, JavaScript renders filtered results another way. Templates diverge. Caching becomes complicated. SEO requires separate handling.

Query Filters avoids this entirely. When a filter changes, the plugin constructs a URL with updated query parameters and uses the WordPress Interactivity API router to navigate. The router fetches the new page from the server — rendered by your actual templates — and patches the DOM with the changes. Scroll position is preserved. Form state is synchronized across all filter inputs on the page.

There is one rendering path. The server renders everything. The Interactivity API handles the transition. What the user sees when they filter is exactly what they’d see if they loaded that URL directly. This means caching works, CDN edge caching works, and crawlers see the same content users see.

Native integration means every core block WordPress built for Query Loop works with your filters out of the box.

WordPress already built blocks designed to work with Query Loop. Pagination handles page navigation. Query Total displays the result count. Query No Results shows a message when the query returns nothing. These blocks read from the query and render accordingly.

Because Query Filters extends the query rather than replacing it, all of these blocks work with filtered results without any modification. Apply a filter and the pagination updates to reflect the new result count. Filter down to zero results and the No Results block appears. The total count adjusts in real time.

This also applies to term counts, though that required solving a real limitation in WordPress’s data model. WordPress stores a single count per taxonomy term across all post types. A “News” category with 50 blog posts and 10 products shows “News (60)” everywhere, even on a product catalog. There is no core API for per-post-type term counts.

We built a custom database table to solve this within our filter blocks. The table stores pre-calculated counts per term per post type, maintained automatically through hooks on term and post relationship changes. When you’re filtering products, the counts in the filter reflect products. When you’re filtering posts, the counts reflect posts. The numbers always match what the user will actually get. There’s no manual rebuild, no scheduled task, no performance hit on page load — the counts are already calculated when the page renders. This applies to our filter blocks specifically; WordPress core blocks like the category list or tag cloud still display the global count.

For hierarchical taxonomies, parent term counts include descendants, which is the behavior users expect when selecting a broad category.

Every filter option should lead to results.

The worst filtering experience is the dead end. A user selects a category, sees a long list of tags, picks one that looks relevant, and gets an empty results page. They’ve learned nothing except that your filter doesn’t work.

Contextual terms solve this. When enabled, each filter dynamically scopes its available options to the current query context. Select “Electronics” from a category filter, and the tag filter updates to show only tags that exist on electronics products. Options that would return zero results are hidden.

Counts update contextually too. The number next to each term shows how many results that specific filter combination will produce, not a global total from across unrelated content. Users can make informed decisions before they click.

The trade-off is an additional query per contextual filter. For most sites this is negligible. For high-traffic catalogs with large taxonomies, it’s a conscious architectural decision weighed against the very real cost of users hitting dead ends and leaving.

Search should never return an empty page.

The text search filter includes autocomplete designed around one principle: every suggestion should lead to results.

Word completion builds a vocabulary index from your content and understands which words follow which. Suggestions are contextually aware, not just alphabetical prefix matches. If “responsive design” is a common phrase in your content, typing “responsive ” suggests “design” rather than unrelated words that happen to start with the next letter.

Post title matching turns search into direct navigation. Users type a few characters, see matching post titles, and click to go directly to the content they’re looking for. No results page needed.

Both modes respect active filters. If a user has already selected a category, suggestions are scoped to that category. A search autocomplete that ignores what the user has already filtered is a search autocomplete that wastes their time.

The vocabulary index is cached and invalidated when content changes. The autocomplete UI follows the full WAI combobox accessibility specification, including keyboard navigation and screen reader announcements.

Extending sorting was challenging, but we made it work.

Sorting seems like a simple feature. A dropdown with a few options. In practice, it’s one of the most architecturally complex areas of the plugin because of how WordPress handles sort configuration differently depending on the query mode.

In custom query mode, the Query Loop block owns the default sort. It exposes a fixed set of sort values — date, title, menu order — that are validated through the WordPress REST API. This list cannot be extended through the block editor. If you need custom sorting (by a meta field, by popularity, by a multi-field ranking), those options can only be applied as user-selected overrides through URL parameters. The default must be a value WordPress already recognizes.

In inherit mode, there is no Query Loop block enforcing sort validation. The plugin manages the default itself and applies it directly to the main query. This means inherit mode can support custom sort defaults that would be rejected in custom query mode.

Two different constraint models required two separate default-handling systems, two ways of detecting and applying sort options, and a validation layer that ensures the editor exposes only what’s valid for the current context.

For developers who need custom sort logic, the plugin provides a PHP filter for registering sort options per post type. Simple field sorting, meta value sorting with type casting, multi-field ordering, and callback-based sorting for complex scenarios are all supported. Options appear automatically in the editor when the post type matches.

The editor handles compatibility automatically. Change the post type and irrelevant sort options are removed. Copy a Query Loop between templates and invalid configurations clean themselves up. The goal is that the sorting interface always reflects what’s actually possible, without the developer needing to manage edge cases manually.

A better experience for WooCommerce filtering.

Products have attributes that taxonomies can’t represent. Price is a number, not a category. Stock status is a meta field with three possible values. Sale status is a boolean flag. Star ratings are aggregated from reviews.

Query Filters includes four dedicated blocks for WooCommerce: price range, on-sale toggle, star rating, and stock status. Each operates through meta queries rather than taxonomy queries, because that’s how WooCommerce stores this data.

The price filter supports two interfaces depending on what fits the catalog: a dual-handle range slider for continuous price selection, or predefined price tiers with customizable labels (Under $25, $25–$50, $50–$100). Both produce the same underlying query.

All WooCommerce filter blocks integrate with the active filters display. Users see every applied filter — taxonomy selections, search terms, price ranges, stock status — in one place, with the ability to remove any of them individually. The experience is unified regardless of whether the filter targets a taxonomy or a meta field.

Accessibility is not a feature. It’s a requirement.

Accessibility in Query Filters is not a layer added after the filtering logic was built. It’s embedded in the rendering architecture from the start.

Every dropdown outputs consistent ARIA attributes, keyboard navigation handlers, and focus management. Arrow keys navigate options. Escape closes dropdowns and restores focus. Screen readers announce state changes. The autocomplete search follows the full WAI combobox specification with live region announcements.

Responsive layout switching is handled accessibly. When a filter transitions from inline layout to dropdown at a breakpoint, the interactive pattern updates without breaking assistive technology. Elements that shouldn’t receive focus are properly inert.

Filter blocks generate unique IDs that account for multiple Query Loops on the same page, ensuring label associations and ARIA references always point to the correct elements, even in complex multi-filter layouts.

For teams building sites that need to meet WCAG standards — government, healthcare, education, enterprise — accessibility is built into how every filter renders by default. Not a configuration option, not an afterthought.

Aligned with WordPress core. Tap into capabilities that already exist.

Groundworx Query Filters is built on the hooks, APIs, and design systems that WordPress provides and actively maintains. The Block Editor for content architecture. The Interactivity API for client-side behavior. The theme.json specification for design tokens. The query modification filters that WordPress ships for exactly this kind of extension.

This alignment is a deliberate architectural choice. It means the plugin benefits from every improvement WordPress makes to these systems. As the Interactivity API matures, as Query Loop gains new capabilities, as theme.json expands its scope — Query Filters inherits those improvements because it’s built on the same foundation.

For teams evaluating filtering solutions, the question isn’t just whether a plugin works today. It’s whether its architecture is positioned to keep working as WordPress evolves. A system built on WordPress’s own standards carries less risk than one built around them.

The documentation, demos, and full block reference are available at groundworx.dev/products/query-filters.

Let’s solve what’s holding you back.

Ready to Build Better WordPress Sites?

Join agencies and freelancers who’ve stopped fighting with page builders