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: