How to use the offset query argument with FacetWP
Let’s start with the bad news: FacetWP does not work with a directly set offset
query argument, due to the following issues:
The issues with using offset
A query’s offset
is defined as “the number of post to displace or pass over”. This is problematic with FacetWP because this post displacement is applied every time the query is used: on first page load, but also after filtering with facets.
For example, after clicking a facet choice that would normally result in five posts in the results, the listing will only show the two last posts of the original five, if the query has an offset
set to 3
.
A similar issue happens with pagination: using offset
will break pagination in general (not only when using FacetWP), because it is used by WordPress internally to calculate which posts need to be on which paged page.
Fortunately, there is a way to work around these issues: using get_posts()
in combination with a post__not_in
query argument:
An alternative offset solution
We can work around the above issues by first using the get_posts() function to get all posts IDs to skip over, and then feeding these post IDs back into the post__not_in
query argument of the listing query, which will effectively remove them from the listing.
The hook to use depends on the listing template type:
Using offset in a Listing Builder listing
If you are using a Listing Builder listing, you can use the facetwp_query_args hook to do this.
Add the following code to your (child) theme’s functions.php. Make sure to replace my_listing_name
in line 3 with the name of your listing. Instead of using the listing template name as condition here, you could also use the page URI, which is the part of the URL without the domain name and the query variables, and without beginning or ending slashes. See this example.
Next, set the desired offset
value in line 6:
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 // Set the desired offset $offset = 3; // Prevent issues with sticky posts $query_args['ignore_sticky_posts'] = true; // Create a copy of the listing query arguments $gp_query_args = $query_args; // Get all posts $gp_query_args['posts_per_page'] = -1; // Get only ids $gp_query_args['fields'] = 'ids'; // Get all post ids without offset $all_posts = get_posts( $gp_query_args ); // Filter out the offset posts $excluded_posts = array_slice( $all_posts, 0, $offset ); // Exclude the offset posts from the query if ( !empty( $excluded_posts ) ) { $query_args['post__not_in'] = $excluded_posts; } } return $query_args; }, 10, 2 );
Explanation of the above code:
- After setting the desired
offset
value in line 6, we first add anignore_sticky_posts
query argument, to prevent issues with sticky posts in the query. - Next, we make a copy of the original listing query arguments into
$gp_query_args
, and add two new arguments. To get all post, we skip pagination by setting theposts_per_page
argument to-1
. And to get only the post IDs instead of all post fields, we set thefields
argument toids
. - Then we use these query arguments in the get_posts() function, to let it retrieve
$all_posts
, an array of all post IDs that are in the original listing query. - Next, we use
array_slice()
to create a new array$excluded_posts
that only contains the first3
post IDs of$all_posts
, where3
is the number of posts set inoffset
in line 6. - The final step is to feed
$excluded_posts
into thepost__not_in
query argument of the original listing query, which will remove these posts from the listing.
The above will result in a customized listing query, with the “offset” posts removed. Pagination and facet filtering will all keep working as expected.
Also, as opposed to using a query offset argument directly, this workaround will still function when using a posts_per_page
set to -1
.
Using offset in a WP archive or custom WP_Query
In a WP archive or a custom WP_Query, we can use exactly the same approach as above, but instead we use the pre_get_posts hook.
Add the following code to your (child) theme’s functions.php. The condition in line 3 makes this example only work on the blog page. See the green banner below for how to adapt this condition.
Set the desired offset
value in line 6:
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_action( 'pre_get_posts', function( $query ) { if ( $query->is_main_query() && is_home() ) { // Works on the blog page only // Set the desired offset $offset = 3; // Prevent issues with sticky posts $query->set( 'ignore_sticky_posts', true ); // for the original listing query $query_args['ignore_sticky_posts'] = true; // for get_posts() // Create a copy of the listing query arguments $gp_query_args = $query_args; // Get all posts $gp_query_args['posts_per_page'] = -1; // Get only ids $gp_query_args['fields'] = 'ids'; // Get all post ids without offset $all_posts = get_posts( $gp_query_args ); // Filter out the offset posts $excluded_posts = array_slice( $all_posts, 0, $offset ); // Exclude the offset posts from the query if ( !empty( $excluded_posts ) ) { $query->set( 'post__not_in', $excluded_posts ); } } }, 998, 1 );
Using offset 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 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.