Using a custom WP_Query
FacetWP works with custom WP queries, as long as you point FacetWP to that query with a special query argument, as shown below.
How to create a custom query
A custom query exists of the definition of the query in the query arguments, and the query loop. Both need to be present on your page for your posts/results to show up.
See the section below for where to place your custom query.
Define the query arguments and enable FacetWP
To run the query with new WP_Query( $args );
, you first need to define the query arguments.
The arguments determine which posts need to be fetched from the database, how many per page, and how they are ordered:
How to use custom PHP code?
PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info
// Define the query arguments $args = [ "post_type" => ["event"], "posts_per_page" => 10, "orderby" => ["title" => "ASC"], // ... your arguments "facetwp" => true //This flags this custom query as the one to be used by FacetWP ]; // Run the query $my_query = new WP_Query( $args );
See WordPress’ WP_Query documentation for all query arguments you can use in the query arguments. If you have trouble writing your query arguments, this WP_Query generator could be useful. And if you need to filter or sort your query by one or more custom fields, see our tutorial about that.
For FacetWP to work with this custom query, the facetwp => true
argument needs to be added. FacetWP uses this special argument to automatically detect this query as the main query to use for filtering. Without it, facets will not show up.
Create the query loop
A basic loop example using the above custom query would look like this:
How to use custom PHP code?
PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info
if ( $my_query->have_posts() ) : while ( $my_query->have_posts() ) : $my_query->the_post(); ?> <h1><?php the_title(); ?></h1> <div><?php the_content(); ?></div> <?php endwhile; else : ?> <p><?php _e( 'Sorry, no posts matched your criteria.' ); ?></p> <?php endif; wp_reset_postdata(); // get_footer(); // Make sure to include footer.php in your template, and make sure the wp_footer(); function is present in it.
Notice how $my_query
is named the same as in $my_query = new WP_Query( $args );
in above snippet.
A “no results” message (translatable by using _e()
) can be defined within the else
statement, as shown on line 11.
Prevent empty facets
If you’re building your template from scratch, make sure that the wp_footer() function is present in your (child) theme’s footer section. The footer section is usually a file named footer.php
. If this function is not there, FacetWP’s JavaScript and CSS assets cannot load in the footer, resulting in empty facets. Also code snippets using the “wp_footer” action will not work. If wp_footer();
is not present in your footer.php
(or your main template, if it does not use a separate footer include), you can add it manually by adding <?php wp_footer(); ?>
to the file, right before </body>
. And of course, make sure footer.php
is actually included in the template used, usually with WP’s get_footer() function.
Solving issues with FacetWP’s automatic post loop detection
Most of the time, after placing the custom query and some facets on a single page/post (not on an archive), the above template will just work: FacetWP will automatically detect the custom query and the post loop that it needs to dynamically change when filtering with facets.
However, there are circumstances in which FacetWP’s automatic detection fails, resulting in the facets and listing not being updated.
How it works
The above facetwp => true
query argument flags the custom query as the main query to use for filtering.
Using WP’s loop_start hook, Facetwp identifies the post loop that uses this query, and a <!--fwp-loop-->
HTML comment is placed above the post loop in the template.
Next, FacetWP’s front-end JavaScript looks for that HTML comment and automatically adds a facetwp-template
class on its direct parent HTML element, unless there is already another facetwp-template
class present on the page. FacetWP needs this class to dynamically replace all HTML within this container with the AJAX refresh that happens when facets are used.
When the facetwp-template
class ends up missing because one of these steps goes wrong, the solution is to add it manually.
Note that this is also the fix for issues with some caching and optimization plugins (like Cloudflare) that minify HTML. HTML minification removes HTML comments, including the <!--fwp-loop-->
comment needed by FacetWP.
Fix the loop detection
When the above described automatic query/post-loop detection fails (which can happen for a myriad of reasons), the facetwp-template
class will not be not placed on the post-loop parent element. Without it, FacetWP does not know which content to dynamically replace, the facets will not work and the listing will not get updated when filtering.
The solution is easy: manually place the facetwp-template
class on an element that (directly) surrounds the post loop, in the template file that contains the loop:
How to use custom PHP code?
PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info
?> <div class="facetwp-template"> <?php if ( $my_query->have_posts() ) : while ( $my_query->have_posts() ) : $my_query->the_post(); ?> <h1><?php the_title(); ?></h1> <div><?php the_content(); ?></div> <?php endwhile; else : _e( 'Sorry, no posts matched your criteria.' ); endif; wp_reset_postdata(); ?> </div> <?php // get_footer(); // Make sure to include footer.php in your template, and make sure the wp_footer(); function is present in it.
As can be seen in the above code, the facetwp-template
class needs to be placed on a container element (usually a <div>
) that surrounds the loop code, preferably on the direct parent element. If there is no container element, you can add one yourself.
Where to place the custom query?
It’s recommended to place your custom query on a single page or post, and not on a WP archive page. If you do place it on an archive page, keep the following in mind:
Using a custom WP_Query on a WP archive
We often see developers using a custom WP_Query
in WP archive templates, in order to replace WP’s default archive query (or to change its arguments, like the number of posts per page). The downside of this approach is that the database is getting queried twice, adding unnecessary overhead. It also breaks plugins that rely on WP’s default archive query.
A more efficient approach is to modify the already existing archive query itself, with a pre_get_posts filter, instead of using a separate custom WP_Query
.
However, if you have a custom WP_Query
on a WP archive page, and you don’t want or cannot move the custom WP_Query
from the WP archive, you have to tell FacetWP explicitly which query (not) to use.
FacetWP has built-in query detection that determines which query on the page is the main query to use for filtering. On WP archive pages, FacetWP by default will always prioritize the archive query ahead of any other query on the page, including the custom WP_Query
you placed on that page. This is the reason why a custom query on a WP archive will lead to unexpected results: FacetWP is using another query than the one defined in the custom query.
Using the facetwp_is_main_query hook, it is possible to force FacetWP to ignore the archive query, and use the custom query instead. Add the following code to your (child) theme’s functions.php to do this:
How to use custom PHP code?
PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info
add_filter( 'facetwp_is_main_query', function( $is_main_query, $query ) { if ( $query->is_archive() && $query->is_main_query() ) { $is_main_query = false; } return $is_main_query; }, 10, 2 );