How to prevent duplicate results
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:
- 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 todate
. Imported posts often share the exact same post date, and by default, WordPress will order queries by post date. - The
orderby
argument is set tomenu_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 to0
. It can also be set with plugins like Post Types Order. - The
orderby
argument is set to a specific custom field (withmeta_value
ormeta_value_num
) and multiple posts have the same value for that field, or the field is empty or does not exist for multiple posts. - The
orderby
argument is set torand
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
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.
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
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.