9

I want to search for posts with WP_Query() and select only posts that begin with a specific letter. I found a bunch of old posts with filters that were pre-4.4, but since 4.4 there is a title search added to the WP_Query function.

 $q = new WP_Query( array('post_type'=>'post','title'=>'This Song Title') );

This will return only exact matches of posts with the title "This Song Title".

What I would like to do is return all posts that start with, in this example, the letter 'T'

I also found this post, which was never really answered. It's the accepted answer, but I don't see how it answers the question at all. How to limit search to first letter of title?

Other query features like comment and meta searches have a 'compare' for adding LIKE% to queries, but title does not seem to.

1 Answer 1

23

It's not possible with WP_Query directly out of the box, but by using the posts_where filter to look for a custom argument on WP_Query, it is possible to add this functionality.

Assuming starts_with is the name of the argument we want to use, we can filter posts_where to add a WHERE clause limiting results to those that begin with the given value if starts_with has been set on the query:

function wpse_298888_posts_where( $where, $query ) {
    global $wpdb;

    $starts_with = esc_sql( $query->get( 'starts_with' ) );

    if ( $starts_with ) {
        $where .= " AND $wpdb->posts.post_title LIKE '$starts_with%'";
    }

    return $where;
}
add_filter( 'posts_where', 'wpse_298888_posts_where', 10, 2 );

With this filter added, we can query posts like this:

$query = new WP_Query( array(
    'starts_with' => 'M',
) );

That will return all posts beginning with "M".

If you want to be able to filter the main query, you can also use this argument in pre_get_posts:

function wpse_298888_pre_get_posts( $query ) {
    if ( $query->is_main_query() ) {
        $query->set( 'starts_with', 'M' );
    }
}
add_action( 'pre_get_posts', 'wpse_298888_pre_get_posts' );
8
  • 3
    This works perfect, but I made one adjustment that may also help others. I used REGEXP instead of LIKE so I could feed the range 0-9 for titles that start with numbers into a single group. $where .= " AND $wpdb->posts.post_title REGEXP '^$starts_with'";
    – Mark
    Mar 26, 2018 at 19:07
  • Would it be possible to do this the other way around, where you could select all of the posts and/or pages that don't start with a particular letter/string? I want to exclude specific pages whose titles start with the same word from the search results, but want to keep it open-ended in case more of these are created as opposed to using static IDs. These aren't posts, so they don't have tags or categories. Dec 18, 2018 at 21:19
  • 1
    Just change the LIKE in the first block of code to NOT LIKE. Dec 18, 2018 at 23:42
  • 1
    @VincenzoPiromalli If you filter the main query with the pre_get_posts method, then any normal pagination will work. Mar 8, 2021 at 0:44
  • 1
    Just be aware that if you have titles in complicated language with diacritics the result might not be accurate. The database collation of the title field plays big role too. Even then the result might not be perfect. Plus I would escape the string you feed in with something like: mb_substr($starts_with, 0, 1) // for CH and dual letters and indeed $wpdb->esc_like()
    – 2046
    Mar 4 at 18:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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