How to use sticky posts with FacetWP
FacetWP does not work well with sticky posts out of the box. This tutorial explains workarounds you can implement to ignore sticky posts in FacetWP listings entirely or partially, and how to make sticky posts work if you actually want them. The described solutions depend on the listing template type you are using.
FacetWP issues with sticky posts
You’ll encounter the following issues with FacetWP when your have sticky posts in your listing:
- Sticky posts will be prepended to the other posts in the listing. This means they will always appear at the top of the listing when the page first loads, which may be what you want. But they will also appear at the top after facet filtering, (except when using a Pager facet), even when they should not be in the filtered results. This behavior is caused by sticky posts not actually being part of the query. WordPress will add them to the top no matter what, even if FacetWP has filtered them out.
- Because of the prepended sticky posts, the number of posts per page, and the Pager facet’s results counter will be off. For example, if you have set the listing query’s
posts_per_page
argument to6
, and you have two sticky posts, you’ll see eight posts on the first page, while the results counter will show “1 – 6 of x results”. And after using facets, if those two sticky posts were not already in the results, you’ll see them prepended and not counted too.
There are a few ways to fix these issues, depending on if you want to use sticky posts in the FacetWP listing or not:
Ignore sticky posts entirely
Fortunately, WordPress has a built-in query argument that can be used to ignore sticky posts. To fix the above issues, we can just set ignore_sticky_posts
to true
for the query:
A. Ignore sticky posts on a Listing Builder listing
If you are using a Listing Builder listing, you can use the facetwp_query_args hook to do this.
The following code disables sticky posts on a specific listing, set in line 2.
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_query_args', function( $query_args, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing $query_args['ignore_sticky_posts'] = true; } return $query_args; }, 10, 2);
The condition in line 2 can also be adapted to select for a specific page. The following example uses the page URI, which is the part of the URL without the domain name and the query variables, and without beginning or ending slashes:
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_query_args', function( $query_args, $class ) { if ( 'my/page' == $class->http_params['uri'] ) { // Replace 'my/page' with your page URI $query_args['ignore_sticky_posts'] = true; } return $query_args; }, 10, 2);
B. Ignore sticky posts on a WP archive or custom WP_Query
If you are using a WP archive or a custom WP_Query, you can ignore sticky posts with WP’s pre_get_posts hook.
The following example works on the blog page:
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( 'pre_get_posts', function( $query ) { if ( $query->is_main_query() && is_home() ) { // Works on the blog page only $query->set( 'ignore_sticky_posts', true ); } }, 998, 1);
Ignore sticky posts only after refresh
To ignore sticky posts only after refresh (after using facets), you can use the following snippets. But be aware that this does not solve the result counter issues mentioned above, as the first page will still have the sticky posts prepended to the listing, and not counted. So it would be best to only use this solution if you are not using a “Result counts” type Pager facet.
A. Ignore sticky posts only after refresh on a Listing Builder listing
To ignore sticky posts only after using facets, on a Listing Builder listing:
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_query_args', function( $query_args, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing $filtered = ( FWP()->request->is_preload && !empty( FWP()->request->url_vars ) ) || ( FWP()->request->is_refresh && !empty( $_POST['data']['http_params']['get'] ) ) ? true : false; if ( $filtered ) { $query_args['ignore_sticky_posts'] = true; } } return $query_args; }, 10, 2);
Also here, you can use a condition to check the page URI instead, as shown above.
B. Ignore sticky posts only after refresh on a WP archive or custom WP_Query
To ignore sticky posts only after using facets, on a WP archive or a custom WP_Query, use the following snippet:
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( 'pre_get_posts', function( $query ) { if ( $query->is_home() && $query->is_main_query() ) { // Works on the blog page only $filtered = ( FWP()->request->is_preload && !empty( FWP()->request->url_vars ) ) || ( FWP()->request->is_refresh && !empty( $_POST['data']['http_params']['get'] ) ) ? true : false; if ( $filtered ) { $query->set( 'ignore_sticky_posts', true ); } } return $query; }, 998, 1 );
Also here, make sure to use the correct conditional tags to select the query on line 2, as explained above.
Alternative sticky posts at the top, but only when they are in the results
To let specific posts appear at the top of the listing only when they are already in the results, while preventing the above-mentioned issues and caveats of the previous solutions, you can use the facetwp_pre_filtered_post_ids or facetwp_filtered_post_ids hook, depending on the situation.
These hooks let you manipulate the bucket of post IDs after filtering. In the following example, we get the sticky posts, remove them from the list of post IDs, and add them back in at the beginning.
This solution only works if we first set the ignore_sticky_posts
query parameter to true
and order the query by post__in
.
A. Alternative sticky posts in a Listing Builder listing
In the following code examples, the first part uses the facetwp_query_args
hook to ignore sticky posts and order the query by post__in
. In these snippets, you can also use an alternative condition to check the page URI, as shown above.
The second part uses the facetwp_pre_filtered_post_ids hook to make sticky posts appear at the top, but only if they are already in the results.
Option A1: before and after filtering
To let this happen before and after filtering, also on the first page, and also on the first page load, use the following code.
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'facetwp_query_args', function( $query_args, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing $query_args['ignore_sticky_posts'] = true; $query_args['orderby'] = 'post__in'; } return $query_args; }, 10, 2 ); // Part 2: Float sticky posts to the top when they are in the results: add_filter( 'facetwp_pre_filtered_post_ids', function( $post_ids, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove the sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add the sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } return $post_ids; }, 10, 2 );
Option A2: only after filtering
If you want to do the same as above in A1, but only after using facets (so not on the first page load), use the following code instead. It will make sticky posts appear at the top, if they are already in the results, when using any facet (except Pager facets and Sort facets):
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'facetwp_query_args', function( $query_args, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing $query_args['ignore_sticky_posts'] = true; $query_args['orderby'] = 'post__in'; } return $query_args; }, 10, 2 ); // Part 2: Float sticky posts to the top when they are in the results, only after facet filtering add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing // Is only true after facet filtering, but not when using only a Pager or Sort facet if ( $post_ids !== FWP()->unfiltered_post_ids ) { // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } } return $post_ids; }, 10, 2 );
Note that in the above code, we used the facetwp_filtered_post_ids hook and not facetwp_pre_filtered_post_ids
, because of the condition in line 16 which checks against the unfiltered post ids.
Option A3: only after filtering with a specific facet
If you want to do the same as above in A2, but only when a specific facet is used, use the following code instead.
Make sure to replace “my_facet_name” with the name of you facet, in line 18:
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'facetwp_query_args', function( $query_args, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing $query_args['ignore_sticky_posts'] = true; $query_args['orderby'] = 'post__in'; } return $query_args; }, 10, 2 ); // Part 2: Float sticky posts to the top when they are in the results, only after facet filtering with one specific facet add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) { if ( 'my_listing_name' == $class->ajax_params['template'] ) { // Replace 'my_listing_name' with the name of your Listing Builder listing // Is only true after facet filtering, but not when using only a Pager or Sort facet if ( $post_ids !== FWP()->unfiltered_post_ids ) { $facet_name = 'my_facet_name'; // Replace 'my_facet_name' with the name of your facet // If this facet is present if ( isset( $class->facets[ $facet_name ] ) ) { $selected = $class->facets[ $facet_name ]['selected_values']; // If this facet has any selected choices if ( ! empty( $selected ) ) { // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } } } } return $post_ids; }, 10, 2 );
Option A4: only after filtering but NOT with a specific facet
If you want to do the same as above in A3, but instead of showing them, you want to ignore the sticky posts when a specific facet is used, replace line 26 with the opposite:
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 ( empty( $selected ) ) {
B. Alternative sticky posts in a WP archive or custom WP_Query
In a WP archive or a custom WP_Query, use the following code instead.
The setup is similar to the one for Listing Builder listings. However, the first part uses the pre_get_posts
hook to ignore sticky posts and order the query by post__in
.
The second part uses the facetwp_pre_filtered_post_ids hook to make sticky posts appear at the top, but only if they are already in the results.
In the following snippets, make sure to use the correct conditional tags on line 3 and line 12 to select the query, as explained above.
Option B1: before and after filtering
To let this happen before and after filtering, also on the first page, and also on the first page load, use the following code.
Because this code is used in a WP archive or custom WP_Query, it needs the additional part 3. Because of performance reasons, the facetwp_pre_filtered_post_ids
hook is prevented from changing the query on the first page load for these types of templates. The facetwp_preload_force_query
filter hook forces the hook to also work on the first page load.
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'pre_get_posts', function( $query ) { if ( $query->is_main_query() && is_home() ) { // Works on the blog page only $query->set( 'ignore_sticky_posts', true ); $query->set( 'orderby', 'post__in'); } }, 998, 1 ); // Part 2: Float sticky posts to the top when they are in the results: add_filter( 'facetwp_pre_filtered_post_ids', function( $post_ids, $class ) { if ( is_main_query() && is_home() ) { // Works on the blog page only // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } return $post_ids; }, 10, 2 ); // Part 3: Make 'facetwp_filtered_post_ids' work on first page load: add_filter( 'facetwp_preload_force_query', '__return_true' );
Option B2: only after filtering
If you want to do the same as above in B1, but only after using facets (so not on the first page load), use the following code instead. It will make sticky posts appear at the top, if they are already in the results, when using any facet (except Pager facets and Sort facets):
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'pre_get_posts', function( $query ) { if ( $query->is_main_query() && is_home() ) { // Works on the blog page only $query->set( 'ignore_sticky_posts', true ); $query->set( 'orderby', 'post__in'); } }, 998, 1 ); // Part 2: Float sticky posts to the top when they are in the results, only after facet filtering add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) { if ( is_main_query() && is_home() ) { // Works on the blog page only // Is only true after facet filtering, but not when using only a Pager or Sort facet if ( $post_ids !== FWP()->unfiltered_post_ids ) { // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } } return $post_ids; }, 10, 2 );
Note that in the above code, we used the facetwp_filtered_post_ids hook, and not facetwp_pre_filtered_post_ids
, because of the condition in line 15 which checks against the unfiltered post ids.
Another difference with option B1 is that we don’t need the facetwp_preload_force_query
hook (part 3), because we only need the code to work after facet filtering.
Option B3: only after filtering with a specific facet
If you want to do the same as above in B2, but only when a specific facet is used, use the following code instead.
Make sure to replace “my_facet_name” with the name of you facet, in line 17:
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
// Part 1: Ignore sticky posts and order by post__in: add_filter( 'pre_get_posts', function( $query ) { if ( $query->is_main_query() && is_home() ) { // Works on the blog page only $query->set( 'ignore_sticky_posts', true ); $query->set( 'orderby', 'post__in'); } }, 998, 1 ); // Part 2: Float sticky posts to the top when they are in the results, only after facet filtering with one specific facet add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) { if ( is_main_query() && is_home() ) { // Works on the blog page only // Is only true after facet filtering, but not when using only a Pager or Sort facet if ( $post_ids !== FWP()->unfiltered_post_ids ) { $facet_name = 'my_facet_name'; // Replace 'my_facet_name' with the name of your facet // If this facet is present if ( isset( $class->facets[ $facet_name ] ) ) { $selected = $class->facets[ $facet_name ]['selected_values']; // If this facet has any selected choices if ( ! empty( $selected ) ) { // Get the list of sticky post IDs $sticky_posts = get_option( 'sticky_posts' ); // Get the sticky post ids in $post_ids $stickies_in_results = array_intersect( $sticky_posts, $post_ids ); // Remove sticky post IDs $post_ids = array_diff( $post_ids, $stickies_in_results ); // Re-add sticky posts at the beginning of $post_ids // The query must be ordered by 'post__in' for the sticky posts to appear at the top $post_ids = array_merge( $stickies_in_results, $post_ids ); } } } } return $post_ids; }, 10, 2 );
Option B4: only after filtering but NOT with a specific facet
If you want to do the same as above in B3, but instead of showing them, you want to ignore the sticky posts when a specific facet is used, replace line 25 with the opposite:
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 ( empty( $selected ) ) {
Using sticky posts in a block or page builder module listing
If you are using blocks, or a page builder module/widget for you posts listing, you can use the same approach as in all examples above, but you may have to change the facetwp_query_args
/ pre_get_posts
hook:
Block queries
If you are using WP blocks, the hook to use depends on the exact block. See this section on our Blocks page.
Bricks Posts, Products, or Query Loop elements
If you are using Bricks with a Posts, Products, or Query Loop element, you can use the bricks/posts/query_vars
hook. See this explanation and example, and this Bricks tutorial.
Elementor Post or Loop Grid widget
If you are using Elementor with a Posts widget or a Loop Grid widget, you can use Elementor’s elementor/query/{my_query_id}
hook. See this explanation and example, and this Elementor tutorial.
Beaver Builder Posts module
If you are using Beaver Builder with a Posts module, you can use the fl_builder_loop_query_args
hook. See this explanation and example, and this Beaver Builder tutorial.