Search WordPress Posts by Keyword with WP_Query and $wpdb LIKE Queries

WordPress’s built-in search — accessible via the s query variable — performs a LIKE match against post_title and post_content for all public post types. This is fine for front-end search results, but it does not help when you need to query programmatically: find all posts containing a specific phrase to audit content before a migration, locate every post that references an outdated product name, or build an admin tool that lists pages by keyword. For those use cases you have two options: pass 's' to WP_Query to use WordPress’s search logic with all its filtering hooks, or use $wpdb->get_results() with a prepared LIKE query for direct control over what columns are searched. This article covers both approaches and explains when to use each.

Problem: You need to find all published posts whose title or content contains a specific word or phrase — for example to list every page that mentions "Contact us" before a site rename — and output their edit links in a custom admin tool.

Solution: Use WP_Query with the 's' parameter for standard WordPress search semantics. For column-level control (searching meta, custom tables, or specific columns only) use $wpdb->get_results() with a prepared LIKE query.

Approach 1 — WP_Query with 's' (searches title + content, respects post status/type filters):

<?php
/**
 * Search published posts/pages by phrase and return an array of
 * [ 'id' => int, 'title' => string, 'edit_link' => string ].
 */
function search_posts_by_phrase( $phrase, $post_types = [ 'post', 'page' ] ) {
    if ( empty( trim( $phrase ) ) ) {
        return [];
    }

    $query = new WP_Query( [
        's'              => sanitize_text_field( $phrase ),
        'post_type'      => $post_types,
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'no_found_rows'  => true,
        'fields'         => 'ids',
    ] );

    $results = [];
    foreach ( $query->posts as $post_id ) {
        $results[] = [
            'id'        => $post_id,
            'title'     => get_the_title( $post_id ),
            'edit_link' => get_edit_post_link( $post_id ),
            'url'       => get_permalink( $post_id ),
        ];
    }

    return $results;
}

Approach 2 — $wpdb with a prepared LIKE query (full control over columns, no WordPress search filters applied):

<?php
function search_posts_in_content( $phrase ) {
    global $wpdb;

    $like = '%' . $wpdb->esc_like( $phrase ) . '%';

    $results = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT ID, post_title
             FROM {$wpdb->posts}
             WHERE post_status = 'publish'
               AND post_type   IN ('post', 'page')
               AND (post_title LIKE %s OR post_content LIKE %s)
             ORDER BY post_date DESC",
            $like,
            $like
        )
    );

    foreach ( $results as $row ) {
        printf(
            '<p><a href="%s">%s</a> — <a href="%s">Edit</a></p>',
            esc_url( get_permalink( $row->ID ) ),
            esc_html( $row->post_title ),
            esc_url( get_edit_post_link( $row->ID ) )
        );
    }
}

NOTE: Both LIKE '%phrase%' queries and WP_Query 's' perform full-column scans on the post_content field — which is a longtext column and cannot be indexed in the traditional sense. On a site with tens of thousands of posts this can be slow. For production admin tools that run rarely this is acceptable; for front-end search on large sites, use a dedicated search plugin (SearchWP, ElasticPress) that maintains a full-text index.

Sources:

  1. https://stackoverflow.com/questions/9677039/wordpress-how-search-a-post-for-post-content-with-wp-query-class
  2. https://question-it.com/questions/345044/mysql-poisk-tochnogo-slova-iz-stroki