By default, woocommerce sku search fails — a customer searching for a product code like SHOE-002-RED gets no results. WordPress’s core search only scans post_title and post_content; SKUs live in post meta (_sku) and stay invisible. The fix is a posts_search filter that runs a parallel meta query for SKU matches and OR-merges the result IDs into the main search SQL. This guide walks through the snippet, its caveats, and when to consider an external search instead.
Last verified: 2026-04-23 on WooCommerce 9.x with WordPress 6.5. Originally published 2023-04-01, rewritten and updated 2026-04-23.
TL;DR
add_filter( 'posts_search', 'how7o_include_sku_in_search', 999, 2 );
function how7o_include_sku_in_search( $search, $query_vars ) {
global $wpdb;
if ( empty( $query_vars->query['s'] ) ) {
return $search;
}
$term = $query_vars->query['s'];
$posts = get_posts( array(
'post_type' => 'product',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => array(
array(
'key' => '_sku',
'value' => $term,
'compare' => 'LIKE',
),
),
) );
if ( empty( $posts ) ) {
return $search;
}
$id_list = implode( ',', array_map( 'absint', $posts ) );
$search = str_replace(
'AND (((',
"AND ((({$wpdb->posts}.ID IN ({$id_list})) OR (",
$search
);
return $search;
}
Why the default search misses SKUs
The core WordPress search assembles a query like:
SELECT * FROM wp_posts
WHERE ...
AND (((post_title LIKE '%term%') OR (post_content LIKE '%term%')))
AND ...
No wp_postmeta join means no chance of matching the _sku row. WooCommerce doesn’t extend this by default — SKU-as-searchable is left to developers.
How the filter works
posts_searchis a filter WordPress applies after it’s built theAND (((title OR content)))fragment.- Before the fragment is stitched into the final query, we intercept it.
- We run a parallel
get_posts()against_skumeta withLIKE, collect the matching IDs. - We inject those IDs as an OR-clause at the start of the search block:
AND ((({posts}.ID IN (...)) OR (title OR content))). - The final query matches anything whose ID is in our SKU-match list or whose title/content matches.

Scoping to product queries only
add_filter( 'posts_search', function ( $search, $query ) {
if ( ! is_search() || $query->get( 'post_type' ) !== 'product' ) {
return $search;
}
// ... (filter body as above)
return $search;
}, 999, 2 );
The base snippet fires on every search, including post and page searches — wasteful even though the meta_query is scoped to products. Wrapping with is_search() plus the post_type check skips non-product searches entirely.
The brittle bit
$search = str_replace( 'AND (((', "AND ((({$wpdb->posts}.ID IN ({$id_list})) OR (", $search );
WordPress generates the search fragment with a distinctive AND ((( prefix, which this str_replace targets. It’s worked for many years but is technically tied to WordPress’s internal SQL generator. If a future WordPress release changes the fragment shape, this str_replace silently stops matching and SKU search stops working — no error, just empty results. Watch for it on major version upgrades (test a SKU search after updating core).
When to skip this and use external search
- Catalogs over ~10,000 products — the
get_postswith-1scans every SKU on every search. Fine at a few hundred products, slow at tens of thousands. - Need typo-tolerance / ranking — “SHOE-002-RED” vs “shoe 002 red” are very different to MySQL’s LIKE; they’re the same to ElasticPress, Algolia, or Meilisearch.
- Need multi-field fuzzy matching — search against SKU + attribute + category + description with relevance scoring. MySQL LIKE is out of its depth here.
For small catalogs where the native filter works, keep this snippet. For production shops, an external index is the appropriate tool.
Frequently asked questions
Because WordPress’s core search only scans post_title and post_content. SKUs live in product meta (_sku row in wp_postmeta), which the default LIKE query never touches. Extending the search to match SKUs requires filtering posts_search to inject an OR-clause that also matches products whose meta _sku value contains the term.
It’s scoped with 'post_type' => 'product' inside the meta query, so only products match by SKU. Other post types (posts, pages) still only match by title/content. But the filter itself fires on every search query, so if your theme runs the search form across mixed post types, the meta_query runs every time — harmless but an extra query. For scope, wrap the filter body in is_search() && $query->get('post_type') === 'product'.
The filter only adds matching products with an SKU to the result set; it doesn’t exclude SKU-less products from title/content matches. A search for ‘shoes’ will still match both a product titled ‘Running Shoes’ (no SKU needed) and a product with SKU SHOES-001 via the meta clause — the OR’ing is additive.
str_replace on the SQL safe? Yes, because AND ((( is a stable string literal in WordPress’s generated search SQL — it marks the start of the search clause group, and the replacement injects our own post-ID list before that. It’s not elegant — it’s a workaround for a filter that gives you the SQL fragment rather than structured data. If WordPress ever changes the literal, the filter silently stops matching. Keep an eye on major WP version changes.
pre_get_posts? You can set the meta_query directly in pre_get_posts, but the catch is that WordPress’s search uses the s parameter to build its own SQL, and combining that with a meta_query results in AND (match title AND sku), not the OR you want. The posts_search filter is the pragmatic path — ugly, works. For a more robust solution, an external search layer (ElasticPress, Algolia, Meilisearch) indexes SKUs as searchable fields natively.
Related guides
- How to Prepare a %LIKE% SQL Statement in WordPress — the raw
$wpdbLIKE mechanics behind WordPress’s search. - How to Search Users by Multiple Fields in WordPress — the same “search across multiple fields” problem on the user side.
- How to Display a Product View Counter in WooCommerce Without a Plugin — another meta-based product enhancement.
- How to Apply pre_get_posts on Custom Post Types in WordPress — cousin query-modification pattern.
References
WordPress developer reference for posts_search: developer.wordpress.org/reference/hooks/posts_search.