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.

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.

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

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:

$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:

add_action( 'pre_get_posts', function( $query ) {
if ( $query->is_category() ) {
    $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:

<?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

If you are using Elementor Pro with a Posts widget, you can use Elementor’s Custom Query Filter hook to customize the query. This hook works similar to WP’s pre_get_posts hook.

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

function my_query_order( $query ) {
  $query->set( 'orderby', [
    'date'  => 'DESC', // Primary sort: by post date
    'title' => 'ASC'   // Secondary, fallback sort: by post title
  ] );
}
add_action( 'elementor/query/{$query_id}', 'my_query_order' );

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