Sorting a custom post type in pre_get_posts

You can add a filter on posts_clauses to;

  • left join additional columns for each meta filter
  • add order by clause base on those column values.

Something like below should work, assuming your meta key is start_date and the value has the actual date format Y-m-d and your post type is talks

add_filter( 'posts_clauses', function ($clauses, \WP_Query $q) {
    
    // exit if post type not talk, or in admin, or not main query, or not archive
    if ( $q->get('post_type') != 'talks' || is_admin() || !$q->is_main_query() || !is_archive() ) 
        return $clauses;


    // left join the meta key for future date (fd) and past date (pd)
    $clauses['join'] = "
    
    LEFT JOIN wp_postmeta fd ON (wp_posts.ID = fd.post_id)
                    AND fd.meta_key = 'start_date'
                    AND CAST(fd.meta_value AS DATE) >= CAST('".date('Y-m-d')."' AS DATE) 
                    
    LEFT JOIN wp_postmeta pd ON (wp_posts.ID = pd.post_id)
                    AND pd.meta_key = 'start_date'
                    AND CAST(pd.meta_value AS DATE) < CAST('".date('Y-m-d')."' AS DATE)";
                    
     //order them first if fd value is null, followed by fd ascending order, then pd descending order
    $clauses['orderby'] = 'CASE WHEN fd.meta_value IS NULL THEN 1 ELSE 0 END, CAST(fd.meta_value AS DATE) ASC, CAST(pd.meta_value AS DATE) DESC';
    
 
    return $clauses;
}, 1, 2 );