What Is JSON-LD Format? Examples and How Google Reads It
JSON-LD is the format most websites now use to describe their content to search engines and AI systems: who runs the business, what a page is about, what a product costs. It looks like ordinary JSON, but a few special keys turn plain data into linked data that a machine can understand without guessing. Many small sites copy a block from a plugin or generator without knowing what each part does, and a single misplaced comma or wrong key can make the whole block invisible to Google. This guide explains the JSON-LD format in plain terms, shows how it differs from plain JSON, walks through annotated examples, and shows you how to add, test and maintain a block that Google can actually read.
What JSON-LD means
JSON-LD stands for JavaScript Object Notation for Linked Data. It is a W3C standard, first published in 2014 and updated as JSON-LD 1.1 in 2020, for writing linked data using normal JSON syntax. The community site at json-ld.org hosts the specification links and a playground for testing.
On a website, JSON-LD is almost always used to carry structured data written with the Schema.org vocabulary. Schema.org is a shared dictionary of types such as LocalBusiness, Product, Article and FAQPage, and properties such as name, address and price. JSON-LD is the container; Schema.org supplies the words that go inside it.
The block sits in a script tag with a special media type:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Bakery",
"name": "Example Bakery",
"telephone": "+44 1632 960961",
"url": "https://example.com/"
}
</script>
Because the type is application/ld+json and not text/javascript, the browser does not run it and visitors never see it. Search engines, AI crawlers and other tools read it as data. That separation is why Google recommends JSON-LD over the older Microdata and RDFa formats: you can add, change or remove it without touching the visible HTML.
JSON vs JSON-LD: the actual difference
Every JSON-LD document is valid JSON. The difference is that JSON-LD gives certain keys a fixed meaning, so a program that has never seen your data before can still work out what it describes. Plain JSON has no such agreement: a key called name could mean anything, and only the developer who wrote it knows.
| Point | Plain JSON | JSON-LD |
|---|---|---|
| Syntax | Objects, arrays, strings, numbers, true, false, null | Exactly the same syntax |
| Meaning of keys | Private to the app that uses it | Defined by a vocabulary named in @context |
| Identity | No standard way to say two records are the same thing | @id gives each thing a global identifier |
| Type | Usually a custom field, if any | @type points to a defined class |
| Typical use | APIs, config files, app data | Structured data for search engines, knowledge graphs |
| Media type | application/json | application/ld+json |
So the honest answer to json vs json-ld is that you are not choosing between two formats. JSON-LD is JSON with an agreed set of rules on top. Anything that can parse JSON can parse JSON-LD; it simply will not understand the linked data part unless it knows the rules.
This also means a JSON syntax error breaks JSON-LD completely. A trailing comma, a smart quote pasted from a word processor or a missing bracket stops the parser before it reads a single property. The JSON syntax guide lists the common mistakes and how to spot them.
The special keys: @context, @type, @id and @graph
JSON-LD reserves keys that start with @. You only need four of them for nearly all website structured data.
@context
Tells the reader which vocabulary the other keys come from. For search, it is "https://schema.org". With that context, name means the Schema.org property name and nothing else. Google accepts http or https and with or without a trailing slash, but use https://schema.org consistently.
@type
Says what kind of thing the object is. It must match a Schema.org type exactly, including capitals: LocalBusiness works, localbusiness does not. You can give more than one type as an array, for example ["WebPage", "TechArticle"].
@id
A unique identifier for the thing, written as a URL. It does not have to be a page that loads; it is a name. The convention is your page URL plus a fragment, such as https://example.com/#business. Once an object has an @id, other objects can point to it instead of repeating it. The guide to schema @id values covers naming patterns and common clashes.
@graph
Holds several top-level objects in one block, sharing a single @context. This is how WordPress SEO plugins output a website, a web page, a breadcrumb trail and a business in one script tag.
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Bakery",
"@id": "https://example.com/#business",
"name": "Example Bakery",
"url": "https://example.com/"
},
{
"@type": "WebPage",
"@id": "https://example.com/opening-times/#webpage",
"url": "https://example.com/opening-times/",
"name": "Opening times",
"publisher": { "@id": "https://example.com/#business" }
}
]
}
The publisher property contains only an @id. A reader joins the two objects together, so the page is published by the bakery described above. That joining is the linked part of linked data.
@id reference that points to nothing on the page is not an error in the JSON-LD standard, but Google cannot use it either. Make sure every referenced @id is defined somewhere in the page's structured data.JSON-LD examples, annotated
A local business
{
"@context": "https://schema.org",
"@type": "Bakery",
"@id": "https://example.com/#business",
"name": "Example Bakery",
"telephone": "+44 1632 960961",
"url": "https://example.com/",
"address": {
"@type": "PostalAddress",
"streetAddress": "1 Example Street",
"addressLocality": "Exampletown",
"postalCode": "EX1 1AA",
"addressCountry": "GB"
},
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "17:00"
}]
}
Notice that address is a nested object with its own @type. Nesting is how JSON-LD says that a property's value is itself a thing with properties. Country codes use two-letter ISO codes, and times use 24-hour format.
An article
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How we bake sourdough",
"datePublished": "2026-03-02T09:00:00+00:00",
"dateModified": "2026-09-14T11:30:00+01:00",
"author": { "@type": "Person", "name": "Sam Example" },
"image": "https://example.com/images/sourdough.jpg"
}
Dates use ISO 8601. Include a time zone offset so the date cannot be misread. More date rules are in the schema dates guide.
A product
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Seeded rye loaf",
"image": "https://example.com/images/rye.jpg",
"offers": {
"@type": "Offer",
"price": "4.50",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock"
}
}
Values such as availability are written as full Schema.org URLs. The price is a plain number without a currency symbol, and the currency goes in its own property. The product schema guide covers the properties Google needs for product results.
How Google reads JSON-LD
Google's documentation says it supports JSON-LD, Microdata and RDFa, and recommends JSON-LD. In practice, this is what happens when Googlebot fetches a page:
- It reads every
application/ld+jsonscript in the HTML, in the head or the body. Position does not matter. - It parses each block as JSON. A block that fails to parse is dropped, and Search Console may report it as an unparsable structured data item.
- It renders the page. JSON-LD added by JavaScript after load can also be read at this stage, as long as rendering succeeds, though server-rendered markup is more dependable.
- It matches objects to the features it supports, such as product snippets, breadcrumbs or local business details, and checks each against that feature's required and recommended properties.
- It decides whether to show a rich result. Valid markup makes a page eligible; it does not guarantee anything appears.
Google does not need one block per page. Several separate blocks, or one block with a @graph, both work. What matters is that the markup describes content visitors can actually see on the page, which is part of Google's structured data guidelines. Marking up prices, opening hours or questions that are not on the page breaks those guidelines and can lead to a manual action.
| What you see | What it usually means |
|---|---|
| Rich Results Test shows no items | The block failed to parse, uses a type Google has no feature for, or is not in the fetched HTML |
| Item detected with errors | A required property for that feature is missing or the wrong kind of value |
| Item valid with warnings | Optional properties are missing; the page can still be eligible |
| Schema Markup Validator passes, Rich Results Test shows nothing | The markup is valid Schema.org, but the type is not one Google uses for a rich result |
The last row confuses many people. Schema.org has hundreds of types and Google builds features for a small number of them. Other types are still read and can help Google and AI systems understand the page, but they will not show in the Rich Results Test. The guide to testing schema and rich results explains both tools in depth.
Where JSON-LD comes from on common platforms
- WordPress: SEO plugins such as Yoast and Rank Math write a
@graphblock automatically. Adding a second plugin or a theme that also writes schema creates duplicates. See JSON-LD on WordPress. - Shopify: most themes print product JSON-LD from a Liquid snippet. Apps can add more. Check the theme code before installing a schema app. See JSON-LD on Shopify.
- Next.js and React: render the block on the server with a script tag and
JSON.stringify. See JSON-LD in Next.js. - Hand-coded sites: paste the block in the head of each page, or include it from a shared template so it cannot drift between pages.
To see what a live page really serves, fetch the raw HTML and pull out the blocks:
curl -s https://example.com/ | grep -o '<script type="application/ld+json">.*</script>'
If a block spans several lines, the grep shows nothing, so open the page in Chrome, press Ctrl+U for the source and search for ld+json instead. In DevTools, the Console line below lists every block the page holds after rendering:
[...document.querySelectorAll('script[type="application/ld+json"]')].map(s => JSON.parse(s.textContent))
If that line throws an error, one of the blocks is not valid JSON.
The fix, step by step
Step 1
Decide what the page describes
Before writing any code, pick the single main thing the page is about: a business, a product, an article, an event. Find the matching Schema.org type on schema.org, such as Bakery for a bakery or Product for a shop item. Note the properties Google lists as required and recommended for that feature in its Search Central documentation. You will know this step is done when you have one type name and a short list of properties whose values are all visible on the page itself.
Step 2
Write the block with @context and @type
Open a plain text editor, not a word processor, so quotes stay straight. Start with an object containing "@context": "https://schema.org" and "@type" set to your chosen type, spelt with the exact capitals Schema.org uses. Add each property as a key in double quotes followed by its value. Nest objects such as PostalAddress or Offer with their own @type. Paste the result into a JSON validator or the JSON-LD Playground and confirm it parses without a single error.
Step 3
Give important things an @id
Add an @id to every object you may want to refer to from elsewhere, such as the business or the website. Use your real domain plus a fragment, for example https://example.com/#business, and use exactly the same value on every page. Where one object refers to another, such as a page publisher, replace the repeated details with a reference containing only that @id. Check that each reference has a matching definition by searching the block for the identifier string.
Step 4
Wrap it in the script tag
Place the JSON inside a script element with type="application/ld+json". Put it in the page head or anywhere in the body; both work. Do not add async, defer or a src attribute, because the data must be inline. If your platform has a header code box or a theme template, use that so the block appears on every page of that type. Save, then view the page source with Ctrl+U and search for ld+json to confirm the block is present.
Step 5
Check the served HTML
Fetch the live page as a crawler would with curl -s https://example.com/page/ and confirm the block appears in the raw HTML, not only after JavaScript runs. Make sure caching or minification plugins have not stripped quotes or merged the block into another script. If the block is missing from the raw HTML but present in DevTools, it is being injected client side, which Google can still read after rendering but which is less dependable than server-side output.
Step 6
Test with the Rich Results Test
Paste the URL into Google's Rich Results Test and run it with the default smartphone crawler. Open each detected item and read any errors and warnings. Errors mean a required property is missing or malformed and the item cannot produce a rich result; warnings mean optional properties are missing. Fix errors first, then decide which warnings are worth filling. A green valid item for your chosen feature confirms Google can read and use the block on that page.
Step 7
Validate against Schema.org
Run the same URL or code through the Schema Markup Validator at validator.schema.org. It checks every type and property against the full vocabulary, including types Google has no rich result for, and flags misspelt property names that the Rich Results Test simply ignores. Correct any unknown properties or wrong value types. When the validator shows your objects with no errors and the properties grouped under the right types, the markup is correct as linked data.
Step 8
Monitor in Search Console
After Google recrawls the page, open the relevant report under Enhancements or Shopping in Search Console, such as Breadcrumbs or Product snippets. Use URL Inspection on the page and open the detected structured data to confirm Google saw the same items you tested. If an item shows as invalid, fix it and use Validate fix on the report. Repeat this check after every theme, plugin or template change, because that is when blocks most often break.
Edge cases worth knowing
- HTML inside values. A description containing
</script>ends the block early. Escape the slash as<\/script>or strip HTML from text values. - Smart quotes. Curly quotes copied from a document are not valid JSON delimiters. Use straight double quotes only; single quotes are not allowed for keys or strings.
- Comments. JSON has no comments. A
//note left inside the block makes it unparsable. - Numbers as text.
"price": "4.50"and"price": 4.50are both accepted by Google, but a value like"ยฃ4.50"is not a valid price. - Case of keys.
@TypeorNameare different keys from@typeandname, and are ignored. - Unknown properties. A misspelt property such as
telephonis ignored silently by most tools. The Schema Markup Validator flags it, so run both tools. - Several blocks describing the same thing. Two separate business objects without a shared
@idlook like two different businesses. Give repeated things the same@idso they merge.
Keeping your JSON-LD correct
JSON-LD rarely breaks on its own. It breaks when something else changes: a theme update, a new plugin, a price change that never reached the markup, or a page template edited by someone who did not know the block was there. A few habits keep it healthy:
- Generate the block from the same data that fills the visible page, so prices, hours and names cannot drift apart.
- Keep one source of schema per page type. If a plugin writes it, do not also paste it by hand.
- Re-test a sample of templates after every theme or plugin update, not only the home page.
- Watch the enhancement reports in Search Console for new invalid items.
- Run a crawl that checks every page's blocks parse, so a single broken template is found in one pass. Our JSON checker guide shows how.
1. What is JSON-LD format?
JSON-LD is a way of writing linked data in ordinary JSON, using a few reserved keys such as @context, @type and @id to give the data an agreed meaning. On websites it carries Schema.org structured data inside a script tag with the type application/ld+json, so search engines and AI systems can read facts about the page, such as a business name, a product price or an article date, without parsing the visible text. Browsers ignore the block, so visitors never see it and it does not affect the layout.
2. What is the full form of JSON-LD?
JSON-LD stands for JavaScript Object Notation for Linked Data. JavaScript Object Notation is the common data format made of objects, arrays and simple values, and Linked Data is the idea of giving things global identifiers so that data from different places can be connected. JSON-LD became a W3C Recommendation in 2014, and version 1.1 followed in 2020. The specification and a free playground for testing documents are linked from json-ld.org, which is run by the community that maintains the format.
3. What is the difference between JSON and JSON-LD?
JSON-LD is JSON with extra rules that give certain keys a shared meaning. The syntax is identical, so any JSON parser can read a JSON-LD block. The difference is that JSON-LD names a vocabulary in @context, gives things a type with @type and an identity with @id, so a program that has never seen your data can still understand it. Plain JSON keys only mean what the developer of one application decides, which is fine for APIs but not for search engines.
4. Does Google prefer JSON-LD over Microdata?
Yes, Google recommends JSON-LD for structured data, while still supporting Microdata and RDFa. JSON-LD is easier to add and maintain because it sits in its own script block, separate from the visible HTML, so you can change it without editing your templates around headings and paragraphs. Google treats valid markup in any of the three formats the same way for rich result eligibility, so there is no need to rewrite working Microdata in a hurry, but new markup is best written in JSON-LD.
5. Where should I put JSON-LD on the page?
You can put JSON-LD in the head or anywhere in the body, and Google reads it in either place. Most platforms place it in the head through a plugin or theme setting, which keeps it in one predictable spot. What matters more is that the block is present in the HTML Google fetches or renders, that it is valid JSON, and that it describes content visitors can actually see on that page. Avoid loading it from an external file, because the data must be inline.
6. Can a page have more than one JSON-LD block?
Yes, a page can have several JSON-LD blocks, and Google reads all of them. Many sites have one block from an SEO plugin and another from a theme or app. The risk is duplication: two blocks describing the same business or product with different details can confuse search engines. Either combine everything into one block with a @graph array, or give repeated things the same @id so they are understood as one entity and their properties are merged.
7. Why does my JSON-LD not show in the Rich Results Test?
Usually the block either failed to parse as JSON or uses a type that Google has no rich result for. A trailing comma, a curly quote or a stray comment stops parsing completely, so check the code in a JSON validator first. If it parses, run the Schema Markup Validator: if your items appear there but not in the Rich Results Test, the markup is valid Schema.org but not linked to a Google feature, which is normal for many types and not an error.
The JSON checker on aiwebpageseo.com fetches your pages, extracts every application/ld+json block and reports parse errors, missing types and broken @id references, so you can fix a template once instead of page by page.
Open JSON Fixes โ