Skip to main content

Implementation + validation + operations (the finished JSON-LD for a fictional SaaS)

What this chapter covers

We combine the types decided in Chapter 03 to present the finished JSON-LD for Acme Workflow's service page.

  • Validation with the Google Rich Results Test / Schema Markup Validator
  • Typical errors / warnings and how to fix them
  • Pitfalls during implementation
  • Ongoing operations (Search Console monitoring / preventing regressions / keeping up with schema.org updates)

is what we cover.

The finished JSON-LD

This is the finished JSON-LD for Acme Workflow's service page (https://example.com/services/workflow/).

The JSON-LD for the Acme Workflow service page
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Acme Inc.",
"url": "https://example.com/",
"logo": "https://example.com/assets/logo.png",
"sameAs": [
"https://www.linkedin.com/company/acme-inc-example",
"https://x.com/acme_inc_example"
]
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com/",
"name": "Acme Inc.",
"publisher": { "@id": "https://example.com/#organization" },
"inLanguage": "ja"
},
{
"@type": "WebPage",
"@id": "https://example.com/services/workflow/#webpage",
"url": "https://example.com/services/workflow/",
"name": "Acme Workflow — a task management SaaS for operations staff",
"description": "Acme Workflow is a SaaS that lets operations staff at small and medium-sized businesses manage their team's tasks in one place.",
"inLanguage": "ja",
"isPartOf": { "@id": "https://example.com/#website" },
"mainEntity": { "@id": "https://example.com/services/workflow/#service" }
},
{
"@type": "Service",
"@id": "https://example.com/services/workflow/#service",
"name": "Acme Workflow",
"description": "A task management SaaS for operations staff. It manages a team's tasks in one place and helps make work visible.",
"serviceType": "Task management SaaS",
"provider": { "@id": "https://example.com/#organization" },
"audience": {
"@type": "BusinessAudience",
"audienceType": "Operations staff",
"name": "Operations staff at small and medium-sized businesses"
},
"areaServed": {
"@type": "Country",
"name": "Japan",
"identifier": "JP"
}
}
]
}

The intent behind each field

We organize the role of each @graph element.

Organization (Acme Inc.)

  • @id: unique across the whole site (https://example.com/#organization). Intended to be referenced from other pages via @id
  • name / url / logo: the basic information for the knowledge panel
  • sameAs: an array of related URLs. Makes it easier for Google to recognize that "these are the same organization"

WebSite

  • @id: unique across the whole site (https://example.com/#website)
  • publisher: references the Organization via @id
  • inLanguage: the site's language

WebPage

  • @id: unique to this page (https://example.com/services/workflow/#webpage)
  • name / description: information about the page itself
  • isPartOf: references the parent WebSite via @id
  • mainEntity: references this page's subject Service via @id

Service (Acme Workflow)

  • @id: unique to this page (https://example.com/services/workflow/#service)
  • name / description / serviceType: the service's basic information
  • provider: references the Organization via @id (= provided by Acme Inc.)
  • audience: expresses the target with BusinessAudience
  • areaServed: expresses the service area with Country (Japan)

Embedding it in HTML

You embed the finished JSON-LD inside <head> with a <script> tag.

services/workflow/index.html (the relevant part only)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Acme Workflow — a task management SaaS for operations staff</title>
<meta name="description" content="Acme Workflow is a SaaS that lets operations staff at small and medium-sized businesses manage their team's tasks in one place.">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
...
]
}
</script>
</head>
<body>
...
</body>
</html>
Implementation by framework
  • Next.js (App Router): return the following script tag in app/services/workflow/page.tsx (officially recommended; the metadata export does not support JSON-LD)

    <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
  • Nuxt 3: useHead({ script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(jsonLd) }] })

  • Static sites (Astro / Hugo / Jekyll): output <script> directly in the template

For details that depend on your implementation language, refer to each framework's documentation. This guide focuses on the structure of the JSON-LD.

Validation step 1: Schema Markup Validator (the schema.org official tool)

The Schema Markup Validator (checked: 2026-05) is schema.org's official validation tool. It validates only whether the markup is lexically correct in schema.org terms (Google-specific requirements are checked with the Rich Results Test next).

