schema-markup
Helps you add structured data to web pages so search engines show rich results like star ratings and FAQs, and machines can understand your content better.
Installation
Paste this into Claude Code, Cursor, or any agent that can run commands.
SKILL.mdShow the author's original SKILL.md
---
name: schema-markup
description: Write, audit and debug structured data (JSON-LD, Schema.org) so pages qualify for rich results and so machines can extract facts reliably. Use whenever the user mentions schema, structured data, JSON-LD, rich results, rich snippets, Schema.org, microdata, RDFa, star ratings in search, FAQ or HowTo markup, Product or Article or LocalBusiness or Organization markup, breadcrumbs, sitelinks, or a structured data error or warning in Search Console. Also use when adding a product, article, event, recipe, job posting, course or review page to a site.
---
# Schema Markup
Structured data does two jobs. It qualifies a page for rich results in Google, and it hands every machine that reads the page a clean set of facts instead of asking it to infer them from prose.
It is not a ranking factor, and saying otherwise is how people end up with 400 lines of JSON-LD on a page that still doesn't rank. Be honest about what it buys.
## What it actually gets you
| Type | Rich result | Worth it? |
|---|---|---|
| `Product` + `Offer` | Price, availability, review stars | Yes, on any commercial page |
| `Review` / `AggregateRating` | Stars in the SERP | Yes, if the ratings are real and on-page |
| `Article` / `NewsArticle` | Top stories, article carousels | Yes for publishers, marginal for blogs |
| `BreadcrumbList` | Path instead of a raw URL | Yes, cheap and universal |
| `Organization` | Knowledge panel, entity resolution | Yes, once, site-wide |
| `LocalBusiness` | Local pack support, hours, directions | Yes for anything with an address |
| `FAQPage` | Reduced to a trickle in Google since 2023 | Only for authoritative government and health sites now |
| `HowTo` | Retired from Google Search | No |
| `Event`, `Recipe`, `JobPosting`, `Course`, `VideoObject` | Dedicated rich results | Yes, when the page genuinely is one |
The two entries at the bottom of the "yes" list matter most and get skipped most: `Organization` and `BreadcrumbList`. Do those before anything clever.
## Rules that keep you out of trouble
1. **Use JSON-LD in a `script` tag.** Google prefers it, it's separable from markup, and microdata mixed into templates rots.
2. **Only mark up what a human can see on the page.** Marking up a rating that isn't displayed is a manual-action risk, not a clever trick.
3. **One entity per thing, linked by `@id`.** Don't repeat the full Organization object on every page. Define it once and reference it.
4. **Fill required properties, then stop.** Optional properties you can't populate honestly are noise.
5. **Validate before shipping.** The Rich Results Test tells you what Google will actually use. The Schema.org validator tells you what's syntactically legal. They disagree, and Google's answer is the one that matters.
6. **Keep it in sync with the page.** Stale prices and dead availability values are worse than no markup.
## The site-wide base
Emit this once, from the layout, on every page. Everything else references it.
```json
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Example",
"url": "https://example.com",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png",
"width": 512,
"height": 512
},
"sameAs": [
"https://www.linkedin.com/company/example",
"https://github.com/example",
"https://x.com/example"
]
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com",
"name": "Example",
"publisher": { "@id": "https://example.com/#organization" }
}
]
}
```
`sameAs` is the part people skip and the part that does the most work. It's how a search engine confirms that the company on this site is the same entity as the LinkedIn page, the GitHub org and the Crunchbase profile. See `entity-brand-consistency` for how far to take that.
## Article pages
```json
{
"@context": "https://schema.org",
"@type": "Article",
"@id": "https://example.com/blog/post#article",
"headline": "Under 110 characters, matching the visible H1",
"description": "One sentence, matching the meta description.",
"image": ["https://example.com/img/post-16x9.jpg"],
"datePublished": "2026-09-07T09:00:00+02:00",
"dateModified": "2026-09-07T09:00:00+02:00",
"author": {
"@type": "Person",
"name": "Jane Doe",
"url": "https://example.com/authors/jane-doe"
},
"publisher": { "@id": "https://example.com/#organization" },
"mainEntityOfPage": "https://example.com/blog/post"
}
```
Notes that matter more than the shape:
- **`author` must be a real, resolvable person or organization** with a URL that goes somewhere. An author page that exists is worth more than a name in a string. See `eeat-author-pages`.
- **`dateModified` must be true.** Bumping it without changing the content is a well-known pattern and it gets discounted.
- **`headline` over 110 characters** can invalidate the whole block for some rich results.
## Product pages
```json
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Product name as displayed",
"image": ["https://example.com/p/1-1x1.jpg", "https://example.com/p/1-4x3.jpg"],
"description": "Matches the on-page description.",
"sku": "ABC-123",
"brand": { "@type": "Brand", "name": "Example" },
"offers": {
"@type": "Offer",
"url": "https://example.com/p/1",
"price": "49.00",
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
"priceValidUntil": "2027-01-31",
"shippingDetails": { "@type": "OfferShippingDetails" },
"hasMerchantReturnPolicy": { "@type": "MerchantReturnPolicy" }
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "128"
}
}
```
- **`price` is a string with no currency symbol and no thousands separator.** This breaks constantly.
- **Drop `aggregateRating` entirely if there are no reviews.** Inventing one is fraud, and a `reviewCount` of 0 invalidates the block.
- **`shippingDetails` and `hasMerchantReturnPolicy`** now drive the shipping and returns annotations in Google Shopping surfaces. Most sites omit them and lose the annotation.
- **Variants** belong in `ProductGroup` with `hasVariant`, not as twelve near-identical `Product` blocks.
## Debugging structured data
When Search Console reports an error, work through it in this order:
1. **Read the exact error string.** "Missing field" and "Invalid value" have completely different causes.
2. **Fetch the rendered page**, not the source. If JSON-LD is injected client-side, Google may never see it.
3. **Check for multiple conflicting blocks.** Themes and plugins each add their own. Two `Organization` blocks with different names is worse than none.
4. **Validate the JSON itself.** A trailing comma silently kills the entire block, and no tool will tell you which page it was on.
5. **Confirm the value exists on the page.** Most "invalid" warnings are actually honesty warnings.
```bash
# Extract and validate every JSON-LD block on a page
curl -s https://example.com/page \
| grep -oPz '(?s)<script type="application/ld\+json">.*?</script>' \
| sed -e 's/<[^>]*>//g' \
| python -c "import sys,json; [json.loads(b) for b in sys.stdin.read().split('\0') if b.strip()]; print('valid')"
```
## Don't do these
- **Don't mark up content that isn't on the page.** The single fastest route to a structured data manual action.
- **Don't add `FAQPage` to every page.** Google stopped showing it for almost everyone, and a page stuffed with fake questions reads badly to humans, which still counts.
- **Don't use `HowTo`.** It's retired.
- **Don't self-serve `AggregateRating` on your own Organization.** Google ignores self-reported business ratings.
- **Don't stack every type you can think of.** A page that claims to be an Article, a Product, a FAQPage and a Course is telling a machine that it doesn't know what it is.
- **Don't expect rankings.** Structured data changes how a result looks and how reliably facts get extracted. That's the pitch, and it's a good enough one.
## For AI answer engines
Structured data isn't required for AI Overviews, AI Mode or LLM citation, and Google has said so directly. What actually helps is the same discipline applied to the visible page: state facts plainly, put them near the heading that asks the question, keep entities named explicitly, and make sure the number in the prose matches the number in the JSON.
Where schema does help is consistency. When the price, the author, the date and the organization name agree across the visible page, the JSON-LD and every off-site profile, there's nothing for a model to get wrong.
## Related skills
`technical-seo-audit`, `entity-brand-consistency`, `eeat-author-pages`, `ecommerce-seo`, `local-seo`, `video-seo`, `nextjs-seo`
Mirrored from the author's public source. Install counts from the open skills registry.