JSON-LD: how to write it, where to put it, how to test it

You want machines to read your pages without guesswork. JSON-LD is the format Google recommends. This guide shows a working example, where to put it, and how to test it without wasting a sprint.

By , founder of Porteur · Updated 14 September 2026 · Markdown

Why JSON-LD is the default

Use JSON-LD for structured data on a product site. Google recommends it, and it keeps your HTML cleaner than in-line attributes.

JSON-LD is not a ranking factor. It makes your page eligible for rich results and helps machines understand your entities and facts.

You can add or change it without touching visible copy. That lowers the risk of layout regressions and speeds up reviews.

JSON-LD vs microdata

QuestionJSON-LDMicrodata or RDFa
Where it livesA script block you can place onceInline attributes on many tags
Ease of editingOne place to changeMany places to change
Google supportRecommended by Google as of 2026Read by Google, not recommended
Works with JS frameworksYes, as a script or injected stringPossible but brittle
Risk to layoutNone, it is not renderedHigher, attributes sit in HTML

If you already ship microdata, do not duplicate it with JSON-LD on the same facts. Keep one source to avoid conflicts in the graph.

Where to put JSON-LD: head or body

Place your JSON-LD in the head. It keeps the markup close to metadata and is easy for your team to find.

Body also works. Google reads JSON-LD in head or body. Both are valid as of 2026.

Server render the script where you can. Google renders pages, so injected JSON-LD is seen. Most answering AI crawlers do not run scripts yet, so do not rely on client injection for them.

Use one script per page or one @graph holding all your types. Both patterns are fine. A single @graph scales better on complex pages.

One @graph for several types

Put several types in one @graph. Link them with @id so machines can join the dots.

  • Use absolute URLs in @id and url fields. Do not use relative paths.
  • Give each node a stable @id, for example your homepage with a hash: https://yourproduct.com/#organization.
  • Reference nodes by @id instead of nesting deeply. This keeps the graph tidy.
  • Only mark up content that exists on the page. If it is not visible, it risks a policy violation.

On a product homepage, your graph can include Organization and WebSite. On a product page, add Product, Offer and AggregateRating. On a blog post, add Article and BreadcrumbList.

A complete example: Organization and WebSite

Drop this script on https://yourproduct.com. Adjust names, images and policies. The WebSite node omits SearchAction. Google removed the sitelinks search box in November 2024, so that action now earns nothing.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://yourproduct.com/#organization",
      "name": "Your Product Ltd",
      "url": "https://yourproduct.com/",
      "logo": {
        "@type": "ImageObject",
        "url": "https://yourproduct.com/static/logo-112.png",
        "width": 112,
        "height": 112
      },
      "sameAs": [
        "https://twitter.com/yourproduct",
        "https://www.linkedin.com/company/yourproduct/"
      ],
      "contactPoint": {
        "@type": "ContactPoint",
        "contactType": "customer support",
        "email": "[email protected]",
        "url": "https://yourproduct.com/support",
        "areaServed": "GB"
      },
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "12 Example Street",
        "addressLocality": "London",
        "postalCode": "EC1A 1AA",
        "addressCountry": "GB"
      },
      "founder": {
        "@type": "Person",
        "name": "Alex Smith",
        "url": "https://yourproduct.com/about"
      },
      "foundingDate": "2022-04-14",
      "merchantReturnPolicy": {
        "@type": "MerchantReturnPolicy",
        "applicableCountry": "GB",
        "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
        "merchantReturnDays": 30,
        "returnMethod": "https://schema.org/ReturnByMail",
        "returnFees": "https://schema.org/FreeReturn"
      },
      "hasMerchantReturnPolicy": "https://yourproduct.com/legal/returns",
      "shippingDetails": {
        "@type": "OfferShippingDetails",
        "shippingRate": {
          "@type": "MonetaryAmount",
          "value": 0,
          "currency": "GBP"
        },
        "shippingDestination": {
          "@type": "DefinedRegion",
          "addressCountry": "GB"
        }
      }
    },
    {
      "@type": "WebSite",
      "@id": "https://yourproduct.com/#website",
      "url": "https://yourproduct.com/",
      "name": "Your Product",
      "publisher": {
        "@id": "https://yourproduct.com/#organization"
      }
    }
  ]
}
</script>

A fixed page has absolute URLs, ISO 8601 dates and crawlable images. The logo is at least 112 by 112 pixels and fetchable by Googlebot.

