Schema markup is the structured data that tells search engines what a page represents: an article, a product, a local business, an event. It doesn’t change what visitors see, which is exactly why it gets skipped. But it’s what makes a page eligible for rich results such as product ratings, event dates, recipe cards, and breadcrumbs under the URL.
This is the practical walkthrough: which type to use, how to generate it, where to put it on common platforms, and how to check it worked. If you want to understand the JSON-LD format itself (@context, @id, @graph, nesting), read the companion JSON-LD schema markup guide for developers.
Step 1: Choose the schema type that matches the page
Mark up what’s actually on the page. Structured data that describes content visitors can’t see goes against Google’s guidelines and can cost you rich results.
| Page | Schema type |
|---|---|
| Homepage | Organization (or LocalBusiness) and WebSite |
| Blog post or news article | Article / BlogPosting |
| Product page | Product with Offer (and AggregateRating if you show reviews) |
| Physical business location | LocalBusiness or a subtype like Restaurant |
| Event listing | Event |
| Recipe | Recipe |
| Any page with a breadcrumb trail | BreadcrumbList |
One page can carry several types. A blog post commonly has BlogPosting plus BreadcrumbList.
A note on FAQ markup: since 2023, Google shows FAQ rich results only for well-known government and health websites. FAQPage is still valid schema.org markup, but on most sites it won’t produce the expandable questions in search results, so don’t add it expecting that.
Step 2: Generate the JSON-LD
JSON-LD is the format Google recommends: a <script> block that sits apart from your HTML, so you can add or change it without touching your templates’ markup.
You can write it by hand, but most types have several required and recommended fields, and typos in property names fail silently. The Schema Generator builds it from a form, with sample data for each type. A minimal Article looks like this:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to Add Schema Markup to Your Website",
"image": "https://example.com/images/schema-guide.jpg",
"datePublished": "2026-09-15",
"dateModified": "2026-09-15",
"author": { "@type": "Person", "name": "Jane Doe", "url": "https://example.com/about/" }
}
</script>
Use absolute URLs, ISO 8601 dates, and the same values that appear on the page.
Step 3: Add it to your pages
The script block can go in the <head> or the <body>; search engines read both. What matters is that it’s in the HTML the crawler receives.
Plain HTML or a static site: paste the block into the page’s <head>. For repeated types such as Article, put it in the template and fill the values from your content.
WordPress: SEO plugins like Yoast SEO, Rank Math, and All in One SEO already output Organization, WebSite, Article, and BreadcrumbList. Check what they generate before adding your own, or you’ll end up with duplicate or conflicting entities. For types the plugin doesn’t cover, use the plugin’s schema settings or a header-scripts plugin rather than editing theme files, which get overwritten on update.
Next.js (App Router): render the script in the page component:
export default function Page() {
const schema = { "@context": "https://schema.org", "@type": "Product", name: "Trail Shoe" };
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema).replace(/</g, "\\u003c") }}
/>
);
}
Astro:
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
The .replace(/</g, "\\u003c") in the Next.js example matters when values come from a CMS or user input: without it, a string containing </script> would end the script block early.
Avoid injecting JSON-LD only with client-side JavaScript after the page loads. Google can render JavaScript, but it happens later and less reliably than reading the initial HTML.
Step 4: Validate before and after deploying
Two free checks:
- Rich Results Test shows which Google rich result types the page is eligible for and flags missing required fields. Paste code before deploying, or test the live URL after.
- Schema Markup Validator checks the markup against the full schema.org vocabulary, including types Google doesn’t use for rich results.
The usual errors are a misspelled property name, a missing required field (for example offers on a Product), dates in the wrong format, and URLs that don’t match the page’s canonical URL.
Step 5: Monitor it in Search Console
After Google recrawls the page, Search Console lists detected structured data under Enhancements (with reports such as Products, Breadcrumbs, and Events) and reports errors across the whole site. Check it after template changes: one broken template field can invalidate the markup on every page that uses it.
Rich results are never guaranteed, even with valid markup. Google decides whether to show them. Valid, accurate markup is what makes a page eligible.
Step 6: Keep it accurate
Update dateModified when you meaningfully change the content, not on every build. Remove AggregateRating if you stop showing reviews. When prices or event dates change on the page, the markup should change with them. Generating the JSON-LD from the same data that renders the page is the easiest way to keep the two in sync.
While you’re in the <head>: social meta tags
Structured data controls rich results in search. Open Graph and Twitter Card tags control how the same page looks when someone shares it on Facebook, LinkedIn, Slack, or X. They’re separate tags, and pages often have one without the other. Run the page through the Meta Tag Preview to see the social cards, a Google result preview, and a list of missing tags.
Checklist
- Pick the types that match the visible content.
- Generate the JSON-LD with the Schema Generator or from your template data.
- Add it to the server-rendered HTML.
- Validate with the Rich Results Test and the Schema Markup Validator.
- Watch the Enhancements reports in Search Console.
- Update it whenever the page changes.