Order custom post type posts by custom field with pretty URL

This is possible, if a little involved. To achieve this you will need:

  • A custom query var.
  • A filter that sorts results based on the query var.
  • A rewrite rule for the archive that applies the query var for your pretty URL.
  • Another rewrite rule for the same thing, but including pagination.
  • Check for the query var to apply changes to any other parts of the page that require it.

So the first thing to do is add the query var. This means that the value of a ?ranked=1 query parameter added to the URL will be passed to the main WP_Query object.

add_filter(
    'query_vars',
    function( $query_vars ) {
        $query_vars[] = 'ranked';

        return $query_vars;
    }
);

Then we can check for that value inside the pre_get_posts hook, and if it’s present, set the main query to order posts the way we want:

add_action(
    'pre_get_posts',
    function( WP_Query $query ) {
        if ( $query->is_post_type_archive( 'my_post_type' ) ) {
            if ( $query->get( 'ranked', false ) ) {
                $query->set( 'meta_key', 'rating' );
                $query->set( 'orderby', 'meta_value_num' );
            }
        }
    }
);

This much will give you the functionality you want, but with an ugly URL. Accessing /my_post_type/?ranked=1 will display the results formatted the way you want, and the pagination links should automatically keep the ranked parameter, so pagination should work.

The next step is adding the rewrite rules. You’ll need two. One for the first page, and then one for the paginated pages. You can just take the default rewrite rules for the post type archive, and add ranked:

add_action(
    'init',
    function() {
        add_rewrite_rule( 'my_post_type/ranked/?$', 'index.php?post_type=my_post_type&ranked=1', 'top' );
        add_rewrite_rule( 'my_post_type/ranked/page/([0-9]{1,})/?$', 'index.php?post_type=my_post_type&ranked=1&paged=$matches[1]', 'top' );
    }
);

Now you will be able to load ranked posts at /my_post_type/ranked/ and /my_post_type/ranked/page/2. The default output of paginate_links() will automatically give you the correct links to paginate through ranked results.

So the above 3 blocks of code will give you the functionality you want. If you need to check whether you’re viewing the ranked view within other fitlers or actions you can use this condition:

if ( get_query_var( 'ranked', false ) ) {

}

For example, you might want to filter the <title></title> tag of the page if the ranked view is loaded to add “Top Rated”. You would do that like this (as long as your theme supports title-tag):

add_filter(
    'document_title_parts',
    function( $title_parts ) {
        if ( is_post_type_archive( 'my_post_type' ) ) {
            if ( get_query_var( 'ranked', false ) ) {
                $title_parts['title'] = 'Top Rated ' . $title_parts['title'];
            }
        }

        return $title_parts;
    }
);