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 an ignore_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 the posts_per_page argument to -1. And to get only the post IDs instead of all post fields, we set the fields argument to ids.
  • 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 first 3 post IDs of $all_posts, where 3 is the number of posts set in offset in line 6.
  • The final step is to feed $excluded_posts into the post__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.

See also