Detect, validate, and generate Schema.org structured data (JSON-LD preferred). Covers per-type templates (Article, FAQPage, Product, LocalBusiness, BreadcrumbList, and 15+ more), nested @graph patterns, rich-results eligibility, and the deprecated-type guardrails. Powers Stage 4 of the seo-writing pipeline and the seo-schema agent.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add trungdo9/ClauKit --skill seo-schema --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Seo Schema?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/trungdo9-seo-schema)More formats (shields.io, HTML) on the badges page.
---
name: seo-schema
description: Detect, validate, and generate Schema.org structured data (JSON-LD preferred). Covers per-type templates (Article, FAQPage, Product, LocalBusiness, BreadcrumbList, and 15+ more), nested @graph patterns, rich-results eligibility, and the deprecated-type guardrails. Powers Stage 4 of the seo-writing pipeline and the seo-schema agent.
allowed-tools: Read, Write, Glob, Grep
---
# SEO Schema — JSON-LD Generation & Validation
> A page can say the right things and still lose the rich result to a competitor with cleaner markup — schema is the layer that tells Google (and AI crawlers) exactly what's on the page, not just what it says.
## When this skill activates
**Implicit:** "add schema markup", "generate JSON-LD", "is my structured data valid", "will this qualify for rich results", "extract the schema from this page".
**Explicit:** "Use the seo-schema skill to [task]."
**Routed from:** [[seo-writing]] Stage 4 (generates `Article` + `FAQPage` when the article has an FAQ section), the `seo-schema` agent, `/mk:seo schema`.
## Scope
Covers:
- Detecting existing structured data (JSON-LD, Microdata, RDFa) on a page.
- Generating valid JSON-LD for the active Schema.org types (Article, FAQPage, Product, LocalBusiness, BreadcrumbList, Organization, and the extended catalog in `references/templates.json`).
- Nested `@graph` patterns that tie WebPage + BreadcrumbList + Article + Person + FAQPage into one linked block.
- Validation (required properties, data types, placeholder detection, absolute URLs, ISO 8601 dates) and rich-results eligibility.
- Deprecated/retired-type guardrails — steering requests away from dead rich results toward the current replacement.
Does NOT cover:
- LocalBusiness/GBP depth (multi-location, geo-grid, review-response schema) → [[seo-local]]. This skill emits the base `LocalBusiness` template only.
- Product/Offer e-commerce depth (variant pricing, loyalty programs, adult-consideration flags, Merchant API migration) → [[seo-ecommerce]]. This skill emits the base `Product` template only.
- AI-search/GEO content structuring (entity clarity, extractable answers) → [[seo-geo]] — schema is a signal for that work, not a substitute for it.
- Writing the article content that the schema describes → [[seo-content]] / [[seo-writing]].
## Detection
When auditing an existing page:
1. Scan page source for JSON-LD: `<script type="application/ld+json">`.
2. Check for Microdata (`itemscope`, `itemprop` attributes).
3. Check for RDFa (`typeof`, `property` attributes).
4. Always recommend migrating Microdata/RDFa to JSON-LD — it's Google's stated preference and the only format this skill generates.
## Schema type status
Full status tables live in `references/schema-types.md` (active / no-rich-result / deprecated / recent additions) and `references/deprecated-types.md` (retirement dates, sources, replacement decision table). The short version:
- **Active, recommend freely:** Organization, LocalBusiness, Product, Offer, Service, Article/BlogPosting/NewsArticle, Review, AggregateRating, BreadcrumbList, WebSite, WebPage, Person, VideoObject, Event, JobPosting, Course, ProductGroup, ProfilePage, QAPage, and more — see the table.
- **No SERP rich result, keep only if useful:** **FAQPage** — Google retired FAQ rich results for all sites May 7, 2026. Flag existing FAQPage at Info (not Critical); never recommend it, or removal of it, for SERP benefit. For genuine user Q&A, use `QAPage` instead.
- **Deprecated, never recommend:** HowTo, SpecialAnnouncement, CourseInfo, EstimatedSalary, LearningVideo, ClaimReview, VehicleListing, Book Actions, Practice Problem. If a user asks for one, explain the retirement and offer the replacement from the decision table in `references/deprecated-types.md`.
- **Special case:** Dataset is *not* discontinued — it just has no Google Search rich result, only Dataset Search. Don't claim it was killed.
## Generation workflow
1. **Identify page type** from content analysis (blog post, product page, local business, FAQ section present, event, job listing, etc.).
2. **Select the schema type(s)** — prefer the narrowest correct type (e.g. `BlogPosting` over generic `Article` for a blog, `QAPage` over `FAQPage` for genuine user Q&A).
3. **Pull the template** from the inline examples below or `references/templates.json` for less common types.
4. **Fill every bracketed placeholder** with real, verifiable page data. No real data for a field → omit the field or mark it `[NEEDS DATA]` in the surrounding report, never ship a literal `[Company Name]`.
5. **Decide single-block vs. `@graph`** — one entity → single JSON-LD block; a page with multiple related entities (WebPage + Breadcrumb + Article + Author + FAQ) → the nested `@graph` pattern below, so entities reference each other by `@id` instead of duplicating data.
6. **Validate** against the checklist before presenting output.
## Core JSON-LD templates
### Organization
```json
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "[Company Name]",
"url": "[Website URL]",
"logo": "[Logo URL]",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "[Phone]",
"contactType": "customer service"
},
"sameAs": [
"[Facebook URL]",
"[LinkedIn URL]",
"[Twitter URL]"
]
}
```
### LocalBusiness
*(base template only — multi-location, geo-grid, and GBP-review-schema depth is [[seo-local]]'s job)*
```json
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "[Business Name]",
"image": "[Storefront/Logo Image URL]",
"address": {
"@type": "PostalAddress",
"streetAddress": "[Street]",
"addressLocality": "[City]",
"addressRegion": "[State]",
"postalCode": "[ZIP]",
"addressCountry": "[Country Code, e.g. US]"
},
"telephone": "[Phone]",
"priceRange": "[e.g. $$]",
"openingHours": "Mo-Fr 09:00-17:00",
"geo": {
"@type": "GeoCoordinates",
"latitude": "[Lat]",
"longitude": "[Long]"
}
}
```
### Article / BlogPosting
```json
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "[Title, <= 110 chars]",
"description": "[Meta/summary description]",
"author": {
"@type": "Person",
"name": "[Author Name]",
"url": "[Author Profile URL]"
},
"datePublished": "[YYYY-MM-DD]",
"dateModified": "[YYYY-MM-DD]",
"image": "[Featured Image URL]",
"publisher": {
"@type": "Organization",
"name": "[Publisher Name]",
"logo": {
"@type": "ImageObject",
"url": "[Logo URL]"
}
},
"mainEntityOfPage": "[Canonical Page URL]"
}
```
### FAQPage
*(no Google SERP rich result since May 7, 2026 — emit only when the article genuinely has a Q&A section; document it, don't sell it as a ranking lever)*
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "[Question text]",
"acceptedAnswer": {
"@type": "Answer",
"text": "[Answer text]"
}
}
]
}
```
### Product
*(base template only — variant/loyalty/adult-consideration/Merchant-API depth is [[seo-ecommerce]]'s job)*
```json
{
"@context": "https://schema.org",
"@type": "Product",
"name": "[Product Name]",
"description": "[Product Description]",
"image": "[Product Image URL]",
"sku": "[SKU]",
"brand": {
"@type": "Brand",
"name": "[Brand Name]"
},
"offers": {
"@type": "Offer",
"price": "[Price]",
"priceCurrency": "[Currency Code, e.g. USD]",
"availability": "https://schema.org/InStock",
"url": "[Product Page URL]",
"priceValidUntil": "[YYYY-MM-DD]",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnPolicyCountry": "[Country Code — required since March 2025]"
}
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "[Average Rating]",
"reviewCount": "[Number of Reviews]"
}
}
```
### BreadcrumbList
*(the final, current-page item conventionally has no `item` URL — it isn't a link)*
```json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "[Home]", "item": "[https://example.com/]" },
{ "@type": "ListItem", "position": 2, "name": "[Category Name]", "item": "[https://example.com/category/]" },
{ "@type": "ListItem", "position": 3, "name": "[Current Page Title]" }
]
}
```
**Extended catalog** — VideoObject, BroadcastEvent, Clip, SeekToAction, SoftwareSourceCode, ProductGroup, ProfilePage, Certification, OfferShippingDetails, WebSite, WebPage, Person, Review, AggregateRating, Event, JobPosting, QAPage, ItemList — see `references/templates.json` for ready-to-use JSON-LD.
## Nested `@graph` pattern
For a typical blog article page, don't repeat data across separate blocks — link entities by `@id` inside one `@graph`:
```json
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": "[https://example.com/blog/post-slug/#webpage]",
"url": "[https://example.com/blog/post-slug/]",
"name": "[Page Title]",
"isPartOf": { "@id": "[https://example.com/#website]" },
"primaryImageOfPage": { "@id": "[https://example.com/blog/post-slug/#primaryimage]" },
"datePublished": "[YYYY-MM-DD]",
"dateModified": "[YYYY-MM-DD]",
"breadcrumb": { "@id": "[https://example.com/blog/post-slug/#breadcrumb]" }
},
{
"@type": "BreadcrumbList",
"@id": "[https://example.com/blog/post-slug/#breadcrumb]",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "[https://example.com/]" },
{ "@type": "ListItem", "position": 2, "name": "Blog", "item": "[https://example.com/blog/]" },
{ "@type": "ListItem", "position": 3, "name": "[Post Title]" }
]
},
{
"@type": "Article",
"@id": "[https://example.com/blog/post-slug/#article]",
"isPartOf": { "@id": "[https://example.com/blog/post-slug/#webpage]" },
"headline": "[Post Title]",
"author": { "@id": "[https://example.com/author/name/#person]" },
"publisher": { "@id": "[https://example.com/#organization]" },
"datePublished": "[YYYY-MM-DD]",
"dateModified": "[YYYY-MM-DD]",
"mainEntityOfPage": { "@id": "[https://example.com/blog/post-slug/#webpage]" },
"image": { "@id": "[https://example.com/blog/post-slug/#primaryimage]" }
},
{
"@type": "ImageObject",
"@id": "[https://example.com/blog/post-slug/#primaryimage]",
"url": "[Featured Image URL]",
"contentUrl": "[Featured Image URL]"
},
{
"@type": "Person",
"@id": "[https://example.com/author/name/#person]",
"name": "[Author Name]",
"url": "[Author Profile URL]"
},
{
"@type": "FAQPage",
"@id": "[https://example.com/blog/post-slug/#faq]",
"mainEntity": [
{
"@type": "Question",
"name": "[Question 1]",
"acceptedAnswer": { "@type": "Answer", "text": "[Answer 1]" }
}
]
}
]
}
```
Only include the `FAQPage` node in the graph when the article actually has a Q&A section (see the FAQPage caveat above). Drop unused nodes rather than emitting empty ones.
## Validation checklist
For any schema block, verify before shipping:
1. `@context` is `"https://schema.org"` (not `http`).
2. `@type` is valid and non-deprecated (check `references/deprecated-types.md`).
3. All required properties for that type are present.
4. Property values match expected data types (string vs. object vs. array).
5. No placeholder text remains (e.g. literal `"[Business Name]"`).
6. URLs are absolute, never relative.
7. Dates are ISO 8601 (`YYYY-MM-DD` or full timestamp with timezone).
8. Images have valid, absolute URLs.
9. JSON is syntactically valid — no trailing commas, all keys quoted.
## Rich-results eligibility
A generated block is only worth shipping if it can actually earn a rich result:
- **Article/BlogPosting/NewsArticle** — needs `headline`, `image`, `datePublished`, `author`; eligible for the Top Stories / article rich result.
- **Product** — needs `name`, `image`, `offers.price`, `offers.priceCurrency`, `offers.availability`; `aggregateRating` or `review` adds star ratings. Missing `returnPolicyCountry` on `MerchantReturnPolicy` blocks the merchant listing since March 2025.
- **LocalBusiness** — needs `name`, `address`; `geo` and `openingHours` strengthen local-pack eligibility (map-pack ranking itself is not schema-driven).
- **BreadcrumbList** — needs `itemListElement` with sequential `position`; renders as the breadcrumb trail under the SERP title.
- **FAQPage** — technically valid, zero SERP rich-result value since May 7, 2026. Not an eligibility question anymore — it's a "don't sell this as ranking value" question.
- **QAPage** — needs `mainEntity.acceptedAnswer` (or `suggestedAnswer`); still eligible for the Q&A rich result.
- **VideoObject** — needs `name`, `description`, `thumbnailUrl`, `uploadDate`; `duration` and `contentUrl`/`embedUrl` strengthen it.
Anything asked for from the deprecated list (HowTo, SpecialAnnouncement, ClaimReview, VehicleListing, EstimatedSalary, LearningVideo, CourseInfo carousel, Practice Problem, Book Actions) is **not eligible for any rich result** — say so plainly rather than generating dead markup.
## Key concepts
- **JSON-LD over Microdata/RDFa** — Google's stated format preference; this skill only generates JSON-LD, and recommends migrating any Microdata/RDFa found during detection.
- **`@graph` linking** — entities reference each other by `@id` instead of nesting full copies, so one Organization/Person/WebPage definition backs every page that needs it.
- **Placeholder discipline** — a bracketed field left unfilled in shipped output is a validation failure, not a formatting choice; either fill it with real data or omit the property.
- **Rich result ≠ valid schema** — a block can be perfectly valid JSON-LD and still earn nothing in the SERP (FAQPage, and everything on the deprecated list). Eligibility and validity are separate checks.
- **JS-rendered schema risk** — per Google's December 2025 JS SEO guidance, structured data injected via JavaScript can face delayed processing. For time-sensitive markup (Product, Offer especially), put the JSON-LD in the initial server-rendered HTML.
## Output
- `plans/marketing/<campaign>/seo-schema-report.md` — detection + validation results:
| Schema | Type | Status | Issues |
|--------|------|--------|--------|
| ... | ... | pass / warn / fail | ... |
plus a Recommendations section (missing schema opportunities, validation fixes, generated code).
- `generated-schema.json` (or inline in the conversation) — ready-to-use JSON-LD block(s), single or `@graph`.
- When routed from [[seo-writing]] Stage 4: the `Article` (+ `FAQPage` if applicable) block handed back to the pipeline to store alongside the article for the publish step.
## Error handling
| Scenario | Action |
|----------|--------|
| URL unreachable | Report the connection error with status code. Suggest verifying the URL and checking whether the page requires authentication. |
| No schema markup found | Report that no JSON-LD, Microdata, or RDFa was detected. Recommend schema types based on page content analysis. |
| Invalid JSON-LD syntax | Parse and report the specific syntax error (missing bracket, trailing comma, unquoted key). Provide corrected JSON-LD. |
| Deprecated schema type requested | Flag it with its retirement date (`references/deprecated-types.md`). Recommend the current replacement, or say plainly that none exists. |
## Cross-references
- `plans/marketing-context.md` — required hub
- [[seo-writing]] — Stage 4 calls this skill to generate `Article` + `FAQPage` (when applicable) for the optimized draft
- [[seo-local]] — LocalBusiness depth: multi-location, geo-grid, GBP review schema
- [[seo-ecommerce]] — Product/Offer depth: variants, loyalty pricing, adult-consideration flags, Merchant API
- [[seo-geo]] — AI-search content structuring that schema supports but doesn't replace
- [[seo-content]] — writes the content this schema describes
- `.claude/workflows/marketing-rules.md` — content quality rules
- `.claude/skills/marketing/README.md` — full kit overview
- `.claude/skills/marketing/seo/SKILL.md` — orchestrator (parent); owns the deprecated-schema guardrail as a cross-cutting quality gate
## Provenance
Imported from `AgriciDaniel/claude-seo` (`skills/seo-schema/SKILL.md` + `skills/seo/references/schema-types.md` + `skills/seo-schema/references/deprecated-types-2024-2026.md` + root `schema/templates.json`, v2.2.4) and adapted for KitForge: merged the source's inline common-templates section with its separate schema/templates.json catalog into one skill-local `references/templates.json`; added FAQPage, BreadcrumbList, QAPage, WebSite, WebPage, Person, Review, AggregateRating, Event, and JobPosting templates (standard, correctly-formed Schema.org structures per schema.org/Google documentation, not present as literal JSON in the source pack) to meet the pipeline's minimum coverage; added the nested `@graph` example; scoped LocalBusiness/Product depth out to [[seo-local]]/[[seo-ecommerce]] to avoid duplicating those skills' territory; retitled `deprecated-types-2024-2026.md` to `deprecated-types.md`. Facts and dates (retirement timelines, tooling-removal dates) carried over verbatim from source — not re-verified against Google's live docs during this port.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!