How to use it

  1. Open the tool
  2. Select the Code Snippet tab
  3. Paste the JSON-LD above
  4. Click Run Test
  5. As a result, the detected types and each property are listed

What to check

  • Is each @type recognized correctly?
  • Do the properties match the type's range?
  • Are any "Errors" / "Warnings" shown?

Errors in the Schema Markup Validator are schema.org spec violations, so always fix them. Warnings are recommendations, so address them as far as you can.

Validation step 2: Google Rich Results Test

The Google Rich Results Test (checked: 2026-05) validates whether it is valid as a Google-specific rich result target.

How to use it

  1. Open the tool
  2. Select the Code tab (or specify a URL directly)
  3. Paste the HTML containing the JSON-LD (including the <script> tag)
  4. Click Test
  5. As a result, the types Google recognized as rich result targets are listed
In this guide's JSON-LD, only Organization is detected

Because the Rich Results Test's list of target types includes Organization, testing this guide's finished JSON-LD detects Organization. On the other hand, WebPage / Service / Audience / Country are not targets, so they do not appear in the list, but this is not an error. The validity of the syntax and vocabulary is guaranteed by the Schema Markup Validator in the previous step.

The difference between the Rich Results Test and the Schema Markup Validator

ToolValidation scopeMain use
Schema Markup ValidatorThe validity of schema.org vocabularyWhether the structured data follows the spec
Google Rich Results TestThe scope Google interprets + rich result requirementsThe likelihood of display on Google

Validating with both is recommended. Confirm that it is structurally correct with the Schema Markup Validator, and confirm the likelihood of display from Google's perspective with the Rich Results Test.

Typical errors / warnings and how to fix them

Error 1: A range violation (the tool may not detect it)

Writing "audience": "Operations staff" as a string is a schema.org range violation, but the Schema Markup Validator may interpret it leniently and not raise an error (measured). When you run a Google rich-result target type like Product through the Rich Results Test, you get an "Invalid value type" error like the following.

The value of property "audience" should be of type "Audience" but got "Text"

For the types in this guide, even if the tool stays silent, a range violation needs to be fixed. Check the range on each schema.org type page and convert it to an Audience object.

"audience": {
"@type": "BusinessAudience",
"audienceType": "Operations staff",
"name": "Operations staff at small and medium-sized businesses"
}

Error 2: "Unknown property"

Property "providor" not recognized

→ A typo in a property name (e.g., provider, not providor). schema.org property names are strict camelCase.

Error 3: "Missing required field"

A warning specific to the Rich Results Test, shown when a field Google judges necessary for a rich result is missing.

For example, if a Product lacks offers / aggregateRating / review, it warns that "there is no review / price information, so it is not a rich result target."

Pitfall: a duplicate @id does not raise an error and is "silently merged"

Even if multiple objects in a @graph have the same @id, the validation tool does not raise an error. In JSON-LD, the same @id means the same node, and it is interpreted as a single entity with the properties mixed together (measured: validator.schema.org merges with zero errors). Unintended reuse becomes a design mistake the tool cannot detect, so make @id unique yourself, following the conventions in Chapter 03.

Pitfalls during implementation

Pitfall 1: Specifying a version in @context

"@context": "https://schema.org/version/13.0"

You may be tempted to pin a version like this, but the convention is not to pin a specific version for the schema.org context. Leave it as "https://schema.org". Google does not require a version either.

Pitfall 2: The @id URL does not match the page URL

The @id of WebPage should match the URL base of that page.

"@id": "https://example.com/services/workflow/#webpage"

If the page URL is /services/workflow/, use the same path for @id too. Attaching a fragment to the root, like https://example.com/#workflow-page, makes the correspondence between the page and the object ambiguous.

Pitfall 3: Over-deep nesting

{
"@type": "WebPage",
"mainEntity": {
"@type": "Service",
"provider": {
"@type": "Organization",
"publisher": {
"@type": "Organization",
...
}
}
}
}

Nesting this deeply hurts readability and maintainability. Rewriting it with @graph + @id references gives you a structure where each object is visible independently.

Pitfall 4: Missing escaping when embedding dynamic data

When you embed values fetched from a CMS or DB into a template, lax JSON escaping breaks the JSON on a " or a newline. Always go through JSON.stringify (or the equivalent function in your server language).

// Good
const html = `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`;

