55

I need to do a WP_Query with a LIKE on the post_title.

I started with this regular WP_Query:

$wp_query = new WP_Query( 
    array (
        'post_type'        => 'wp_exposants',
        'posts_per_page'   => '1',
        'post_status'      => 'publish',
        'orderby'          => 'title', 
        'order'            => 'ASC',
        'paged'            => $paged
    )
); 

But what I actually want to do looks like this in SQL:

$query = "
        SELECT      *
        FROM        $wpdb->posts
        WHERE       $wpdb->posts.post_title LIKE '$param2%'
        AND         $wpdb->posts.post_type = 'wp_exposants'
        ORDER BY    $wpdb->posts.post_title
";
$wpdb->get_results($query);

The output prints the results i'm excpecting, but I use the regular <?php while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?> to display the results.
And that is not working with $wpdb->get_results().

How can I achieve what I described here?

6 Answers 6

54

I would solve this with a filter on WP_Query. One that detects an extra query variable and uses that as the prefix of the title.

add_filter( 'posts_where', 'wpse18703_posts_where', 10, 2 );
function wpse18703_posts_where( $where, &$wp_query )
{
    global $wpdb;
    if ( $wpse18703_title = $wp_query->get( 'wpse18703_title' ) ) {
        $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'' . esc_sql( $wpdb->esc_like( $wpse18703_title ) ) . '%\'';
    }
    return $where;
}

This way you can still call WP_Query, you just pass the title as the wpse18703_title argument (or change the name to something shorter).

5
  • 2
    This one is somehow missing the $wpdb->prepare().
    – kaiser
    Nov 6, 2012 at 15:46
  • 1
    @kaiser: It's been a long time, but I think this was not possible with prepare(). $wpdb->prepare('LIKE "%s%%"', 'banana') would return "LIKE ''banana'%'", so we have to construct the query ourselves, and do the escaping too.
    – Jan Fabry
    Nov 6, 2012 at 19:36
  • 1
    @JanFabry Happy to see you agaaaaaaaain! :) Drop by in chat some time, hm? StopPress would be happy to see you. About that prepare(). Yeah, that's tricky and I had to try that several times, before I got around it. From something I just made: $wpdb->prepare( ' AND {$wpdb->posts}.post_title LIKE %s ', esc_sql( '%'.like_escape( trim( $term ) ).'%' ) ). And I'm pretty sure the esc_sql() is unnecessary and just paranoid.
    – kaiser
    Nov 7, 2012 at 10:35
  • It seems that you can't search a string with ' (apostrophe) inside. I guess it's because of escaping ? I didn't find the solution yet Aug 30, 2018 at 15:10
  • I had to remove the & from &$wp_query otherwise I was getting a PHP expected to be a reference, value given error
    – ecairol
    May 28, 2021 at 20:23
24

Simplified:

function title_filter( $where, &$wp_query )
{
    global $wpdb;
    // 2. pull the custom query in here:
    if ( $search_term = $wp_query->get( 'search_prod_title' ) ) {
        $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( like_escape( $search_term ) ) . '%\'';
    }
    return $where;
}

$args = array(
    'post_type' => 'product',
    'posts_per_page' => $page_size,
    'paged' => $page,
    // 1. define a custom query var here to pass your term through:
    'search_prod_title' => $search_term,
    'post_status' => 'publish',
    'orderby'     => 'title', 
    'order'       => 'ASC'
);

add_filter( 'posts_where', 'title_filter', 10, 2 );
$wp_query = new WP_Query($args);
remove_filter( 'posts_where', 'title_filter', 10 );
return $wp_query;
8
  • 14
    Please include an explanation along with your code.
    – s_ha_dum
    Apr 19, 2013 at 13:49
  • 2
    Great simplification Nov 10, 2014 at 19:52
  • 4
    Code is i think self explained, atleast for me. Thanks for sharing complete script. Aug 8, 2017 at 6:55
  • 2
    Use '$wpdb->esc_like (' instead of 'esc_sql( like_escape('
    – fdrv
    Feb 19, 2018 at 17:16
  • 2
    remove_filter only uses 3 arguments, the last one can be removed.
    – Skatox
    Jun 24, 2020 at 15:03
21

Wanted to update this code you guys worked on for the wordpress 4.0 and above as esc_sql() is deprecated in 4.0 higher.

function title_filter($where, &$wp_query){
    global $wpdb;

    if($search_term = $wp_query->get( 'search_prod_title' )){
        /*using the esc_like() in here instead of other esc_sql()*/
        $search_term = $wpdb->esc_like($search_term);
        $search_term = ' \'%' . $search_term . '%\'';
        $where .= ' AND ' . $wpdb->posts . '.post_title LIKE '.$search_term;
    }

    return $where;
}

Rest of the stuff is same.

Also I want to point out you can use s variable within WP_Query arguments to pass search terms, which will also search for post title i believe.

Like this:

