If you are seeing duplicate posts in your listing, especially in combination with a Pager/Load more facet or Sort facet, the cause is almost always the way the posts are ordered in the query, which is determined by the orderby query argument.

If results are ordered by a value that is the same (or empty) for multiple posts, or by a random value, MySQL does not have a fallback and will often sort erratically.

There are several scenarios that can cause this to happen, but – long story short – the solution is to add a secondary, fallback sort order.

The duplicate posts will be most visible when the listing has numbered or “load more” pagination, for example with a Pager facet. On each refresh, the fetched posts will be different and/or duplicated.

Scenarios causing duplicate entries

Look out for the following scenarios that are most often the cause of multiple posts sharing the same value for the orderby field, resulting in duplicate posts:

  1. Posts were imported (for example with WP All Import or WebToffee Import Export), and no specific orderby argument is set in the query, or it is set to date. Imported posts often share the exact same post date, and by default, WordPress will order queries by post date.
  2. The orderby argument is set to menu_order. Note that the “menu order” is not just the order of your posts in the backend. It is a post setting that has to be specifically set for each post, and it defaults to 0. It can also be set with plugins like Post Types Order.
  3. The orderby argument is set to a specific custom field (with meta_value or meta_value_num) and multiple posts have the same value for that field, or the field is empty or does not exist for multiple posts.
  4. The orderby argument is set to rand to create a random order. Even with a fallback sort order, the results will be re-randomized on each facet interaction (including pagination), which will lead to duplicate posts and other issues. The solution can be found here.

Duplicate entries when using a Sort facet

The above scenarios can also happen with one or more sort options in a Sort facet. The solution is the same as for template queries: add a secondary, fallback sorting method.

Add a fallback sort order

The solution for all scenarios described above (except for random ordering) is simple: provide the query with a secondary, fallback field to order by. Preferably use a field that is unique, like ID. But title, or date (if not in the first scenario above) can work too.

Using a custom WP_Query

If you are using a custom WP_Query, you can add a secondary sort method to the orderby query argument by using an array:

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

$args = [ // ... your arguments 'orderby' => [ 'date' => 'DESC', // Primary sort: by post date 'ID' => 'DESC' // Secondary, fallback sort: by post ID ], // ... your arguments ];

Using a WP archive query

To modify the orderby argument of a WP archive template query, you can use a pre_get_post hook, 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

add_action( 'pre_get_posts', function( $query ) { if ( $query->is_post_type_archive( [ 'resources', 'courses']) && $query->is_main_query() ) { $query->set( 'orderby', [ 'date' => 'DESC', // Primary sort: by post date 'title' => 'ASC' // Secondary, fallback sort: by post title ] ); } } );

Make sure to include the original/intended sort order as the primary sort in the array, because the orderby argument in the pre_get_posts hook will override the original query argument.

Using the Listing Builder

Adding a fallback sorting method in a Listing Builder template. E.g. sort by post date first, then by post title.
Adding a fallback sorting method in a Listing Builder template. E.g. sort by post date first, then by post title.

If you are using a Listing Builder template, you can add a secondary sort in the Query tab.

Click the “Add query sort” button to add a primary sorting rule. Then click the “Add query sort” button again to add a fallback sorting rule.

Using the Listing Builder in Dev mode

If you are using a Listing Builder template in dev mode, you can add the fallback sort to the Query Arguments box 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

<?php return [ // ... your arguments 'orderby' => [ 'date' => 'DESC', // Primary sort: by post date 'ID' => 'DESC' // Secondary, fallback sort: by post ID ], // ... your arguments ];

Using Elementor Pro with a Posts widget or Loop Grid widget

If you are using Elementor Pro with a Posts widget or a Loop Grid widget, you can use Elementor’s Custom Query Filter hook. This hook works similarly to WP’s pre_get_posts hook.

How to give your widget's query a unique Query ID.
How to give your widget’s query a unique Query ID.

In the following example, we set the orderby query argument to sort the query by post date first. Then we add a secondary, fallback order to sort by post title.

Make sure to replace my_query_id with the unique Query ID you have set in the “Query ID” setting of the widget:

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('elementor/query/my_query_id', function($query) { // Replace "my_query_id" with your unique Query ID $query->set( 'posts_per_page', 12 ); $query->set('orderby', [ 'date' => 'DESC', // Primary sort: by post date, descending (from new to old) 'title' => 'ASC' // Secondary, fallback sort: by post title, alphabetically (from A-Z) ]); });

Using WordPress blocks

If you are using one of the supported blocks in FacetWP’s Blocks add-on, the method to add a fallback order depends on the exact block type. This section gives an overview of the correct query hook to use for each block type.

The following example adds a fallback order for the WordPress Query Loop block. We restrict the hook to blocks that are enabled for FacetWP. Add other Conditional Tags to further restrict the hook as needed. This example only runs on a page with ID 123:

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( 'query_loop_block_query_vars', function( $query, $block, $page ) { if ( is_page(123) && ( strpos( ( $classname = $block->parsed_block['attrs']['className'] ?? '' ), 'facetwp-template') !== false ) ) { // Adapt the is_page() conditional as needed $query['posts_per_page'] = 2; $query['orderby'] = [ 'date' => 'DESC', // Primary sort: by post date, descending (from new to old) 'title' => 'ASC' // Secondary, fallback sort: by post title, alphabetically (from A-Z) ]; } return $query; }, 10, 3 );

Using a Sort facet

Adding a fallback sorting method in a Sort facet. E.g. sort by post date first, then by post title.
Adding a fallback sorting method in a Sort facet. E.g. sort by post date first, then by post title.

In a Sort facet you can add a secondary, fallback sorting method by clicking the black “plus” icon next to an already existing sort criteria row.

See also