Structured data basics: JSON-LD for small sites
What structured data is, why JSON-LD is the usual format, two small examples, where the code goes, and how to avoid duplicates and extra page weight.
In this guide
What it is
Schema.org describes itself as a joint effort between search engines to improve the web with a markup vocabulary. Its FAQ says on-page markup helps search engines understand the information on web pages and provide richer search results. The vocabulary is a set of types, such as Organization, LocalBusiness and Article, and properties, such as name, url and address. Schema.org is not a formal standards body.
The 2025 Web Almanac puts it plainly: structured data is not a direct ranking factor, but it helps search engines understand the context of a page and can power rich results. Half of the home pages in its data had some, 50% on both desktop and mobile.
Lighthouse's documentation says the same about purpose. You can tell search engines that your page is an article, a job posting or an FAQ, and marking content up makes it more likely to appear in rich results. Its structured data audit is manual, so it does not affect the Lighthouse SEO score.
Three formats, one choice
Schema.org's FAQ says the markup can be written in Microdata, RDFa or JSON-LD, and that some things are much harder in Microdata, such as mixing vocabularies. In the Almanac, JSON-LD was on 43% of home pages, by far the most common, and microdata was on about 19% of inner pages. The Almanac adds that Google typically recommends JSON-LD as the easiest form to implement and maintain. Pick it for new work. It sits in one block, apart from the visible HTML, which makes it easier to write, review and remove.
Two small examples
A company-wide block for the home page:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example Plumbing Co.",
"url": "https://example.com/",
"logo": "https://example.com/img/logo.png",
"sameAs": [
"https://www.facebook.com/exampleplumbing",
"https://www.linkedin.com/company/exampleplumbing"
]
}
</script>A business with a street address, for a contact or location page:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Example Plumbing Co.",
"url": "https://example.com/",
"image": "https://example.com/img/storefront.jpg",
"address": {
"@type": "PostalAddress",
"streetAddress": "100 Example Street",
"addressLocality": "Miami",
"addressRegion": "FL",
"postalCode": "33101",
"addressCountry": "US"
},
"openingHours": "Mo-Fr 08:00-17:00"
}
</script>I checked that both blocks parse as JSON, and I checked each type and property against the schema.org vocabulary file. LocalBusiness is a subtype of Organization and Place, openingHours belongs to it, and sameAs and image belong to every Thing. The names and addresses are made up.
Schema.org's FAQ says both https://schema.org and http://schema.org are fine as the context, with https preferred in new examples.
The types you will meet
In the Almanac, WebSite was the most common type on home pages: 36% on mobile in 2025, against 35% in 2024 and 30% in 2022. Organization overtook WebPage for the first time in 2025. On inner pages the most common type was ListItem, at 27% on mobile, down from 30%. A ListItem is how a breadcrumb trail is written. Schema.org describes a BreadcrumbList as a chain of linked pages, typically described by their address and name and ending with the current page, with position numbers that begin at 1 for the top of the trail:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com/" },
{ "@type": "ListItem", "position": 2, "name": "Guides", "item": "https://example.com/guides/" },
{ "@type": "ListItem", "position": 3, "name": "Image formats", "item": "https://example.com/guides/image-formats/" }
]
}
</script>JSON-LD can also group several items under one @context with an @graph list, which the specification says saves repeating the context. Some tools write one block this way, and others write one block per item. Both are valid.
Where it goes
- In the HTML the server sends. The Almanac found that most sites give structured data in the raw HTML, and only 2% added it with JavaScript. It notes that Google can process script-injected markup, but that providing it in the initial HTML avoids potential delays and crawling problems.
- In the head or the body. Both are valid. A plugin usually puts it in the head.
- Not as a script that runs. MDN says a
scriptelement with a type that is not a script type is a data block that the browser does not process. JSON-LD is read by search engines, not run in the page.
Rules that keep it honest
- Mark up what visitors can see. Schema.org's FAQ says to mark up only the content that is visible on the page, not hidden elements.
- You do not need every property. The FAQ says it is fine to mark up only some, though more complete markup helps.
- Keep it true. Names, addresses and hours should match the page and your other listings.
- One copy of each item. The Almanac notes that markup templated at the CMS level can end up duplicated on inner pages, and that
WebSitemarkup, meant for the home page, appeared on many inner pages. - Do not add what is retired. The Almanac says the sitelinks search box that the popular
WebSiteandSearchActionmarkup powered was sunset in November 2024. There is no need to remove existing markup, and no reason to add it.
In WordPress and other builders
SEO plugins, themes and page builders often write JSON-LD, and more than one may be active. The result can be two Organization blocks that disagree, or a heavy page. See themes and plugins.
- View the page source and search for
application/ld+json. - Count the blocks. Open each and read its
@type. - If two write the same type, turn one off in its settings.
- Check the pages that matter most: the home page, a contact page and one deep page.
From a terminal, curl -s https://example.com/ | grep -c 'application/ld+json' counts the blocks on a page. This Node one-liner goes further. It lists the types in each block, reads @graph lists too, and says so when a block is not valid JSON:
curl -s https://example.com/ | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{for(const m of s.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)){try{const j=JSON.parse(m[1]);console.log(JSON.stringify([].concat(j["@graph"]||j).map(x=>x["@type"])))}catch(e){console.log("not valid JSON")}}})'I tested it on a page with a grouped block, a breadcrumb block and a broken block. It printed ["WebSite","Organization",["WebPage","FAQPage"]], ["BreadcrumbList"] and not valid JSON.
The speed side
Structured data is part of the HTML, so every byte travels with every page. The two examples above are about 300 and 430 characters. A plugin that writes a large graph for a simple page is a different story, and the HTML it adds is not cached apart from the page. Because the block is data, it does not run and does not block rendering. Keep it short, keep one copy, and measure page weight if a plugin adds a lot. See page weight and requests.
How to check
- Read it yourself. One stray comma makes a JSON block unreadable, so use a validator that checks the syntax.
- Use a testing tool. Lighthouse's audit is manual, and its documentation points to a linter and to a search engine's testing tool instead. Tool names and addresses change, so look for a current one from schema.org or a search engine.
- Compare with the page. Does every fact in the markup appear on the page?
- Check after each plugin or theme update. Output changes silently.
Mistakes to avoid
- Markup for things that are not on the page.
- Two plugins writing the same `Organization`.
- Markup built by a script that search engines may not wait for.
- Copying another company's block with its name and address.
- Old addresses and hours that no longer match the page.
- A huge graph on every page of a small site.
- Expecting a ranking boost. It supports understanding and rich results, and does not rank a page by itself.
Check your own page
Run the free audit, then view the source of your home page and one inner page. Count the application/ld+json blocks, read their @type values, and confirm that each fact in them is on the page.
Sources
- Schema.org FAQ and data model: the purpose, the three formats, marking up visible content,
httpsand the vocabulary's types and properties. - SEO chapter, Web Almanac 2025: adoption, JSON-LD against microdata, raw against JavaScript-added markup, and the common types.
- Structured data is valid in the Lighthouse documentation: what structured data is for, and why the audit is manual.
- The script element on MDN: data blocks.
- JSON-LD 1.1 at the W3C: the
@graphkeyword and how one context can serve several items.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Technical SEO · Book a call