// Bad - embedding directly in a template literal (may break)
const html = `<script type="application/ld+json">
{ "name": "${page.title}" }
</script>`;

In addition, if a CMS-sourced string contains something like </script>, it can break the script block or cause XSS, so replacing < with \u003c is the official recommendation (it stays valid as a JSON string while being neutralized for the HTML parser).

Pitfall 5: Placing Audience outside Service

{
"@type": "WebPage",
"audience": { "@type": "BusinessAudience", ... },
"mainEntity": {
"@type": "Service",
...
}
}

There are cases where audience is placed directly under WebPage like this, but audience is the target of the subject (Service), so placing it inside Service is semantically correct. WebPage.audience becomes "the target readers of the page itself," which shifts the meaning.

Operations — it is not "implement and done"

Structured data requires ongoing operations. It goes stale with CMS updates / site redesigns / Google algorithm changes / schema.org updates. We organize the operational conventions after implementation.

Convention 1: Monitoring the Search Console rich results report (when there are target types)

The rich results report in Search Console displays the per-type error / warning / valid item counts when it detects valid markup for a supported rich result type (breadcrumbs / product snippets / review snippets, etc.). On the other hand, types that only add meaning, like this guide's WebPage / Service / Audience / Country, are not targets and do not appear in Search Console. The main means of monitoring these are the manual validation in Convention 5 and syntax checks in CI.

On sites that also use rich-result target types, read the report's two categories as follows.

  • Error (red): a serious problem that makes it not a rich result target. Fix immediately
  • Warning (yellow): the rich result appears but a recommended field is missing. Improve as far as you can
Recommended operations when there are target types
  • Make a habit of checking the rich results report weekly or monthly
  • If you have enabled Search Console notifications, you can receive a notice when a new error is detected
  • For large sites, periodically export the report to CSV to track changes

Convention 2: A JSON-LD check checkbox in the PR template

When changing the code of a page that contains JSON-LD, it is easy to forget to confirm the consistency of the structured data in review. Put a checkbox like the following in the PR template.

PR template example (.github/pull_request_template.md)
## Structured data check (when applicable)

- [ ] The JSON-LD `name` / `description` match the displayed text
- [ ] The structured data elements corresponding to removed body content were also removed
- [ ] Validated with the Rich Results Test (https://search.google.com/test/rich-results)
- [ ] The `@id` URL is consistent with the page URL

Convention 3: A workflow for keeping up with schema.org updates

schema.org has version updates several times a year, with new types added / existing properties deprecated, and so on.

Recommended operations
  • Once a year (e.g., each April), check the schema.org Release History
  • Confirm whether the types / properties in use have been deprecated
  • If there are important new features (e.g., a new Audience subtype), consider adopting them

Deprecation notices never come suddenly; a long grace period is usually provided, but it is safer to keep up in a planned way.

Convention 4: Maintaining consistency between content and markup

During a site redesign or CMS migration, there are cases where only the structured data is left old and untouched.

  • When the displayed text changes, update the corresponding values in the structured data at the same time
  • When the Audience / Country / Service content changes, reconsider the types as well
  • During a redesign, include redesigning the structured data in scope

Convention 5: Periodic regression testing

Building validity checks with the validation tools into CI is ideal, but the Rich Results Test has no official public API. To build it into CI, it is realistic to use type-level validation with schema-dts or an OSS library for validating structured data.

As a simple measure, running a few representative pages through the Rich Results Test manually each month is also practical.

A summary of this guide

We look back over the content so far.

  • The fundamentals of structured data and JSON-LD
  • The conventions of Google's guidelines
  • The type-selection process
  • Implementation / validation / operations

We covered all of these. If you replace the Acme Workflow example with the context of your own site, you should be able to implement the JSON-LD for a service page.

Looking back
  • Before choosing a type, first narrow the page's subject down to one
  • Refer to the official specs (schema.org / Google Search Central) as primary sources each time
  • After implementing, always confirm validity with the validation tools
  • Include ongoing operations in your plan

Exhaustive explanations of other types (Article / Product / FAQPage / BreadcrumbList, etc.), the details of Microdata / RDFa, and internationalization (inLanguage for multilingual sites) are out of scope for this guide. When you need them, use this guide's decision process as a foundation and refer to the official documentation for the relevant type.