Most JSON-LD shipped in 2026 is decorative. The types that still earn visible rich results are Product, SoftwareApplication, Recipe, Event, JobPosting, Video, and BreadcrumbList — plus Article for enhanced news-ish treatment. FAQPage rich results have been restricted to government and health sites since August 2023, HowTo was retired outright a month later, and nothing you put in Organization markup will ever render in a SERP. Ship the handful that pay, keep one site-wide identity block for entity clarity, and delete the rest.

The payoff table

TypeWhat rendersVerdict
Product + AggregateRatingStars, price, availabilityThe biggest visible win — where you have real ratings
SoftwareApplicationStars and price on app resultsYes for directories and app pages
BreadcrumbListBreadcrumb trail replaces the raw URLAlways — one template edit, sitewide
ArticleHeadline/image treatment, Top Stories eligibilityYes for any blog
FAQPageNothing, unless you're a government or health siteKeep the Q&A content, drop the rich-result expectation
HowToNothing since September 2023Delete it
Organization + WebSiteSite name, logo, knowledge panel inputsOnce, sitewide — identity, not decoration
VideoObject, Event, JobPostingTheir respective rich resultsOnly with real inventory behind them

The full, current list lives in Google's structured data gallery; check it before implementing anything, because features get retired faster than blog posts about them get updated.

The invisible payoff: being an unambiguous entity

The second reason to ship JSON-LD has nothing to do with rich results. A consistent Organization block with sameAs pointing at your GitHub org, documentation, and social profiles — and stable @id values reused across pages — is how crawlers and answer engines resolve who you are. Ambiguous entities get hedged about or skipped when AI search engines pick citations; disambiguation is cheap insurance. One block, rendered on every page, maintained in one place.

A worked example

The app pages on this site ship SoftwareApplication. The shape that validates and renders:

{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Jellyfin",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Linux, Windows, macOS, Docker",
  "softwareVersion": "10.10",
  "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",
    "ratingCount": 213
  },
  "sameAs": ["https://github.com/jellyfin/jellyfin"]
}

Two constraints people miss: offers or aggregateRating is required for the rich result (free software still needs the zero-price offer), and the rating must correspond to real, visible ratings on the page. Markup describing things a user cannot see on the page is straightforwardly against Google's spam policies, and structured-data manual actions do get handed out for it.

Validation traps

The two validators disagree by design. The Rich Results Test only checks eligibility for Google features; validator.schema.org checks the vocabulary. A page can pass one and fail the other. Run both: schema.org for correctness, Rich Results Test for whether Google cares.

One bad character voids the whole block. JSON-LD fails closed: an unescaped quote in one app's description breaks parsing for the entire script tag, and if it came from a template, it breaks on thousands of pages simultaneously with no warning. Validate rendered output in CI, not the template:

# every JSON-LD block in the build must parse
grep -rl 'application/ld+json' dist/ | shuf -n 200 | while read -r f; do
  python3 -c "
import json, re, sys
html = open('$f').read()
for m in re.findall(r'<script type=\"application/ld\+json\">(.*?)</script>', html, re.S):
    json.loads(m)
" || echo "BROKEN: $f"
done

A 200-page sample per deploy catches template-level breakage within one build instead of three weeks later in Search Console.

Dates in the wrong format. datePublished and dateModified want ISO 8601. They also want to be true — consistency between schema dates, visible dates, and sitemap lastmod is its own topic, covered in what dateModified actually does.

Fragmented blocks that never connect. Five separate script tags with no shared identifiers describe five unrelated things. Use one @graph per page (or consistent @id references across blocks) so the breadcrumb, the article, and the publisher resolve to one connected description. Parsers cope either way; the entity picture is cleaner connected, and it's easier to validate one block than five.

Self-serving reviews. aggregateRating on your own organisation, via markup you control, is explicitly disallowed. Ratings belong on the things you catalogue, not on yourself.

Measuring whether any of it worked

Search Console gives you two instruments. The Enhancements section reports valid/invalid items per rich-result type, which catches breakage but says nothing about value. For value, filter the Performance report by search appearance and compare CTR on pages with and without the rich result — on this site, comparison pages showing stars pull roughly 1.3–1.5× the CTR of the same template without them at equal position. That multiple, not the green check mark, is the number that justifies the maintenance. The same markup also feeds the spec tables that make comparison pages rank, so the marginal cost here rounds to zero.

Bottom line

Ship four things: BreadcrumbList everywhere, Article on the blog, SoftwareApplication (or Product) with honest ratings on entity pages, and one site-wide Organization/WebSite identity block with sameAs. Validate rendered pages in CI with both validators. Skip FAQ and HowTo markup entirely unless Google reverses the 2023 retirements, and treat every other type as not worth the template complexity until you can name the rich result it earns.