Types you will likely use on a product site

  • Article, NewsArticle or BlogPosting on /blog/your-post. Include headline, image, datePublished, dateModified and author. Supply several high-resolution images in 16:9, 4:3 and 1:1. Google truncates long headlines.
  • Product on /product/sku-abc. For product snippets, name is required and at least one of review, aggregateRating or offers. If you include offers, add price, priceCurrency, availability as a schema.org value like InStock, and url. Add image, brand, sku and gtin if you have them. For merchant listing results, offers can also include shipping and return details.
  • SoftwareApplication on /app/your-app. For the software app rich result, set name, offers.price and offers.priceCurrency, and a rating via aggregateRating or a review. applicationCategory and operatingSystem help disambiguate.
  • FAQPage on /help/faq. mainEntity is a list of Question with acceptedAnswer of type Answer. As of August 2023, Google shows FAQ rich results only for well-known government and health sites. Your markup stays valid, but expect no FAQ rich result for a product site.
  • HowTo on /guides/how-to-do-x. Google removed HowTo rich results in September 2023. The markup is harmless and earns nothing now.
  • Organization on the homepage and about pages. Include name, url, logo, sameAs, contactPoint, address, founder and foundingDate. Since 2024, your merchant fields like return policy and shipping can also sit on Organization.
  • BreadcrumbList on pages deeper than one level. itemListElement holds ListItem with position starting at 1, name and item. The last item may omit item. Breadcrumbs can replace the URL line in results.
  • Review snippet on pages with reviews of supported types like Product or SoftwareApplication. Set itemReviewed to a supported type. For individual reviews, include reviewRating with ratingValue and bestRating and the author name. For aggregateRating include ratingValue and ratingCount or reviewCount. Self-serving reviews for your own LocalBusiness or Organization are not shown as rich results.
  • WebSite with SearchAction for internal search. Google removed the sitelinks search box in November 2024, so this now earns nothing. You can still include it for other consumers of schema.

How to add JSON-LD to your pages

  1. Pick the types that match the page

    Decide by intent. /pricing rarely needs Article. /blog/post needs Article and BreadcrumbList. A product page needs Product and Offer.

  2. Draft the graph in a sandbox

    Start with the smallest set of required properties. Add recommended fields you actually have. Keep one node per entity, link with @id.

  3. Place the script in the head

    Ship it server side where possible. Use application/ld+json as the type. Avoid templating bugs that inject trailing commas.

  4. Fill URLs and dates correctly

    Use absolute URLs. Format dates in ISO 8601 like 2026-08-14. Remove currency symbols from numeric fields. Keep rating scales in range.

  5. Deploy on one page

    Test on a single URL. Fix every error. Then roll out across the section with templates and data checks.

How to test JSON-LD today

Use two tools and know the difference. They read the rendered page, so injected markup is visible to them.

  • Rich Results Test at search.google.com/test/rich-results. It shows which Google rich results your page is eligible for, and what required or recommended fields are missing for those features.
  • Schema Markup Validator at validator.schema.org. It checks your syntax against schema.org. It does not apply Google’s eligibility rules, so it will not tell you if you qualify for a rich result.

The Structured Data Testing Tool closed in 2020. Use the two tools above instead as of 2026.

  1. Run the Rich Results Test

    Paste the URL. Check each detected type. Expand errors and warnings. Fix required fields first. Re-test until no errors remain.

  2. Run the Schema Markup Validator

    Paste the same URL or your code. Confirm no schema.org errors. If it passes here but fails the Rich Results Test, you are missing Google’s required fields for that feature.

  3. Check the rendered HTML

    Open View Source and DevTools Elements. Confirm one script block with your JSON-LD exists and contains valid JSON. Remove duplicates.

What Search Console shows after you ship

Search Console creates an enhancement report per rich result type detected on your site. You see counts of valid items, warnings and errors by type.

Use the report to track coverage and regressions. Click a problem, inspect a sample URL, fix the template, then click Validate fix to start rechecking.

Not every schema type creates a report. Only supported rich result types do. For example, Organization appears in Google’s knowledge panels, but there is no Organization enhancement report.

Common errors and quick fixes

  • Missing required field. Fix by adding all required properties for the feature, for example Product.name and Offers.priceCurrency.
  • Relative URL where absolute is needed. Change "/logo.png" to "https://yourproduct.com/logo.png".
  • Date not in ISO 8601. Use "2026-08-14" or a full timestamp. Do not write "14 Sept 2026".
  • Price as text with a currency sign. Use "price": 9.99 and "priceCurrency": "USD". Do not write "$9.99" as a string.
  • Rating out of range. Keep ratingValue within the stated bestRating and worstRating, or the default 1 to 5.
  • Content not visible on the page. Remove markup or add the content users can see. Google requires that marked-up content is visible to users.
  • Broken graph. Do not nest types in a way that hides relationships. Prefer separate nodes linked by @id to deep nesting.

A workflow that scales on a small team

  1. Template per layout

    Add JSON-LD once per layout: post, product, docs, homepage. Keep the graph close to the template and test data.

  2. Define required fields as code checks

    Fail the build if required fields are missing, or if URLs are not absolute. A pre-commit check catches regressions early.

  3. Log when markup changes

    Ship a changelog entry for structured data edits. If clicks drop or rich results vanish, you can link back to a release.

  4. Review in Search Console monthly

    Open enhancement reports. Fix new errors. Scan warnings for patterns you can address in content or data quality.

A fixed page in this workflow has one @graph, no console errors, passes validator.schema.org, and shows expected types in the Rich Results Test.

Questions

Sources

Check my site, free

Get a free check of your JSON-LD from your URL. In about thirty seconds it reads your site, the searches around it and the rivals on them, and shows three findings whole.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next