$args = array(
    'post_type' => 'post',
    's' => $search_term,
    'post_status' => 'publish',
    'orderby'     => 'title', 
    'order'       => 'ASC'        
);
$wp_query = new WP_Query($args);
4
  • What exactly search_prod_title is? Should i change this to something else? Mar 24, 2017 at 17:20
  • 1
    Since when is esc_sql depricated? It's not. $wpdb->escape is though... developer.wordpress.org/reference/functions/esc_sql
    – Jeremy
    Feb 2, 2018 at 17:36
  • 1
    Note that the s parameter searches the post content as well, which may not be the desired aim. =)
    – Christine Cooper
    Apr 19, 2018 at 10:43
  • like_escape deprecated 4.0.0 Use wpdb::esc_like Mar 14, 2020 at 6:40
18

With some vulnerable solution posted here, I come with a bit simplified and sanitized version.

First, we create a function for the posts_where filter which allows you to only show posts matching specific conditions:

function cc_post_title_filter($where, &$wp_query) {
    global $wpdb;
    if ( $search_term = $wp_query->get( 'cc_search_post_title' ) ) {
        $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . $wpdb->esc_like( $search_term ) . '%\'';
    }
    return $where;
}

Now we add cc_search_post_title into our query arguments:

$args = array(
    'cc_search_post_title' => $search_term, // search post title only
    'post_status' => 'publish',
);

And finally wrap the filter around the query:

add_filter( 'posts_where', 'cc_post_title_filter', 10, 2 );
$query = new WP_Query($args);
remove_filter( 'posts_where', 'cc_post_title_filter', 10 );

Using get_posts()

Certain functions which retrieve posts do not run filters, so the posts_where filter functions you attach will not modify the query. If you plan to use get_posts() to query your posts, you need to set suppress_filters to false in your argument array:

$args = array(
    'cc_search_post_title' => $search_term,
    'suppress_filters' => FALSE,
    'post_status' => 'publish',
);

Now you can use get_posts():

add_filter( 'posts_where', 'cc_post_title_filter', 10, 2 );
$posts = get_posts($args);
remove_filter( 'posts_where', 'cc_post_title_filter', 10 );

What about the s parameter?

The s parameter is available:

$args = array(
    's' => $search_term,
);

While adding your search term into the s parameter work and it will search the post title, it will also search the post content.

What about the title parameter which was added with WP 4.4?

Passing a search term into the title parameter:

$args = array(
    'title' => $search_term,
);

Is case sensitive and LIKE, not %LIKE%. This mean search for hello will not return post with title Hello World or Hello.

3
  • Excellent. I was looking for 'post_title' as a parameter and, obviously, didn't find anything.
    – MastaBaba
    Sep 13, 2019 at 15:55
  • I'm getting an error with wp query or get posts: "E_WARNING Error in file »class-wp-hook.php« at line 288: Parameter 2 to cc_post_title_filter() expected to be a reference, value given
    – Elkrat
    Mar 3, 2020 at 17:28
  • @Elkrat Remove the & from &$wp_query in cc_post_title_filter.
    – Mattimator
    May 5, 2020 at 21:04
8

Building on other answers before me, to provide flexibility in the situation where you want to search a post that contains a word in a meta field OR in the title of the post, I give that option via the argument "title_filter_relation." In this implementation, I only allow for "OR" or "AND" inputs with a default of "AND."

function title_filter($where, &$wp_query){
    global $wpdb;
    if($search_term = $wp_query->get( 'title_filter' )){
        $search_term = $wpdb->esc_like($search_term); //instead of esc_sql()
        $search_term = ' \'%' . $search_term . '%\'';
        $title_filter_relation = (strtoupper($wp_query->get( 'title_filter_relation'))=='OR' ? 'OR' : 'AND');
        $where .= ' '.$title_filter_relation.' ' . $wpdb->posts . '.post_title LIKE '.$search_term;
    }
    return $where;
}

Here's an example of the code in action for a very simple post type "faq" where the question is the post title itself:

add_filter('posts_where','title_filter',10,2);
$s1 = new WP_Query( array(
    'post_type' => 'faq',
    'posts_per_page' => -1,
    'title_filter' => $q,
    'title_filter_relation' => 'OR',
    'post_status' => 'publish',
    'orderby'     => 'title', 
    'order'       => 'ASC',
    'meta_query' => array(
        'relation' => 'OR',
        array(
            'key' => 'faq_answer',
            'value' => $q,
            'compare' => 'LIKE'
        )
    )
));
remove_filter('posts_where','title_filter',10,2);
2
  • 1
    Good insight, adding custom "query vars" to the query args passed to WP_Query in order to be able to access them within the posts_where filter.
    – Tom Auger
    Feb 4, 2017 at 21:32
  • seems like a good answer, the only problem is that it's not working for me :-( Mar 28, 2022 at 11:04
0

The optimal approach for conducting a WP_Query with a 'LIKE' search on the post_title is to include the 'search_columns' query argument. If you are constructing the WP_Query manually, you should specify it as follows:

'search_columns' => ['post_title']

You can find more details in the WordPress source code on Trac: https://core.trac.wordpress.org/browser/tags/6.3/src/wp-includes/class-wp-query.php#L1426

However, if your intention is to modify queries generated by third-party plugins or themes created query, you can achieve this by leveraging the 'post_search_columns' filter hook.

The official documentation provides comprehensive information on how to use this hook: https://developer.wordpress.org/reference/hooks/post_search_columns/

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