I have a custom post type where the title is a name, and I want to sort the loop by the last word in title, the surname.

I can easily break the names apart and filter by the last word in a custom query, but I don't want to re-write the main loop / pagination for the archive page. Is it possible to pass a callback to the orderby param in the action pre_get_posts or is this a lost cause?


2 Answers 2


No you can't pass a callback function. Callback functions are PHP, so they can't be run in MySQL. For that to work it would need to query the results from the database first, but you'd get the wrong results because they're unsorted.

You would need to use the posts_orderby filter to do this as part of the SQL query:

    function( $orderby, $query ) {
        if ( ! is_admin() && $query->is_post_type_archive( 'post_type_name' ) ) {
            $orderby = "SUBSTRING_INDEX(post_title, ' ', -1) ASC, post_title ASC";
        return $orderby;

That uses the SUBSTRING_INDEX() MySQL function to order results by everything after the first last space in the post title, and then by the post title so that results with matching surnames are sorted by first name.

  • That's brilliant! I may be pushing my luck but many names have multiple spaces (middle initials, titles, etc). Can SUBSTRING_INDEX accept a range? (-1, -2, if -2 exists, and so on?)
    – jamesfacts
    Aug 4, 2020 at 5:59
  • Sorry, I believe this will actually be based on everything after the last space. So -2 would be after the second last space, and so on. Ultimately you'll be better off storing each part of the name separately, since the number of spaces doesn't tell you much. For example, if the name is "Vincent van Gogh", do you want to sort by "van Gogh" or "Gogh"? If it's "van Gogh", then the same sorting won't work for a first name - last name type of name, so you'd need to store the last name separately. Aug 4, 2020 at 6:03
  • Yep, I misunderstood the position parameter. I tweaked your code a little bit bc $query doesn't seem to be available at that filter point. Thanks for pointing me to SUBSTRING_INDEX()!
    – jamesfacts
    Aug 4, 2020 at 19:07
  • I've updated my answer to fix the missing $query issue. I needed to set the 4th argument of add_filter() to 2. Aug 5, 2020 at 0:15

Just posting my code for posterity, I made some small changes to @Jacob Peatite's code, if anyone needs a drop-in solution:

add_filter('posts_orderby', __NAMESPACE__ . '\surname_orderby', 10, 2);
function surname_orderby($args, $wp_query)
    //make sure this is not an admin dashboard query.
    if (is_admin()) {
    //check if this is the main query, rather than a secondary query such as widgets or menus.
    if (!is_main_query()) {

    // the wp query at this filter is literally just the orderby value, we need to fetch the queried object
    $queriedObj = $wp_query->get_queried_object();
    if (isset($queriedObj->name) && $queriedObj->name === the_one_I_want ) {
        $args = "SUBSTRING_INDEX(post_title, ' ', -1) ASC, post_title ASC";
        return $args;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.