Skip to content
Daniel Shaw · WordPress & WooCommerce Developer Wellington, New Zealand

👋 Not available for new client work right now!

Dynamic search filter for WordPress categories

A common scenario: you have a bunch of categories, say, Label > Artist > Track. Following is an approach for a hierarchical category search filter on your WordPress site.

The basics

Here is the requirement:

  • A form with three select elements to reflect this hierarchy:
    Label > Artist > Release
  • Choosing an option from a preceding select element dynamically populates the following select element

Category structure

The category hierarchy looks something like this:

Music > Label > Artist > Release

Music is really just a container to keep everything tidy and discrete. As the top-most parent category it will generate the contents for the first child select element, Label.

Label, Artist and Release are not actual categories but describe the type of category contained in each select element; the immediate children of the top-most Music category will be music label names, the grand-children will be musician names, and so on. For example:

  • Label: Raster Noton > Artist: Kyoka > Release: Ufunfunfufu

The key to this approach is to use wp_dropdown_categories() to generate each select element. Whenever an option is selected we can use this function's child_of argument to dynamically generate the following select element's contents.

The following will generate the first select element for categories of type Label:

wp_dropdown_categories( array( 'child_of' => 1, 'depth' => 1, 'hide_empty' => 0 'hierarchical' => 1, 'id' => 'label', 'name' => 'label', 'orderby' => 'NAME', 'show_option_all' => 'All Labels', ) );

Tip: set 'hide_empty' to 0 during development if you want to see more than empty select elements. If this argument is not set you'll need to assign categories to posts to make them visible.

Regarding the above example:

  • 'child_of' has a value of 1, the category ID of the Music category I'm using here. Use whichever method you prefer to find the top-most category ID for your set of terms.
  • Both 'depth' and 'hierarchical' keys are given a value of 1 to ensure only the immediate children are included.
  • Additional select elements for Artist- and Release-type categories can be generated in the same way, the only difference being a change in value for the 'child_of' key.

The next step is to set up an AJAX request to return contextual select elements based on user selection.

Switched at birth

Here is what needs to happen:

  • Capture the value whenever a select element changes
  • AJAX the value to a custom function
  • Return a new wp_dropdown_categories() object
  • Update the next select element

Listen / request / receive

Listening for change and making an AJAX request with jQuery:

$( document ).on( 'change', '#label', function() { var label = $( '#label' ).val(); $.ajax({ url: "", type: 'POST', data: 'action=update_label&label=' + label success: function(results) { $( '#artist' ) .replaceWith( results ); } }); });

If you're not overly familiar with WordPress, the URL set in the request above points to WordPress' native implementation of AJAX. Even though it's called admin-ajax it can be used for both back- and front-end requests.

Update the child

The AJAX request posts a value to a custom function which will construct a new child select element. The function is very simple, as simple as setting the passed Label value as the value of child_of:

function label_filter() { if ( isset( $_POST['label'] ) ) { $is_label = $_POST['label']; wp_dropdown_categories( array( 'name' => 'artist', 'id' => 'artist', 'orderby' => 'NAME', 'hierarchical' => 1, 'child_of' => $is_label, 'depth' => 1, 'show_option_all' => 'Select...' ) ); } die(); } add_action( 'wp_ajax_update_label', 'label_filter' ); add_action( 'wp_ajax_nopriv_update_label', 'label_filter' );

Each action uses the native wp_ajax_ action hook to trigger the success attribute of the request. Looking back at the data attribute of the request…

data:'action=update_label&label=' + label

… you'll notice the value of the action attribute—update_label—is appended to wp_ajax_. The first time I worked with this hook I didn't follow this convention and consequently never received a success value.

The other thing to note is the fact there is two actions: one is for logged-in users, the other for non-logged-in users.

At this point the basic moving parts for the search filter are in place—sans sanity and security checks. There's also need for additional logic to initially disable all select children, and enable a child whenever its immediate parent changes.