What is JSON-LD? Schema markup in plain terms
·7 min read
JSON-LD is a way of writing structured data as plain JSON so machines can read what a page is about. On a website it sits inside a <script type="application/ld+json"> tag, uses the schema.org vocabulary, and states facts such as "this page is an article, Jane Doe wrote it, Example Co published it." Browsers don't display it, so visitors never see it. Google reads JSON-LD, microdata and RDFa, and recommends JSON-LD where your setup allows it.
If you've opened a page's source and found a block of braces with @context and @type near the top, that was JSON-LD.
What does JSON-LD stand for?
JSON-LD stands for JavaScript Object Notation for Linked Data. It's a W3C standard. The current version, JSON-LD 1.1, became a W3C Recommendation on July 16, 2020, replacing version 1.0 from January 2014. A working group chartered in January 2026 is writing version 1.2, and its charter says every valid 1.1 document will stay valid.
"Linked Data" means each key is tied to a shared vocabulary. In ordinary JSON, name means whatever the program reading it decides. In JSON-LD it means schema.org's name, and one object can point at another by an identifier, which lets search engines build a graph of pages, people, companies and products. JSON-LD is the format and schema.org is the vocabulary, so "JSON-LD schema" means schema.org types written as JSON-LD.
Four keywords cover nearly every block you'll write for search: @context, @type, @id and @graph.
A JSON-LD example, annotated
Here is a complete block for an article on example.com. It describes the company and the article in one script and connects the two.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Example Co",
"url": "https://example.com/",
"logo": "https://example.com/logo.png",
"sameAs": [
"https://www.linkedin.com/company/example-co",
"https://github.com/example-co"
]
},
{
"@type": "Article",
"@id": "https://example.com/blog/pricing-change#article",
"headline": "Why we changed our pricing",
"datePublished": "2026-10-01T09:00:00+00:00",
"dateModified": "2026-10-08T14:30:00+00:00",
"image": [
"https://example.com/images/pricing-1x1.jpg",
"https://example.com/images/pricing-16x9.jpg"
],
"author": {
"@type": "Person",
"name": "Jane Doe",
"url": "https://example.com/team/jane-doe"
},
"publisher": { "@id": "https://example.com/#organization" }
}
]
}
</script>@context
@context names the vocabulary. With "@context": "https://schema.org" at the top, keys like headline and author are schema.org terms with fixed meanings. Declare it once and every node inside @graph uses it.
@type
@type says what kind of thing a node is, which decides the properties that make sense for it. Google's general guidelines ask for the most specific type that fits, so a blog post could use BlogPosting, a subtype of Article. Type names are case-sensitive, and article in lowercase is not a schema.org type.
Properties and nested objects
Every other key is a property of its node, and a value can be text, a URL, a date or another object. author is a nested object with its own @type of Person, because the author is a person with a name and a profile page. Write dates in ISO 8601 with a time zone. Google's Article documentation recommends the time zone and otherwise uses Googlebot's.
Arrays
Square brackets hold a list, here two crops of the same image and the company's profiles in sameAs. A single value without brackets means the same thing to a JSON-LD processor as a one-item array.
@id and @graph
@id gives a node an identifier, written as a URL. The usual pattern is the page URL plus a fragment, like https://example.com/#organization. Nothing has to load at that address. It's a name. The Article's publisher points at it with { "@id": "https://example.com/#organization" } instead of repeating the name and logo.
@graph is an array of top-level nodes that share one @context. It puts the Organization and the Article side by side in one script while the @id reference states how they relate. Give the organization the same @id on every page of the site, so every block that mentions it uses one identifier.
JSON-LD vs microdata vs RDFa
JSON-LD keeps the data in its own block, while microdata and RDFa add attributes to the HTML elements visitors already see. Here is one fact, a company called Example Co and its website, written in each format.
| Format | The same fact | How it attaches |
|---|---|---|
| JSON-LD | {"@context": "https://schema.org", "@type": "Organization", "name": "Example Co", "url": "https://example.com/"} |
A separate script block in the head or body |
| Microdata | <div itemscope itemtype="https://schema.org/Organization"><span itemprop="name">Example Co</span> <a itemprop="url" href="https://example.com/">example.com</a></div> |
itemscope, itemtype and itemprop attributes on visible elements |
| RDFa | <div vocab="https://schema.org/" typeof="Organization"><span property="name">Example Co</span> <a property="url" href="https://example.com/">example.com</a></div> |
vocab, typeof and property attributes on visible elements |
The real difference is maintenance. Microdata and RDFa live inside your template markup, so a redesign that removes a span can quietly drop a property. JSON-LD is one block a developer can print from the same data that fills the page.
The catch is that JSON-LD can drift from the page, because nothing ties it to the visible text. A price in the block that no longer matches the price on screen is the classic case. Google's general structured data guidelines say not to mark up content readers can't see, so fill the block from the same source as the page.
If your theme already outputs valid microdata, leave it. What to avoid is describing the same product twice, once in each format, with values that disagree.
Does Google recommend JSON-LD?
Yes. Google's introduction to structured data says it recommends JSON-LD "if your site's setup allows it," because it is the easiest format to implement and maintain at scale and the least prone to user error. The same page says all three formats are equally fine for Google as long as the markup is valid and follows each feature's documentation.
So the recommendation is about fewer mistakes. Valid microdata and valid JSON-LD hand Google the same facts, and neither guarantees a rich result. For the rich results that still exist and the types behind them, see our guide to schema markup.
Should JSON-LD go in the head or the body?
Either works. Google describes JSON-LD as a script tag in the head or body of the page, so put it wherever your template prints it most easily.
A page can carry several JSON-LD blocks. Google's general guidelines say it understands multiple items on a page whether you nest them or write each as a separate block, and suggest @id when items belong together, such as a recipe and the video that shows it.
Watch for duplicates. A theme, an SEO plugin and a reviews app can each print their own Organization or Product block, with slightly different names or prices. Before adding another, view the source of a live page and count the application/ld+json scripts. Our guide to adding schema markup on WordPress, Shopify and other platforms covers where each one outputs its own.
Adding JSON-LD with JavaScript or a tag manager
Google reads JSON-LD that JavaScript adds once it renders the page, and crawlers that don't run JavaScript never see it. Google's guide to generating structured data with JavaScript names Google Tag Manager and custom JavaScript as the common methods. In Tag Manager you paste the block into a Custom HTML tag and publish the container.
The same guide says to fill the values from Tag Manager variables that read the page, since values typed into Tag Manager by hand can drift from the page. It also warns stores that dynamically generated Product markup can make Shopping crawls less frequent and less reliable, which hurts when prices and stock change often.
The bigger cost is outside Google. A December 2024 study by Vercel and MERJ found that none of the major AI crawlers, including GPTBot, ClaudeBot and PerplexityBot, render JavaScript. For them, a block injected by a tag manager doesn't exist. If those crawlers matter to you, write the block into the HTML your server sends. Our JavaScript SEO guide shows how to test what a non-rendering crawler receives.
JSON-LD syntax errors that break a block
One syntax error makes the whole block unreadable. A strict JSON parser stops at the first error and keeps nothing, so a stray comma near the end hides every property above it too. Search Console lists blocks Google couldn't read in its Unparsable structured data report. Watch for these four.
Trailing commas. JSON allows no comma after the last item in an object or array. JavaScript does, so developers add them out of habit.
{
"@type": "Organization",
"name": "Example Co",
}Delete the comma after "Example Co" and the block parses.
Curly quotes. JSON strings must start and end with straight double quotes. Paste a block through Word, Google Docs or a CMS editor with smart quotes on and the delimiters come out curly, which JSON parsers reject.
{ “@type”: “Organization”, “name”: “Example Co” }Curly quotes inside a value, like an apostrophe in a headline, are fine.
Unescaped quotes in text. A straight double quote inside a value ends the string early. Escape it with a backslash. The first line below breaks, the second parses.
"headline": "Why we call it "the 20-minute audit""
"headline": "Why we call it \"the 20-minute audit\""Comments. JSON has no comment syntax, so // publisher or /* TODO */ inside the script makes it invalid. Put notes in an HTML comment outside the script tag.
To find the broken block, run this in the browser console. It prints the parser's error for each JSON-LD script that fails.
document.querySelectorAll('script[type="application/ld+json"]').forEach((s, i) => {
try { JSON.parse(s.textContent); console.log(i, "ok"); }
catch (e) { console.log(i, e.message); }
});Generate valid JSON-LD for your page
Our Schema Markup Generator writes the block from fields you fill in. You pick one of 16 types, including Article, BlogPosting, Organization, Product, Event, LocalBusiness, BreadcrumbList and VideoObject. It returns the JSON-LD checked against the same rules as our Schema Markup Checker, so you see any property Google requires for that type that is still missing. It fetches nothing, doesn't choose the type for you and doesn't publish anything. A run costs 3 credits.
Paste the result into a <script type="application/ld+json"> tag and deploy. Then run the Schema Markup Checker on the live URL for 8 credits. It reads the HTML your server sends without running JavaScript, so if it finds your block, the block is in the HTML that non-rendering crawlers receive.