My search broke for four hours overnight and only 74 zero-result searches saw it
Over 90 days and 94,645 searches, a real store logged 95 searches returning zero. 74 of those 95 happened inside the same four-hour window, overnight. It wasn't missing stock: "goku" returned zero with 179 matching products in the catalogue. Zero-result searches aren't just your customers' wish list — they're the only alarm you have.
The usual way to read a store's zero-result searches is as a wish list: someone searched for something, you didn't have it, note it down for the next supplier order.
It's a reasonable reading. This week it would have cost me a full day.
These are the zeros from a real WooCommerce store — collectibles, 5,646 indexed products — between 10 July and 29 September 2026, across 94,645 searches:
| Day | Searches | Zeros | % |
|---|---|---|---|
| 15–26 September (12 days) | 21,736 | 0 | 0.00% |
| 27 September | 2,317 | 63 | 2.72% |
| 28 September | 1,990 | 11 | 0.55% |
| 29 September | 266 | 0 | 0.00% |
Across 90 days there were 95 searches with no results. 74 of those 95 fell inside the same four-hour window, between 11pm on the 27th and 3am on the 28th (Spanish peninsular time). Inside that window, 38.7% of all searches returned an empty page, to 23 distinct visitors. Outside it, across the other 89-odd days: 21 zeros in 94,454 searches — 0.022%.
It wasn't missing stock
The first thing I checked was the obvious one: were people asking for things I don't sell that night?
No. Here are some of the terms that returned zero that night, against the number of products in the catalogue with that word in the title:
| What they searched | Results that night | Products in catalogue |
|---|---|---|
goku |
0 | 179 |
luffy |
0 | 215 |
bleach |
0 | 70 |
miku |
0 | 34 |
A hundred and seventy-nine products with Goku in the title, and search returned an empty page.
And the detail that threw me off at first: search wasn't down. The searches that did work that night returned 71 results on average — exactly the same as any other day of the quarter. From the outside — from the site, from the dashboard, from any "is the server responding?" monitor — the store was fine. It was a partial fault, overnight, and the only trace it left was 74 lines in a search log.
I'm still digging into the exact cause and I'm not going to invent one here. What I do know is what caught it, and that without that log those four hours would not have existed for anyone.
The three different things a zero measures
A zero doesn't mean one thing. It means three, and they have very different priorities:
- You're broken. The product exists, indexed and in stock, and still doesn't show up. Half-finished indexing, an external service failing, a filter stuck on, a deploy.
- You speak differently. The product exists and search can't find it because the customer writes it another way: typos, plurals, synonyms. That's what the typos article and the synonyms one are about.
- You don't stock it. The wish-list case. It's the only one of the three you fix by buying inventory.
The order matters far more than it looks. Almost everyone reads their zero list as if it were all type 3 — the most expensive and slowest to fix. If on 27 September I had opened that list with that mindset, I'd have seen "goku, luffy, bleach, miku" and concluded I had an enormous catalogue gap in the store's best-selling licences.
So the first question about any new zero isn't "what are they asking me for?" but "do I have that product?". If you do, the problem is yours and it's urgent. If you don't, it's a catalogue problem and it can wait until Monday.
How to have this in your store today
WooCommerce does not store your customers' searches. Anywhere. If you don't build it yourself, that four-hour window leaves no trace at all.
It's twenty lines. Save this as wp-content/mu-plugins/registro-busquedas.php — you can create the mu-plugins folder if it doesn't exist, and whatever is inside activates itself:
<?php
/**
* Plugin Name: Search log
* Description: Stores every store search and how many results it returned.
*/
add_action('wp', function () {
if (is_admin() || !is_search() || !is_main_query()) {
return;
}
$termino = trim(get_search_query());
if ($termino === '') {
return;
}
global $wp_query, $wpdb;
$tabla = $wpdb->prefix . 'busquedas';
// The table is created once, not on every search.
if (get_option('registro_busquedas_tabla') !== '1') {
$wpdb->query("CREATE TABLE IF NOT EXISTS {$tabla} (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
termino VARCHAR(191) NOT NULL,
resultados INT NOT NULL,
creado DATETIME NOT NULL,
INDEX idx_termino (termino),
INDEX idx_creado (creado)
) {$wpdb->get_charset_collate()}");
update_option('registro_busquedas_tabla', '1');
}
$wpdb->insert($tabla, [
'termino' => mb_substr($termino, 0, 191),
'resultados' => (int) $wp_query->found_posts,
'creado' => current_time('mysql'),
]);
});
If you already had this from one of the earlier articles, nothing to change: it's the same table.
The query to look at every day
Not the list of terms. The rate, by hour. That's what turns the log into an alarm instead of an archive:
SELECT DATE_FORMAT(creado, '%Y-%m-%d %H:00') AS hora,
COUNT(*) AS busquedas,
SUM(resultados = 0) AS ceros,
ROUND(100 * SUM(resultados = 0) / COUNT(*), 1) AS pct
FROM wp_busquedas
WHERE creado >= NOW() - INTERVAL 14 DAY
GROUP BY hora
HAVING busquedas >= 20
ORDER BY hora DESC;
The HAVING busquedas >= 20 matters: at 4am with three searches, a single zero gives you 33% and a false alarm every night.
What threshold? Yours, not mine. Let it run a week, look at what percentage you get on a normal day, and set the alarm at three or four times that. In this store the baseline is 0.02% and the fault hit 38.7%, so any sensible threshold would have caught it. If your baseline is 8% — very normal with WooCommerce's native search, which fails on any typo — you'll have to fix the typos and synonyms first, because no fault is visible through that much background noise.
And to make it a real alarm, with WP-CLI and an hourly cron job:
wp db query "SELECT ROUND(100*SUM(resultados=0)/COUNT(*),1) FROM wp_busquedas \
WHERE creado >= NOW() - INTERVAL 1 HOUR HAVING COUNT(*) >= 20" --skip-column-names \
| while read pct; do
if [ -n "$pct" ] && [ "${pct%.*}" -ge 10 ]; then
echo "Search: $pct% zero results in the last hour" \
| mail -s "Search alarm" you@example.com
fi
done
When you do sit down to read the list: fold the prefixes first
There's a trap here that pollutes every zero-result report, and it's that a good share of your zeros aren't searches: they're people typing.
If your search shows results as you type, every keystroke gets logged. Across this store's 90 days, the 95 zeros were 76 distinct terms. But looked at in order they appear like this:
ble → blea → bleac → bleach
boa → boa ha → boa hancoc → boa hancock
ju → juu → juuzou → juuzou su → juuzou suzuya
Four and five lines for one person looking for one thing. Count that as four problems and your priority list comes out backwards.
You fold it with one query: keep only the terms that aren't the beginning of any other term in the list.
SELECT a.termino, COUNT(*) AS veces
FROM wp_busquedas a
WHERE a.resultados = 0
AND a.creado >= NOW() - INTERVAL 30 DAY
AND NOT EXISTS (
SELECT 1 FROM wp_busquedas b
WHERE b.resultados = 0
AND b.creado >= NOW() - INTERVAL 30 DAY
AND b.termino <> a.termino
AND b.termino LIKE CONCAT(a.termino, '%')
)
GROUP BY a.termino
ORDER BY veces DESC;
In this store, that query turns 76 terms into 33 real intents. Less than half. And now it reads at a glance: four or five typos, a handful of licences, and one line that's worth the whole exercise on its own:
dragon ball por menos de 4€!("dragon ball for under €4!")
Twice, in two separate sessions. That's not a typo and it's not a missing product: it's someone trying to filter by price by typing it into the search box, exclamation mark included. No store has that on its catalogue wish list, and there it is, written by the customer.
Don't chase the long tail
One last thing, because it saves a lot of wasted time. This is how the store's 94,645 searches spread across its 23,261 distinct terms:
| Terms | % of terms | Searches | % of searches | |
|---|---|---|---|---|
| Searched once | 16,484 | 70.9% | 16,484 | 17.4% |
| Searched 2–4 times | 4,389 | 18.9% | 11,010 | 11.6% |
| Searched 5+ times | 2,388 | 10.3% | 67,151 | 71.0% |
Seven out of ten terms are typed exactly once in a quarter. 10% of terms take 71% of the searches.
Translated to your Monday morning: ignore the one-off zeros without guilt. They're not representative of anything and you won't keep up with them by hand, because every new product brings new names. Sort the folded list by frequency and work top down. The first ten give you nearly all of it.
The exception is the one from the top of this article: if fifty zeros suddenly appear that weren't there yesterday, don't sort them by frequency. Look at the time.
In order, what I'd do this week:
- Put the search log in if you don't have it. Without it, a four-hour fault doesn't exist.
- After seven days, pull your baseline with the hourly query. That number is your normal.
- Set the alarm at three or four times that baseline, with the 20-searches-per-hour minimum.
- Fold the prefixes before reading anything, and sort by frequency.
- For each of the top ten, ask in this order: do I have the product? (then it's a fault or a vocabulary problem) → do they spell it differently? (synonym or typo) → don't I stock it? (catalogue).
If your baseline comes out high and most of your zeros are type 2 — vocabulary — the by-hand list has a ceiling and you hit it fast. That's where a dedicated search engine stops being a luxury: there's the guide on how to choose an ecommerce search engine, with each option's prices verified and when each competitor is better than us.
Is your store losing sales to searches that find nothing?
WildRock adds a search to your WooCommerce that understands what shoppers mean, and tells you in euros how much it generates. Free plugin, no code.
Keep reading
What a visitor to your store is actually worth
Measured today on a real WooCommerce store, 31 days and 11,273 sessions: the average session opens 73.67 € of catalogue and the median opens 0.00 €. The top 10% accounts for 61.9% of the total. The average visitor doesn't exist, and dividing revenue by visits gives you a number you can't decide anything with. Here's the calculation that does work, with the SQL to pull it from your own store.
The 6 store metrics that actually move money
Measured today on a real WooCommerce store with 5,621 products: 255 of them take half the clicks. That's 4.5% of the catalogue. And 4,163 products got none at all in 31 days. Here are the six metrics that actually change decisions — concentration, dead catalogue, demand with no stock — with the SQL to pull each one from your own database today.
