Search engines are good at reading pages, but they still guess. Is this number a price or a model number? Is this name the author or someone quoted in the article? Structured data removes the guesswork: a block of machine-readable data that says exactly what the page is about, using the shared schema.org vocabulary.
Done right, it makes pages eligible for rich results such as product prices and ratings, article details and breadcrumb trails, and helps search engines understand who is behind a site. Done wrong, it's ignored, or in the case of misleading markup, it can lead to a manual action.
Why JSON-LDLink to this section
There are three syntaxes: Microdata, RDFa and JSON-LD. Use JSON-LD. It's recommended by Google, lives in one <script> tag separate from your HTML, and is easy to generate from data you already have:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Caching for WordPress and PHP sites",
"datePublished": "2026-10-22T13:00:00+05:30",
"author": { "@type": "Person", "name": "Hemant Adhikari", "url": "https://www.hemantinsights.com/about" }
}
</script>You can place it in the <head> or <body>. Several blocks per page are fine, or use one block with an @graph array (shown below).
The ground rulesLink to this section
- Mark up what is visible. If the page doesn't show a price, rating or FAQ, the structured data mustn't claim one.
- Keep values identical. The headline, price and dates in the JSON-LD should match what users see.
- Use the most specific type that fits:
BlogPostingorNewsArticlerather thanCreativeWork,Productfor things you sell. - Required and recommended properties are listed in Google's documentation for each rich result. Missing required properties make a page ineligible; missing recommended ones just reduce what's shown.
- Structured data is not a ranking shortcut. It helps understanding and appearance, not position.
The types most sites needLink to this section
Organization (or Person) and WebSiteLink to this section
Describe who publishes the site once, usually on the home page:
{
"@type": "Organization",
"@id": "https://www.example.com/#organization",
"name": "Acme Interiors",
"url": "https://www.example.com/",
"logo": "https://www.example.com/logo.png",
"sameAs": ["https://www.linkedin.com/company/acme-interiors"],
"contactPoint": { "@type": "ContactPoint", "contactType": "customer support", "email": "hello@example.com" }
}For a personal brand, use Person with jobTitle, url, image and sameAs links to real profiles. sameAs should only list profiles that are actually yours.
Article / BlogPostingLink to this section
{
"@type": "BlogPosting",
"headline": "Speeding up a WooCommerce store",
"description": "Cart fragments, HPOS, caching and checkout fixes.",
"image": ["https://www.example.com/covers/woocommerce-speed.jpg"],
"datePublished": "2026-10-22T14:00:00+05:30",
"dateModified": "2026-10-22T14:00:00+05:30",
"author": { "@id": "https://www.example.com/#person" },
"publisher": { "@id": "https://www.example.com/#organization" },
"mainEntityOfPage": "https://www.example.com/blog/speed-up-woocommerce-store"
}Use full ISO 8601 dates with a time zone, update dateModified only for meaningful changes, and give images at least 1,200 pixels wide.
ProductLink to this section
For e-commerce, Product with an Offer enables price, availability and, where you have real reviews, ratings in results:
{
"@type": "Product",
"name": "Oak Dining Chair",
"image": ["https://www.example.com/img/oak-chair.jpg"],
"sku": "CH-OAK-01",
"brand": { "@type": "Brand", "name": "Acme" },
"offers": {
"@type": "Offer",
"url": "https://www.example.com/product/oak-dining-chair",
"price": "149.00",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition"
}
}Variable products can use AggregateOffer with lowPrice and highPrice, or ProductGroup with variants. Never add aggregateRating without real reviews on the page.
BreadcrumbListLink to this section
Breadcrumbs show the page's place in the site and often replace the URL in results:
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Blog", "item": "https://www.example.com/blog" },
{ "@type": "ListItem", "position": 2, "name": "SEO", "item": "https://www.example.com/blog/category/seo" },
{ "@type": "ListItem", "position": 3, "name": "Structured data for developers" }
]
}The last item can omit item, since it's the current page.
LocalBusinessLink to this section
Businesses with a physical location can use LocalBusiness (or a more specific subtype) with address, geo, openingHoursSpecification and telephone. Keep it consistent with the Google Business Profile.
A note on FAQ and HowToLink to this section
FAQPage rich results are now shown only for a small set of authoritative government and health sites, and HowTo rich results have been retired. The markup is still valid schema.org and harmless, but don't expect visual results from it. Spend the effort on Article, Product, Breadcrumb and Organization instead.
Connecting entities with @id and @graphLink to this section
Instead of repeating the organisation and author on every page, give each entity a stable @id (a URL with a fragment) and reference it:
{
"@context": "https://schema.org",
"@graph": [
{ "@type": "WebSite", "@id": "https://www.example.com/#website", "url": "https://www.example.com/", "name": "Acme", "publisher": { "@id": "https://www.example.com/#organization" } },
{ "@type": "Organization", "@id": "https://www.example.com/#organization", "name": "Acme", "url": "https://www.example.com/" },
{ "@type": "WebPage", "@id": "https://www.example.com/about#webpage", "url": "https://www.example.com/about", "isPartOf": { "@id": "https://www.example.com/#website" } }
]
}This builds a small, consistent graph of the site, which is how Yoast and Rank Math structure their output.
Generating structured dataLink to this section
WordPressLink to this section
SEO plugins output Organization, WebSite, WebPage, Article and Breadcrumb automatically, and WooCommerce adds Product. Configure them correctly (organisation name, logo, social profiles) before writing custom code. To add or adjust, use the plugin's filters rather than printing a second, conflicting block. For custom post types, such as property listings or events, add data through those filters so everything stays in one graph.
Next.jsLink to this section
Build the object from your data and render it with JSON.stringify, escaping < so content can't break out of the script tag:
function JsonLd({ data }: { data: object }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data).replace(/</g, "\\u003c") }}
/>
);
}
// in a page
<JsonLd data={{ "@context": "https://schema.org", "@type": "BlogPosting", headline: post.title, datePublished: post.date }} />On a headless WordPress site, generate it in the frontend from the same data that renders the page, so the two can't drift. The headless WordPress guide covers the data layer.
To draft a block by hand, the schema generator produces valid JSON-LD for common types, and the JSON formatter catches syntax errors such as trailing commas, which silently invalidate the whole block.
TestingLink to this section
- Rich Results Test (Google): shows which rich results a URL is eligible for, and errors or warnings.
- Schema Markup Validator (schema.org): checks general schema.org validity, including types Google doesn't use.
- Search Console enhancements reports: track errors across the whole site over time, and confirm fixes with "Validate fix".
Test rendered pages, not templates: JSON-LD built in JavaScript after load is supported by Google but is slower to be picked up than server-rendered markup.
Structured data complements, not replaces, good titles and descriptions; check those with the meta tag generator and the technical SEO checklist.
Structured data for a personal or company siteLink to this section
A small business or personal brand site usually needs only a handful of blocks, placed consistently:
- Every page:
WebSiteandOrganization(orPerson) in the graph, plus aWebPagethat references them, and aBreadcrumbListon inner pages. - Home page: the full
OrganizationorPersondescription, withlogoorimage,sameAslinks to real profiles, and contact details. - About page:
AboutPageas the page type, with thePersonorOrganizationas itsmainEntity. - Blog posts:
BlogPostingwith author, publisher, dates and image. - Services or products:
ServiceorProductwhere the page really describes one, with accurate details. - Contact page:
ContactPage, andLocalBusinessdetails if there is a physical location.
For a personal brand, Person is the central entity. Give it a stable @id, such as https://www.example.com/#person, use it as the author of every article, and link sameAs to profiles such as LinkedIn and GitHub. This helps search engines connect your articles, your site and your profiles as the same person.
Keep it maintainableLink to this section
Generate all of this from one place. In a Next.js site, a small module that builds the graph from site settings and page data keeps every page consistent; in WordPress, configure the SEO plugin once and extend it with filters. Hand-written blocks pasted into individual pages drift out of date when a logo, address or job title changes, and nobody notices until a validator flags them.
Review the output when the site changes significantly: a redesign, a new section, or a move to a different CMS. A quick run through the Rich Results Test on a few representative URLs is enough.
Common problemsLink to this section
- "Missing field" warnings. Recommended properties are absent. Add them if the data exists; warnings don't block eligibility.
- Duplicate or conflicting blocks. A theme, a plugin and custom code each print Organization or Product. Keep one source.
- Invalid JSON. One trailing comma or unescaped quote breaks the whole script. Generate it with
JSON.stringifyorwp_json_encode, never string concatenation. - Rich results don't appear. Eligibility doesn't guarantee display; search engines decide per query. Check the page is indexed and the data matches visible content.
- Prices out of date. Cached pages show old prices in markup. Purge caches when prices change; see the caching guide.
Frequently asked questionsLink to this section
Does structured data improve rankings?Link to this section
Not directly. It helps search engines understand content and can make results more attractive, which can improve clicks.
Should I add every possible type?Link to this section
No. Add accurate markup for what the page really is. More markup isn't better; correct markup is.
Can I use structured data on a single-page app?Link to this section
Yes, but render it on the server where possible. Next.js server components make that straightforward.
How long until changes show in results?Link to this section
After the page is recrawled, often days to weeks. Request indexing in Search Console to speed up important pages.
Want structured data and technical SEO reviewed for your site? Get in touch.
Test yourself
3 quick questions on this lesson.
Finished this lesson?
Mark it complete to track your progress through the learning paths.