---
name: schema
description: Implement and validate schema.org structured data as JSON-LD for rich results. Use when the user says "schema markup", "structured data", "JSON-LD", "rich snippets", "FAQ schema", "product schema", or wants star ratings or enhanced listings in Google. Not for full SEO diagnostics: use seo-audit. Not for AI answer engine optimization: use ai-seo. Not for planning page hierarchy or breadcrumb structure: use site-architecture.
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: rewritten from patterns in an open SaaS marketing skills pack (MIT)
  category: SEO & AEO
---

# Schema Markup

Schema earns rich results only when it is valid JSON-LD, matches what a human sees on the page, and uses a type Google still rewards. Markup that overstates the page is not optimization, it is a penalty risk.

## Before You Write Markup

If a product marketing context file exists (`.agents/product-marketing.md` or `.claude/product-marketing.md`), read it first. Then confirm three things:

1. Page type and primary content. That decides which types are even eligible.
2. Existing markup. Never stack a second block of the same type on top of a broken one.
3. Target rich result and its business value. No target, no markup.

## Workflow

1. **Pick the type from the page, not the wishlist.** Homepage: Organization plus WebSite. Blog post: Article. Product page: Product with offers. FAQ content: FAQPage. Tutorial: HowTo. Local page: LocalBusiness. Read references/schema-types.md for required and recommended properties per type plus full JSON-LD examples.
2. **Write JSON-LD only.** Google's stated preference, easiest to maintain. Place in `<head>` or end of `<body>`, in server-rendered HTML. Client-injected markup can be processed late or missed.
3. **Combine types with `@graph`** when one page needs Organization, WebSite, and BreadcrumbList together. One script block, one graph.
4. **Fill only facts the page displays.** No fabricated ratings, no invented dates. Unknown values become `<FILL_ME>` for the user, never guesses.
5. **Validate before shipping.** Rich Results Test for eligibility, validator.schema.org for syntax, Search Console Enhancements reports for ongoing monitoring.

## Format Rules

- Dates in ISO 8601, URLs absolute and fully qualified.
- Enumerations exact: `https://schema.org/InStock`, not `"in stock"`.
- One canonical block per type per page.
- Keep markup in sync with content. Stale schema after a redesign is a common silent failure.

## Implementation by Stack

| Stack | Approach |
|-------|----------|
| Static HTML | JSON-LD in the template, partials for reusable blocks |
| React / Next.js | Schema component, server-side rendered, data serialized to JSON-LD |
| WordPress / CMS | Plugin (Yoast, Rank Math) or theme-level template edit |

## Verification

Run the generated block through a JSON parser: `python3 -c "import json,sys; json.load(sys.stdin)" < schema.json`. Expect silent success. If it errors, fix the reported syntax issue (trailing comma, unquoted key) and re-run.

Then paste the page URL or code into the Rich Results Test (search.google.com/test/rich-results). Expect zero errors and the target rich result listed as detected. If errors appear, fix required properties first, warnings second, and retest before delivering.

## Good vs Bad

**Bad:** Product page has no reviews yet. You add `aggregateRating` with a placeholder 4.8 so the stars show up. That is fabricated markup, a manual-action risk, and it will be your client's problem in six months.

**Good:** You skip rating markup, ship Product with real name, image, and offers, and note: "Add aggregateRating only after real reviews render on the page."

## Footguns

- **Markup for content that is not on the page.** Google's top reason for structured data manual actions. Fix: every property value must be visible on the rendered page.
- **Relative URLs.** `"url": "/pricing"` fails silently in some validators. Fix: absolute URLs everywhere.
- **JS-injected JSON-LD.** SEO plugins and tag managers often inject schema client-side, so curl-based checks miss it and Google may process it late. Fix: move blocks into server-rendered HTML, especially Product and Offer.
- **Duplicate conflicting blocks.** A plugin block plus a hand-added block of the same type with different values. Fix: audit existing markup first, keep one source of truth.

## Completion Checklist

- [ ] Type matches page content and current Google eligibility
- [ ] JSON parses clean, all URLs absolute, dates ISO 8601
- [ ] No fabricated values, unknowns marked `<FILL_ME>`
- [ ] Rich Results Test passes with zero errors
- [ ] Placement is server-rendered, one block per type

Any box unchecked: not done. Fix or say so.
