The ticket says "Add schema to the product pages." Simple enough. You search for "JSON-LD schema," and half the results are about JSON Schema, which turns out to be something else entirely. The other half are SEO blogs showing FAQ markup that Google stopped rewarding years ago.
Structured data is one of the most useful parts of technical SEO and one of the most misunderstood. Done well, it tells search engines exactly what a page is about: this is a product, here's its price, this is the company that sells it. Done badly, it's a block of stale, hand-written JSON that quietly contradicts the page it sits on.
In this guide, I'll untangle the terminology, walk through a JSON-LD block line by line, cover the schema types still worth adding in 2026 (and the ones Google has dropped), show how to link entities with @graph, generate the markup safely from your data, and test it automatically.
Disclosure: I work with the team behind SEO Site Checker, whose tools I mention below. Everything else here is tool-agnostic.
The Short Version
- JSON-LD is a format. Schema.org is the vocabulary you use inside it. Together, they're what people mean by "JSON-LD schema."
-
Google recommends JSON-LD, and it can go in the
<head>or the<body>. - Only mark up content that's visible on the page. No hidden or invented data.
- Structured data helps Google understand a page and can enable rich results, but it doesn't guarantee them.
- Generate it from your data, inject it safely and validate it in CI.
First, Clear Up the Naming Confusion
Three different things get mixed up here, so let's separate them:
-
JSON-LD stands for JSON for Linked Data. It's a W3C standard for expressing linked data in ordinary JSON, using special keywords like
@context,@typeand@id. - Schema.org is a shared vocabulary of types (Product, Organization, Article, Event and hundreds more) and their properties. It's maintained collaboratively and backed by the major search engines.
- JSON Schema is an unrelated specification for describing and validating the shape of JSON documents, usually API requests and responses. It has nothing to do with SEO.
Then there's structured data markup, the umbrella term for adding machine-readable data to a page. It can be written as JSON-LD, Microdata or RDFa. Microdata and RDFa weave attributes into your HTML, while JSON-LD lives in its own script tag, which is why it's much easier to generate and maintain.
So when an SEO ticket says "JSON-LD schema," it means Schema.org vocabulary written in JSON-LD. You can close the JSON Schema tabs.
Anatomy of a JSON-LD Block
Here's a minimal, valid Organization block:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Acme Tools",
"url": "https://example.com",
"logo": "https://example.com/images/logo.png",
"sameAs": [
"https://www.linkedin.com/company/acme-tools",
"https://www.instagram.com/acmetools"
],
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-555-555-0123",
"contactType": "customer service"
}
}
</script>
Line by line:
-
<script type="application/ld+json">tells the browser this is data, not code to execute. Nothing renders on the page. -
@contextsays which vocabulary the keys come from, which here is Schema.org. -
@typeis the kind of thing you're describing. Types use PascalCase and are case-sensitive, soOrganizationworks andorganizationdoesn't. -
Properties like
name,urlandlogodescribe it. Properties use camelCase. -
Nested objects have their own
@type. Here,contactPointis a ContactPoint, not just a loose object. -
Arrays hold multiple values.
sameAslists your official profiles elsewhere, which helps search engines connect them to the same entity.
Use full absolute URLs everywhere, and make sure every value is valid JSON: double quotes only, and no trailing commas.
Where to Put It and How Google Reads It
Google's introduction to structured data recommends JSON-LD and says it can go in either the <head> or the <body>. A page can have several JSON-LD blocks, so a product page might have one for the product and another for breadcrumbs.
Google can also read JSON-LD injected with JavaScript after the page loads. That works, but server-rendering it is more reliable, because it's in the HTML from the first byte and other crawlers that don't run JavaScript can see it too.
The rule that matters most is this one: structured data must describe content that's visible on that page. If the JSON-LD says a product costs $49 and is in stock, the page should say the same. Marking up hidden content, or data that isn't on the page at all, goes against Google's guidelines and can lead to the markup being ignored.
The Schema Types Worth Adding in 2026
You don't need dozens of types. For most sites, these cover the important ground. The full vocabulary lives on Schema.org if you need something more specific.
Organization or LocalBusiness
Every site should describe who's behind it. Use Organization for most businesses, or LocalBusiness (or a more specific subtype, like Dentist or Restaurant) if customers visit a physical location:
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Happy Paws Grooming",
"url": "https://happypaws.example",
"telephone": "+1-555-555-0188",
"address": {
"@type": "PostalAddress",
"streetAddress": "12 Main Street",
"addressLocality": "Westfield",
"addressRegion": "NJ",
"postalCode": "07090",
"addressCountry": "US"
},
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "09:00",
"closes": "17:00"
}]
}
WebSite
A small block on your homepage that states your site's name, which helps Google show the right site name in results:
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Acme Tools",
"url": "https://example.com"
}
BreadcrumbList
This should mirror the breadcrumbs users actually see on the page:
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Power Tools", "item": "https://example.com/power-tools" },
{ "@type": "ListItem", "position": 2, "name": "Drills", "item": "https://example.com/power-tools/drills" }
]
}
Article or BlogPosting
For editorial content, include the headline, author, dates and a representative image. Keep dateModified honest. Bumping it without real changes doesn't fool anyone.
Product with Offer and AggregateRating
For ecommerce, this is the big one. Price, currency, availability and, if you have them, reviews. Only include ratings from genuine reviews that are visible on the page. Self-written or invented ratings break Google's guidelines.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Cordless Drill 18V",
"image": ["https://example.com/images/drill-1600.jpg"],
"description": "Lightweight 18V cordless drill with two batteries.",
"sku": "ACME-DRL-18",
"brand": { "@type": "Brand", "name": "Acme Tools" },
"offers": {
"@type": "Offer",
"url": "https://example.com/products/cordless-drill-18v",
"priceCurrency": "USD",
"price": "129.00",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "87"
}
}
Others, when they genuinely fit
Event, Recipe, JobPosting and VideoObject are all well supported when your content actually is one of those things. Don't force a type onto a page that isn't that type.
What Changed: Rich Results Google No Longer Shows
This is the part most JSON-LD guides haven't caught up with. Google has been steadily trimming which structured data features produce special search results:
- FAQ rich results have been limited to well-known, authoritative government and health websites since 2023. For almost everyone else, FAQ markup no longer produces the expandable questions in search.
- HowTo rich results were dropped.
- In June 2025, as part of simplifying the search results page, Google phased out seven more features: Book Actions, Course Info, Claim Review, Estimated Salary, Learning Video, Special Announcement and Vehicle Listing.
- In November 2025, Google said Practice Problems markup would be deprecated, and that Dataset markup would only be used in Dataset Search rather than in main search results.
What should you do with markup for deprecated features? Nothing dramatic. Leaving it in place won't trigger errors or hurt your site, and some markup is still useful to other consumers of your data. Just don't add new markup expecting a rich result that no longer exists, and don't promise a client those FAQ dropdowns.
One more expectation to set: Google describes structured data as a way to help it understand your content and enable richer displays. It doesn't describe it as a ranking boost. Good structured data makes a good page easier to understand. It won't rescue a weak one.
Linking Entities With @graph and @id
A single page often describes several connected things: the organization, the website, the web page and the article with its author. Instead of repeating the organization in every block, you can put everything in one @graph and connect the pieces with @id references:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Acme Tools",
"url": "https://example.com",
"logo": "https://example.com/images/logo.png"
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"name": "Acme Tools",
"url": "https://example.com",
"publisher": { "@id": "https://example.com/#organization" }
},
{
"@type": "Person",
"@id": "https://example.com/team/jay#person",
"name": "Jay Barker",
"url": "https://example.com/team/jay"
},
{
"@type": "BlogPosting",
"@id": "https://example.com/blog/choosing-a-drill#article",
"headline": "How to Choose a Cordless Drill",
"datePublished": "2026-12-01",
"dateModified": "2026-12-01",
"author": { "@id": "https://example.com/team/jay#person" },
"publisher": { "@id": "https://example.com/#organization" },
"isPartOf": { "@id": "https://example.com/#website" },
"mainEntityOfPage": "https://example.com/blog/choosing-a-drill"
}
]
}
An @id is just a unique identifier, conventionally a URL with a hash fragment. It doesn't need to resolve to a page, but it must be consistent. Use the same @id for your organization on every page, and search engines can understand that it's one entity rather than many.
Generate It From Data, Don't Hand-Write It
Hand-written JSON-LD has a fatal flaw: it drifts. Someone changes a price in the CMS, and the structured data still says the old one. The fix is to generate JSON-LD from the same data that renders the visible page.
Here's a small, reusable component for Next.js:
// components/JsonLd.tsx
type JsonLdProps = { data: Record<string, unknown> };
export function JsonLd({ data }: JsonLdProps) {
// Escape "<" so a value like "</script>" can't break out of the tag
const json = JSON.stringify(data).replace(/</g, '\\u003c');
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: json }}
/>
);
}
And using it on a product page in the App Router:
// app/products/[slug]/page.tsx
import { JsonLd } from '@/components/JsonLd';
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const product = await getProduct(slug);
const productJsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.images.map((img) => img.url),
description: product.summary,
sku: product.sku,
brand: { '@type': 'Brand', name: product.brand },
offers: {
'@type': 'Offer',
url: `https://example.com/products/${slug}`,
priceCurrency: 'USD',
price: product.price.toFixed(2),
availability: product.inStock
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
};
return (
<>
<JsonLd data={productJsonLd} />
{/* the visible product page, rendered from the same `product` data */}
</>
);
}
Don't skip the escaping. JSON.stringify produces valid JSON, but it doesn't make that JSON safe to drop inside a <script> tag. If any value comes from users or editors, such as a product name or a review, a string containing </script> can close the tag early and inject arbitrary HTML. That's a cross-site scripting (XSS) hole. Replacing every < with < keeps the JSON identical once it's parsed, while making it impossible to break out of the script tag.
The same pattern works anywhere. In Astro, Nuxt, Rails or a PHP template, build a plain object from your data, serialize it, escape < and render it in a script tag from one shared component. If you're on a CMS, plugins can output schema for you. Just check what they produce, because two plugins each emitting their own Organization block is a surprisingly common mess.
Testing and Validation
There are several layers of checking, and each catches different problems:
- Google's Rich Results Test shows whether a page is eligible for Google's rich results and flags errors in the properties Google cares about.
- The Schema Markup Validator at validator.schema.org checks general Schema.org validity, including types Google doesn't use for rich results.
- Google Search Console shows rich result reports across your site, and the URL Inspection tool confirms what Google actually found on a specific page.
For quick manual checks, it helps to format and inspect the raw JSON before anything else. SEO Site Checker has free developer tools, including a JSON viewer and formatter, that make a minified block readable in seconds. For a broader view, a site-wide audit that checks structured data flags pages where it's missing or broken.
But the real win is automation. Here's a small Node script (Node 18 or newer) that fetches pages, extracts every JSON-LD block, confirms it parses, and checks that each node has a @type and the properties you consider required:
// scripts/check-jsonld.mjs
// Usage: node scripts/check-jsonld.mjs https://staging.example.com/ https://staging.example.com/products/sample
// Adjust to the properties you require for the features you care about
const REQUIRED = {
Organization: ['name', 'url'],
LocalBusiness: ['name', 'address'],
Product: ['name', 'offers'],
BlogPosting: ['headline', 'datePublished'],
Article: ['headline', 'datePublished'],
BreadcrumbList: ['itemListElement'],
};
let failed = false;
const fail = (url, msg) => { console.error(`FAIL ${url}: ${msg}`); failed = true; };
const SCRIPT_RE = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
for (const url of process.argv.slice(2)) {
const html = await (await fetch(url)).text();
const blocks = [...html.matchAll(SCRIPT_RE)].map((m) => m[1]);
if (blocks.length === 0) { fail(url, 'no JSON-LD found'); continue; }
for (const raw of blocks) {
let data;
try { data = JSON.parse(raw); }
catch (err) { fail(url, `invalid JSON: ${err.message}`); continue; }
const nodes = (Array.isArray(data) ? data : [data]).flatMap((d) => d['@graph'] ?? [d]);
for (const node of nodes) {
const types = [].concat(node['@type'] ?? []);
if (types.length === 0) fail(url, 'a node has no @type');
for (const type of types) {
for (const prop of REQUIRED[type] ?? []) {
if (node[prop] === undefined) fail(url, `${type} is missing "${prop}"`);
}
}
}
}
}
process.exit(failed ? 1 : 0);
It's deliberately simple. It won't replace Google's tools, but it will fail your build the moment a template change breaks the JSON or drops a property you rely on, which is exactly when you want to know.
Does Structured Data Help With AI Search?
It's the question I get most often now, so here's the honest answer. Google says there's no special schema you need to add to appear in AI Overviews or AI Mode. Its AI features rely on the same indexing and quality systems as regular search, and the same structured data guidelines apply.
That doesn't make structured data useless for AI. Clear, accurate structured data markup removes ambiguity. It states plainly that this page is about a product called "Cordless Drill 18V," made by this brand, costing this much and in stock. Any system reading your page, whether it's a search engine, an AI assistant, a shopping feed or a voice assistant, benefits from facts that are explicit rather than inferred. Consistent @ids and sameAs links also help connect your business to the same entity across the web.
What it won't do is get your content cited by an AI tool on its own. Be wary of anyone selling a special "AI schema" that promises AI visibility. The fundamentals still decide that: useful content, a trusted reputation and pages that can be crawled.
A Practical Rollout Plan
If you're adding structured data to an existing site, don't try to mark up everything at once. Roll it out in this order:
-
Sitewide entities first. Add Organization (or LocalBusiness) and WebSite once, ideally in a shared layout, with stable
@ids. - Your money templates next. Product pages for ecommerce, service or location pages for local businesses, articles for publishers. One template change covers hundreds of pages.
- Breadcrumbs everywhere they're visible.
- Add the CI check from the testing section before you move on, so nothing regresses.
- Watch Search Console for a few weeks. Fix errors and warnings in the rich result reports, and confirm with URL Inspection that Google sees what you expect.
Then stop, and only add more types when a page genuinely is one of them. More schema isn't better schema.
Common Mistakes
- Markup that doesn't match the page, like a price, rating or availability that differs from what users see.
- Fake or self-written reviews in AggregateRating.
-
Relative URLs in
url,imageoritem. - The wrong type, such as Article on a product page.
- Several conflicting Organization blocks, usually from a theme and a plugin both adding their own.
- Invalid JSON, most often from trailing commas or single quotes in hand-written blocks.
- Expecting FAQ or HowTo rich results in 2026.
- Unescaped user content inside the script tag.
FAQs
What is JSON-LD schema?
JSON-LD schema usually means Schema.org structured data written in the JSON-LD format. It's a script block, typed application/ld+json, that describes what a page is about, such as a product, business or article, in a machine-readable way that search engines can understand.
What's the difference between JSON-LD and JSON Schema?
They're unrelated. JSON-LD is a format for linked data, used with the Schema.org vocabulary to describe web content for search engines. JSON Schema is a specification for validating the structure of JSON documents, typically API payloads. Only JSON-LD is relevant to SEO.
Is JSON-LD better than Microdata?
For most sites, yes. Google recommends JSON-LD because it sits in its own script block, separate from your HTML, which makes it easier to generate, maintain and debug. Microdata and RDFa are still valid, but they tie your structured data to your markup, which gets messy fast.
Should JSON-LD go in the head or the body?
Either works. Google reads JSON-LD in both the head and the body, and a page can have more than one block. Put it wherever your framework makes it easiest to generate from the page's data, ideally server-rendered so it's in the initial HTML.
Does schema markup help SEO rankings?
Not directly. Google describes structured data as a way to help it understand your content and to enable rich results, not as a ranking boost. It can still help indirectly, for example by making your results more informative, but it won't make a weak page rank.
Can I add JSON-LD with JavaScript or Google Tag Manager?
Yes. Google can read JSON-LD that's injected with JavaScript, including through Google Tag Manager. Server-rendering it is more reliable, though, because it's present in the initial HTML and visible to crawlers that don't run JavaScript. Either way, keep it in sync with the visible content.
Does valid JSON-LD guarantee rich results?
No. Valid markup makes a page eligible, but Google decides whether to show rich results based on many factors, and it has retired several rich result types in recent years. Use the Rich Results Test to check eligibility, and treat any rich result as a bonus.
Wrapping Up
Treat structured data like any other output of your code. Generate it from real data, inject it safely, keep it truthful to what's on the page and test it in CI. JSON-LD won't rank a page on its own, but it makes your content far easier for search engines, and the growing number of other machines reading the web, to understand correctly.
Top comments (1)
Solid guide! One important rule we enforce for all our web architectures is never injecting client-side hydrated schema for core entities (like Organization or Article). Googlebot loves raw static JSON-LD in the initial SSR payload, ensuring zero delay before Google's indexer picks up rich snippets.