The Rich Results Test says "valid." Search Console's enhancement report says 1,842 valid items, zero errors. And the SERP shows a plain blue link. Nothing is broken — you're just reading a tool that answers a different question than the one you asked.
Google's own documentation is explicit on this: structured data makes a page eligible for a rich result. It does not cause one. The gap between eligible and shown is where most structured data programs quietly fail, and it's measurable if you stop counting markup and start counting impressions.
What validation actually proves
A validator does two things: it parses your markup, and it checks the extracted item against the required and recommended properties for a given feature type. That's a static check against a spec. It runs on a single fetch, in isolation, with no knowledge of your site's quality signals, no query, and no SERP.
Here's what each tool in the chain can and cannot establish:
| Tool | Proves | Does not prove |
|---|---|---|
| Schema Markup Validator | Markup parses and is valid Schema.org | Any Google feature eligibility |
| Rich Results Test | Google can extract the item from the rendered page and it meets feature requirements | That the feature will ever be shown |
| Search Console enhancement reports | Google detected valid items on indexed pages | That those items produced rich results |
| Performance report, Search Appearance dimension | The feature was actually rendered in a result the user saw | Why it wasn't shown on other impressions |
Only the last row is evidence of display. Everything above it is evidence of eligibility.
The three gates between markup and a rich result
Gate one: extraction at index time. The Rich Results Test fetches and renders the live URL on demand. The indexing pipeline uses whatever it has in the index, which may be an older render, a render that timed out, or a version served from a different cache key. If your JSON-LD is injected client-side by a tag manager or a framework hydration step, the test can pass while the indexed document contains no markup at all. The reliable check is the URL Inspection tool's indexed version — the "Crawled page" HTML and the detected enhancements for the indexed copy, not the live test.
Gate two: type-level eligibility and policy. Even with a clean extraction, Google applies per-feature rules: required properties, content policies, and a judgment about whether the markup describes the page's visible primary content. The documented requirement is that structured data must represent the main content of the page and match what users see. Review snippets marked up for content the user can't find, product markup on a category page, or recipe markup on a listing page all fail here without producing a validation error. There is also a dedicated structured data manual action in Search Console for markup that violates the guidelines — if you have one, it will be listed there, and nothing else will fix the problem.
Gate three: query-time selection. This is the one teams underestimate. Rich result rendering is decided per query, per device, per SERP layout. The same URL can show a review snippet for a branded query and a bare link for a head term, on the same day. Google has also retired features outright — in 2023 it announced that FAQ rich results would be limited to a narrow set of authoritative sites and that HowTo results were being dropped — so a feature you were "eligible" for can disappear globally as a product decision, with your markup untouched.
How to measure appearance instead of eligibility
In the Search Console Performance report, the Search Appearance tab breaks impressions down by feature type. Those rows only accrue when the feature was actually part of the result that was shown. That makes them the closest thing to ground truth you have.
The metric worth tracking is an appearance rate per page or per template:
rich result impressions for the feature ÷ total impressions for the same page, same date range, same device
A page with 50,000 impressions and 1,200 review-snippet impressions is not "working." A page at 90% is close to saturated, and further markup work on it is wasted effort.
If you have the Search Console bulk export to BigQuery, this gets precise. The URL-level table (searchdata_url_impression) carries a set of boolean is_* columns, one per search appearance type, alongside impressions and clicks. Flag names and the available set change over time, so list them from the schema rather than hardcoding your mental model:
SELECT column_name
FROM `project.searchconsole.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'searchdata_url_impression'
AND column_name LIKE 'is\\_%';
Then compute appearance rate by template:
SELECT
REGEXP_EXTRACT(url, r'https://[^/]+/([^/]+)/') AS section,
SUM(impressions) AS impressions,
SUM(IF(is_review_snippet, impressions, 0)) AS review_impressions,
SAFE_DIVIDE(SUM(IF(is_review_snippet, impressions, 0)),
SUM(impressions)) AS appearance_rate
FROM `project.searchconsole.searchdata_url_impression`
WHERE data_date BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY section
ORDER BY impressions DESC;
Two caveats. Rows with anonymized queries are still counted in impressions, so aggregate at the URL level, not the query level, when you want totals. And a single impression can carry more than one appearance flag — summing across features will exceed total impressions.
Why eyeballing the SERP is weak evidence
Checking a few queries in a browser tells you almost nothing, because gate three operates at the query level. A feature that appears on 15% of a page's impressions will look absent in a handful of manual checks, and a feature that appears on 85% will look universal. Personalization, location, device, and SERP layout experiments all move the result.
The reasonable inference — not something Google has documented — is that feature selection is competitive within a result set: if the surrounding results don't support the same enhancement, or the layout has limited vertical space, the enhancement gets dropped. You can see the shape of this in your own data by splitting appearance rate by device, where mobile rates for space-hungry features are typically lower.
What to do with this
- Stop reporting "valid items." Replace that metric in dashboards with appearance rate per template per feature. Valid items measure your deployment; appearance rate measures the outcome.
- Verify extraction on the indexed version, not the live test, for any page where markup is injected by JavaScript. If the indexed HTML lacks the JSON-LD, move it server-side before touching anything else.
- Triage by appearance rate, not error count. Templates at 0% with valid markup have a gate two or gate three problem, and more properties won't help. Templates at 40–70% are usually content-match or layout-driven and are worth testing.
- Check the manual actions report before investigating anything else when an entire feature drops to zero sitewide.
- Baseline before and after any markup change using the same Search Appearance slice, because feature availability changes on Google's schedule, not yours, and you need to distinguish your deploy from their deprecation.