schema-markup
Use this skill when implementing structured data markup using JSON-LD and Schema.org vocabulary for rich search results. Triggers on adding schema markup for FAQ, HowTo, Product, Article, Breadcrumb, Organization, LocalBusiness, Event, Recipe, or any Schema.org type. Covers JSON-LD implementation, Google Rich Results eligibility, validation testing, and framework integration (Next.js, Nuxt, Astro).
marketing seoschema-markupjson-ldstructured-datarich-snippetsschema-orgWhat is schema-markup?
Use this skill when implementing structured data markup using JSON-LD and Schema.org vocabulary for rich search results. Triggers on adding schema markup for FAQ, HowTo, Product, Article, Breadcrumb, Organization, LocalBusiness, Event, Recipe, or any Schema.org type. Covers JSON-LD implementation, Google Rich Results eligibility, validation testing, and framework integration (Next.js, Nuxt, Astro).
schema-markup
schema-markup is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex, and 1 more. Implementing structured data markup using JSON-LD and Schema.org vocabulary for rich search results.
Quick Facts
| Field | Value |
|---|---|
| Category | marketing |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex, mcp |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill schema-markup- The schema-markup skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
Schema markup is machine-readable context added to web pages that tells search engines
what your content means, not just what it says. JSON-LD (JavaScript Object Notation
for Linked Data) is Google's recommended implementation format - injected via a
<script type="application/ld+json"> tag rather than woven into the HTML. Implementing
correct structured data makes pages eligible for rich results in Google Search: star
ratings, FAQ dropdowns, breadcrumb trails, recipe cards, event listings, and more.
Rich results increase click-through rates by making listings visually distinct in the SERP.
Tags
seo schema-markup json-ld structured-data rich-snippets schema-org
Platforms
- claude-code
- gemini-cli
- openai-codex
- mcp
Related Skills
Pair schema-markup with these complementary skills:
Frequently Asked Questions
What is schema-markup?
Use this skill when implementing structured data markup using JSON-LD and Schema.org vocabulary for rich search results. Triggers on adding schema markup for FAQ, HowTo, Product, Article, Breadcrumb, Organization, LocalBusiness, Event, Recipe, or any Schema.org type. Covers JSON-LD implementation, Google Rich Results eligibility, validation testing, and framework integration (Next.js, Nuxt, Astro).
How do I install schema-markup?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill schema-markup in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support schema-markup?
This skill works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Schema Markup / Structured Data
Schema markup is machine-readable context added to web pages that tells search engines
what your content means, not just what it says. JSON-LD (JavaScript Object Notation
for Linked Data) is Google's recommended implementation format - injected via a
<script type="application/ld+json"> tag rather than woven into the HTML. Implementing
correct structured data makes pages eligible for rich results in Google Search: star
ratings, FAQ dropdowns, breadcrumb trails, recipe cards, event listings, and more.
Rich results increase click-through rates by making listings visually distinct in the SERP.
When to use this skill
Trigger this skill when the user:
- Wants to implement structured data or schema markup on a page
- Asks about adding FAQ schema, Product schema, Article schema, or any Schema.org type
- Needs to add breadcrumb navigation markup
- Wants to make a page eligible for Google rich results or rich snippets
- Asks to validate or debug structured data errors in Google Search Console
- Needs to integrate JSON-LD into a framework (Next.js, Nuxt, Astro, etc.)
- Asks which schema type to use for a given content type
Do NOT trigger this skill for:
- General on-page SEO (meta tags, title tags, keyword optimization) - use
technical-seo-engineeringinstead - Performance or Core Web Vitals improvements - those are separate concerns
Key principles
Always use JSON-LD format - Google recommends JSON-LD over Microdata and RDFa. JSON-LD keeps structured data separate from HTML, making it easier to maintain and less error-prone. Inject it in
<head>or<body>via a script tag.Only mark up content visible on the page - Google's guidelines explicitly prohibit marking up content that users cannot see. If a product price or FAQ answer is not rendered on the page, do not include it in the schema.
Structured data earns rich results, it does not boost rankings - JSON-LD does not directly improve a page's position in search results. It makes the page eligible for enhanced SERP features (stars, FAQs, breadcrumbs). Eligibility does not guarantee display - Google decides based on query and content quality.
Validate before every deploy - Invalid schema is silently ignored by Google. Run the Rich Results Test and Schema.org Validator on every significant change. See
references/validation-testing.md.One primary type per page, plus supporting types - Each page should have one main
@typematching its primary content (e.g.Product,Article,FAQPage). Supplementary types likeBreadcrumbListorOrganizationcan be added as additional top-level objects in the same script tag or a separate one.
Core concepts
Schema.org vocabulary is a collaborative ontology backed by Google, Bing, Yahoo,
and Yandex. Every valid type and property is documented at schema.org. The vocabulary
is hierarchical: LocalBusiness extends Organization which extends Thing. Properties
from parent types are inherited by all child types.
JSON-LD structure revolves around three core fields:
@context: always"https://schema.org"- declares the vocabulary@type: the Schema.org type (e.g."Product","FAQPage","BreadcrumbList")@id: optional stable URL identifier for the entity (helps Google's Knowledge Graph)
Nesting allows rich relationships. A Product can nest AggregateRating and
Offer objects directly. A HowTo nests HowToStep items. Nesting is preferred
over flat data when the relationship is semantically meaningful.
Required vs recommended properties - Google's documentation distinguishes between properties required for eligibility and those that are recommended for better rich result appearance. Missing required fields causes the rich result to be suppressed. Missing recommended fields may reduce display richness.
Rich Results eligibility is type-specific. Not every Schema.org type produces
a rich result. Google-supported types include: Article, Breadcrumb, Event, FAQ,
HowTo, JobPosting, LocalBusiness, Product, Recipe, Review, VideoObject, and others.
See references/schema-types-catalog.md for the full list with requirements.
Common tasks
Implement Product schema with offers and ratings
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Wireless Noise-Cancelling Headphones",
"image": "https://example.com/images/headphones.jpg",
"description": "Premium wireless headphones with 30-hour battery life.",
"sku": "WH-1000XM5",
"brand": {
"@type": "Brand",
"name": "SoundMax"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "2048"
},
"offers": {
"@type": "Offer",
"url": "https://example.com/headphones",
"priceCurrency": "USD",
"price": "299.99",
"priceValidUntil": "2025-12-31",
"itemCondition": "https://schema.org/NewCondition",
"availability": "https://schema.org/InStock"
}
}
</script>Required fields for Product rich results: name, image, plus at least one of
aggregateRating, offers, or review. Always use https://schema.org/ URLs
for itemCondition and availability values.
Add FAQPage schema
Use FAQPage when the page contains a list of question-and-answer pairs where
the user is seeking answers (not a community Q&A page). Each question must appear
on the page - do not include hidden FAQ items.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is your return policy?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We accept returns within 30 days of purchase. Items must be in original condition."
}
},
{
"@type": "Question",
"name": "Do you offer free shipping?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Free standard shipping on orders over $50 within the contiguous United States."
}
}
]
}
</script>Implement BreadcrumbList
Breadcrumbs in schema must match the breadcrumb navigation visible on the page. Position values must start at 1 and increment sequentially.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://example.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Electronics",
"item": "https://example.com/electronics"
},
{
"@type": "ListItem",
"position": 3,
"name": "Headphones",
"item": "https://example.com/electronics/headphones"
}
]
}
</script>Add Article schema for blog posts
Use Article for news and blog content, BlogPosting for blog-specific posts.
Both produce the same rich result treatment; BlogPosting is a subtype of Article.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "10 Tips for Better Sleep",
"image": "https://example.com/images/sleep-tips.jpg",
"author": {
"@type": "Person",
"name": "Dr. Jane Smith",
"url": "https://example.com/authors/jane-smith"
},
"publisher": {
"@type": "Organization",
"name": "Wellness Daily",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"datePublished": "2024-11-15",
"dateModified": "2025-01-20",
"description": "Evidence-based sleep hygiene tips from a certified sleep specialist."
}
</script>Implement Organization / LocalBusiness schema
Place Organization on the homepage or about page. Use LocalBusiness (or a
more specific subtype like Restaurant, MedicalBusiness) for businesses with
a physical location.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Green Leaf Cafe",
"image": "https://example.com/cafe.jpg",
"@id": "https://example.com/#business",
"url": "https://example.com",
"telephone": "+1-555-234-5678",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main Street",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97201",
"addressCountry": "US"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "07:00",
"closes": "18:00"
}
],
"geo": {
"@type": "GeoCoordinates",
"latitude": 45.5231,
"longitude": -122.6765
}
}
</script>Add HowTo schema
Use HowTo for step-by-step instructional content. Each step should have a
name (short step title) and text (detailed description). Steps can include
images for richer display.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Change a Bicycle Tire",
"description": "Step-by-step guide to replacing a flat bicycle tire at home.",
"totalTime": "PT20M",
"tool": [
{ "@type": "HowToTool", "name": "Tire levers" },
{ "@type": "HowToTool", "name": "Pump" }
],
"step": [
{
"@type": "HowToStep",
"position": 1,
"name": "Remove the wheel",
"text": "Loosen the axle nuts or quick-release lever and pull the wheel free from the dropouts."
},
{
"@type": "HowToStep",
"position": 2,
"name": "Remove the tire",
"text": "Insert tire levers under the tire bead and work them around the rim to pop the tire off."
},
{
"@type": "HowToStep",
"position": 3,
"name": "Install the new tube",
"text": "Place the new inner tube inside the tire, seat the valve through the rim hole, then press the tire back onto the rim."
}
]
}
</script>Framework integration - Next.js App Router
In Next.js App Router, inject JSON-LD using a script tag inside the page component.
Do not use next/head for this - it is not needed for JSON-LD.
// app/products/[slug]/page.tsx
export default function ProductPage({ product }: { product: Product }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"description": product.description,
"offers": {
"@type": "Offer",
"price": product.price,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* page content */}
</>
);
}For Nuxt, use useHead() composable with a script entry. For Astro, inject the
script tag directly in the .astro component's <head> slot. Both follow the
same pattern: serialize the object and inject as type="application/ld+json".
Gotchas
Rich Results Test passes but nothing appears in SERPs - Passing validation only confirms syntax correctness. Google still decides whether to display rich results based on content quality, query relevance, and site authority. Schema eligibility is not a guarantee of display.
Product schema with dynamic pricing goes stale - If
priceoravailabilityare hardcoded in the template rather than dynamically rendered from live data, they drift out of sync. Google issues manual actions for misleading structured data. Always generate these fields server-side from live inventory.FAQPage on community Q&A pages triggers spam detection -
FAQPageis for single-author authoritative Q&A (e.g., a brand's own FAQ). Marking up a community forum or user-generated Q&A asFAQPageviolates Google's guidelines and can result in the rich result being suppressed across the entire domain.Multiple
@typeblocks for the same entity cause conflicts - Having two separate<script type="application/ld+json">blocks that both describe the sameProductorOrganizationcreates conflicting signals. Combine into one block or use an array at the top level.Next.js
dangerouslySetInnerHTMLwithout JSON sanitization - If any product name or description contains a</script>string (possible in user-generated content), it will break the JSON-LD block. Always sanitize dynamic strings or use a JSON serialization library that escapes special characters.
Anti-patterns / common mistakes
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
| Marking up hidden content | Google's guidelines prohibit schema for content not rendered to users. Penalizable as spam. | Only include data that is visible and readable on the page |
Duplicate @type declarations |
Multiple conflicting schema blocks for the same entity confuse parsers and waste crawl budget | Use one block per entity; combine supporting types in the same <script> tag as an array |
| Using Microdata instead of JSON-LD | Microdata is tightly coupled to HTML structure, harder to maintain, and error-prone when HTML changes | Use JSON-LD exclusively; it is decoupled from HTML markup |
Wrong availability / itemCondition values |
Using plain strings like "InStock" instead of the full schema.org URL causes validation errors |
Use full URLs: "https://schema.org/InStock", "https://schema.org/NewCondition" |
| Skipping validation before deploy | Invalid schema is silently ignored - no error, no rich result, no feedback loop | Run Rich Results Test at search.google.com/test/rich-results before every deploy |
| Assuming schema improves rankings | Schema does not directly affect ranking position; misplaced expectations lead to wasted effort | Use schema for rich result eligibility and CTR improvement, not ranking manipulation |
| Stale price / availability data | Product offers with outdated prices trigger Search Console warnings and damage trust | Keep price and availability dynamically generated from live data; set priceValidUntil |
References
For deep detail on specific topics, load the relevant file from references/:
references/schema-types-catalog.md- Full catalog of Google-supported Schema.org types with required/recommended fields and JSON-LD examples. Load when selecting the right type or checking required properties.references/validation-testing.md- How to validate structured data with Rich Results Test, Search Console, and CI/CD integration. Load when debugging schema errors or setting up automated validation.
Only load a references file if the current task requires detail beyond what is in this SKILL.md.
References
schema-types-catalog.md
Schema Types Catalog
This catalog covers Schema.org types supported by Google for rich results. For each type: required fields (missing these suppresses the rich result), recommended fields (improve display richness), and a minimal JSON-LD example.
Full reference: https://developers.google.com/search/docs/appearance/structured-data/search-gallery
Article / BlogPosting / NewsArticle
Use for: News articles, blog posts, sports articles, opinion pieces.
Required:
headline(max 110 characters)image(at least one image, min 1200px wide recommended)datePublishedauthorwith@type: PersonorOrganizationandname
Recommended:
dateModifiedpublisherwithlogodescription
{
"@context": "https://schema.org",
"@type": "NewsArticle",
"headline": "New Study Links Sleep Quality to Productivity",
"image": "https://example.com/sleep-study.jpg",
"datePublished": "2025-03-01",
"dateModified": "2025-03-05",
"author": {
"@type": "Person",
"name": "Dr. Alice Kim"
},
"publisher": {
"@type": "Organization",
"name": "Health News Daily",
"logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" }
}
}FAQPage
Use for: Pages with a list of question-and-answer pairs. Each Q&A must be visible on the page. Do NOT use for community Q&A (Stack Overflow style) or where there are multiple competing answers.
Required:
mainEntityarray ofQuestionobjects- Each
Questionneedsname(the question) andacceptedAnswer - Each
acceptedAnswerneedstext
Recommended:
- Keep answers under 300 characters for best display in SERPs
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What payment methods do you accept?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We accept Visa, Mastercard, American Express, PayPal, and Apple Pay."
}
}
]
}HowTo
Use for: Step-by-step instructional pages (how to cook something, fix something, build something). Not for recipes (use Recipe type).
Required:
name(title of the how-to)steparray ofHowToStepobjects- Each
HowToStepneedstext
Recommended:
HowToStep.name(short step headline)HowToStep.imagetotalTime(ISO 8601 duration, e.g."PT30M")estimatedCosttoolandsupplyarrays
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Repot a Succulent",
"totalTime": "PT15M",
"step": [
{
"@type": "HowToStep",
"position": 1,
"name": "Choose the right pot",
"text": "Select a pot one size larger with drainage holes at the bottom."
},
{
"@type": "HowToStep",
"position": 2,
"name": "Add fresh soil",
"text": "Fill the new pot with cactus/succulent potting mix to about one-third full."
}
]
}Product
Use for: Individual product pages. Requires at least one of aggregateRating,
offers, or review to be eligible for a rich result.
Required (for rich result):
nameimage- At least one of:
aggregateRating,offers,review
Recommended:
descriptionskubrandoffers.price+offers.priceCurrency+offers.availabilityoffers.priceValidUntilaggregateRating.ratingValue+aggregateRating.reviewCount
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Running Shoes Pro X",
"image": "https://example.com/shoes.jpg",
"sku": "RSX-42",
"brand": { "@type": "Brand", "name": "SwiftRun" },
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": "312"
},
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "129.99",
"priceValidUntil": "2025-12-31",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition"
}
}BreadcrumbList
Use for: Navigation breadcrumbs shown on the page. Appears as a breadcrumb path in the SERP URL line (replaces the URL display).
Required:
itemListElementarray ofListItemobjects- Each
ListItemneedsposition(1-indexed integer),name, anditem(URL)
Recommended:
- Include every breadcrumb level visible on the page
- The last item (current page) may omit
itemURL
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com" },
{ "@type": "ListItem", "position": 2, "name": "Shoes", "item": "https://example.com/shoes" },
{ "@type": "ListItem", "position": 3, "name": "Running Shoes Pro X" }
]
}Organization
Use for: Company/brand identity on the homepage or about page. Helps populate
Google's Knowledge Panel. Use @id with a stable URL to link across pages.
Required: None strictly required for Knowledge Panel eligibility, but include as much as possible.
Recommended:
name,url,logo,contactPoint,sameAs(social profiles),@id
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Acme Corp",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-800-555-1234",
"contactType": "customer service"
},
"sameAs": [
"https://twitter.com/acmecorp",
"https://linkedin.com/company/acmecorp"
]
}LocalBusiness
Use for: Businesses with a physical location. Extend with a more specific subtype
when applicable: Restaurant, Store, MedicalBusiness, AutoDealer, LodgingBusiness, etc.
Required (for local rich results):
name,address(withPostalAddress),telephoneorurl
Recommended:
openingHoursSpecification,geo,priceRange,image,servesCuisine(for restaurants)
{
"@context": "https://schema.org",
"@type": "Restaurant",
"name": "Sakura Sushi",
"address": {
"@type": "PostalAddress",
"streetAddress": "456 Oak Avenue",
"addressLocality": "Seattle",
"addressRegion": "WA",
"postalCode": "98101",
"addressCountry": "US"
},
"telephone": "+1-206-555-9876",
"servesCuisine": "Japanese",
"priceRange": "$$",
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "11:30",
"closes": "22:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Saturday", "Sunday"],
"opens": "12:00",
"closes": "23:00"
}
]
}Event
Use for: Events with a specific date, time, and location (concerts, conferences,
webinars). Online events use eventAttendanceMode: OnlineEventAttendanceMode.
Required:
name,startDate,locationlocationmust be aPlace(withnameandaddress) orVirtualLocationeventStatus(usehttps://schema.org/EventScheduled)
Recommended:
endDate,description,image,organizer,offers,eventAttendanceMode
{
"@context": "https://schema.org",
"@type": "Event",
"name": "FrontendConf 2025",
"startDate": "2025-09-20T09:00",
"endDate": "2025-09-21T18:00",
"eventStatus": "https://schema.org/EventScheduled",
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
"location": {
"@type": "Place",
"name": "Portland Convention Center",
"address": {
"@type": "PostalAddress",
"streetAddress": "777 NE Martin Luther King Jr Blvd",
"addressLocality": "Portland",
"addressRegion": "OR",
"addressCountry": "US"
}
},
"offers": {
"@type": "Offer",
"url": "https://example.com/tickets",
"price": "399",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}Recipe
Use for: Food and drink recipes. One of the richest rich result types - can show image, ratings, cook time, and ingredients directly in the SERP.
Required:
name,image,author,datePublished,descriptionrecipeIngredient(array of ingredient strings)recipeInstructions(array ofHowToStep)recipeYield,prepTime,cookTime(ISO 8601 durations)
Recommended:
aggregateRating,nutrition,recipeCategory,recipeCuisine,keywords
{
"@context": "https://schema.org",
"@type": "Recipe",
"name": "Classic Banana Bread",
"image": "https://example.com/banana-bread.jpg",
"author": { "@type": "Person", "name": "Chef Maria" },
"datePublished": "2024-08-10",
"prepTime": "PT15M",
"cookTime": "PT60M",
"recipeYield": "1 loaf",
"recipeIngredient": [
"3 ripe bananas", "1/3 cup melted butter", "3/4 cup sugar", "1 egg", "1 tsp vanilla"
],
"recipeInstructions": [
{ "@type": "HowToStep", "text": "Preheat oven to 350°F (175°C)." },
{ "@type": "HowToStep", "text": "Mash bananas and mix in butter, sugar, egg, and vanilla." },
{ "@type": "HowToStep", "text": "Fold in flour and baking soda, pour into loaf pan, bake 60 minutes." }
],
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.8", "reviewCount": "543" }
}VideoObject
Use for: Pages where a video is the primary content. Enables video rich results with thumbnail, duration, and upload date in SERPs.
Required:
name,description,thumbnailUrl,uploadDate
Recommended:
duration(ISO 8601),contentUrlorembedUrl,expires
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "How to Deploy a Next.js App to Vercel",
"description": "Complete walkthrough of deploying a Next.js app with environment variables and custom domains.",
"thumbnailUrl": "https://example.com/thumbnail.jpg",
"uploadDate": "2025-01-15",
"duration": "PT12M30S",
"embedUrl": "https://www.youtube.com/embed/abc123"
}SoftwareApplication
Use for: Software apps, mobile apps, or web apps. Can show ratings, OS support, and price in SERPs.
Required:
name,operatingSystem,applicationCategory
Recommended:
offers,aggregateRating,screenshot
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "TaskFlow",
"operatingSystem": "iOS, Android, Web",
"applicationCategory": "ProductivityApplication",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"ratingCount": "8200"
}
}Course
Use for: Online courses and educational content. Shows course provider and description in SERPs.
Required:
name,description,provider
Recommended:
hasCourseInstance(withcourseMode,startDate),offers
{
"@context": "https://schema.org",
"@type": "Course",
"name": "Advanced JavaScript: Closures, Async, and Modules",
"description": "Master advanced JavaScript concepts used in real-world applications.",
"provider": {
"@type": "Organization",
"name": "CodeAcademy Pro",
"sameAs": "https://codeacademypro.com"
},
"hasCourseInstance": {
"@type": "CourseInstance",
"courseMode": "online",
"courseSchedule": { "@type": "Schedule", "repeatFrequency": "self-paced" }
}
}JobPosting
Use for: Job listings. Appears as rich results in Google Jobs integration.
Required:
title,description,datePosted,validThrough,hiringOrganization,jobLocation
Recommended:
employmentType,baseSalary,identifier,applicantLocationRequirements
{
"@context": "https://schema.org",
"@type": "JobPosting",
"title": "Senior Frontend Engineer",
"description": "Build and maintain React-based interfaces for our SaaS platform.",
"datePosted": "2025-03-01",
"validThrough": "2025-06-01",
"employmentType": "FULL_TIME",
"hiringOrganization": {
"@type": "Organization",
"name": "BuildFast Inc.",
"sameAs": "https://buildfast.io",
"logo": "https://buildfast.io/logo.png"
},
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"addressCountry": "US"
}
},
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": {
"@type": "QuantitativeValue",
"minValue": 150000,
"maxValue": 200000,
"unitText": "YEAR"
}
}
}Multiple types on one page
When a page has more than one schema type, use an array at the top level or include
separate <script type="application/ld+json"> blocks:
<!-- Option A: array in one block -->
<script type="application/ld+json">
[
{ "@context": "https://schema.org", "@type": "Product", "name": "..." },
{ "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [...] }
]
</script>
<!-- Option B: separate blocks (also valid) -->
<script type="application/ld+json">
{ "@context": "https://schema.org", "@type": "Product", "name": "..." }
</script>
<script type="application/ld+json">
{ "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [...] }
</script>Both approaches are valid. Arrays in a single block are preferred for organization.
validation-testing.md
Validation & Testing for Structured Data
Structured data errors are silent - invalid JSON-LD is simply ignored by Google with no visible error. A working rich result requires syntactically valid JSON-LD, schema.org vocabulary compliance, and meeting Google's content guidelines. This file covers the tools and workflows to catch errors before they reach production.
Validation tools overview
| Tool | When to use | URL |
|---|---|---|
| Google Rich Results Test | Primary check before deploy - tests live URL or code snippet for rich result eligibility | search.google.com/test/rich-results |
| Schema.org Validator | Checks schema.org vocabulary compliance (catches typos in property names) | validator.schema.org |
| Google Search Console > Enhancements | Monitor rich result health in production over time | search.google.com/search-console |
| Lighthouse SEO Audit | Quick check in dev tools / CI - flags structured data issues among other SEO problems | Built into Chrome DevTools |
Google Rich Results Test
The primary tool for checking whether a page's structured data qualifies for a rich result. Accepts either a live URL or pasted HTML/JSON-LD code snippet.
How to use:
- Go to https://search.google.com/test/rich-results
- Paste a URL or paste HTML directly
- Review detected rich result types and any errors/warnings
Output interpretation:
- Eligible for rich results - Schema is valid and meets requirements
- Warnings - Schema is valid but missing recommended fields (rich result may be partial)
- Errors - Required fields missing or invalid values; rich result suppressed
Common errors and fixes:
| Error message | Cause | Fix |
|---|---|---|
| "Missing field 'name'" | Required property absent | Add the name property to the root type |
| "Either 'offers', 'review', or 'aggregateRating' should be specified" | Product type without qualifying nested property | Add at least one of these three objects |
| "Invalid URL in field 'availability'" | Using "InStock" instead of full URL |
Use "https://schema.org/InStock" |
| "Invalid value for field 'ratingValue'" | Rating is a number not within 0-5 range | Ensure value is between 0 and 5; use string representation "4.5" |
| "Invalid value for field 'position'" | BreadcrumbList position values are not sequential from 1 | Start at 1, increment by 1 for each item |
| "Invalid ISO 8601 date" | Dates formatted incorrectly | Use YYYY-MM-DD for dates, YYYY-MM-DDTHH:MM for datetimes |
Schema.org Validator
Tests vocabulary compliance against the schema.org specification. Catches property
name typos (e.g., ratingValues instead of ratingValue) that the Rich Results
Test may not surface.
How to use:
- Go to https://validator.schema.org
- Paste JSON-LD or enter a URL
- Review property warnings
Note: The schema.org validator flags issues with the vocabulary itself but does not test Google's specific rich result requirements. Use both tools for full coverage.
Google Search Console Enhancements
After deploying structured data to production, monitor ongoing health in Search Console under Enhancements in the left sidebar. Each supported schema type has its own report (FAQs, Products, Breadcrumbs, Events, etc.).
What to monitor:
- Valid items - Pages with correct schema producing rich results
- Valid with warnings - Schema valid but missing recommended fields
- Invalid - Errors preventing rich results; Google provides the specific error message and affected URLs
Workflow for fixing production errors:
- Open the relevant Enhancement report
- Click an error entry to see affected URLs and the specific error
- Fix the schema in your codebase
- Re-deploy
- Use the "Validate Fix" button in Search Console to request re-crawl (results in 1-7 days)
Lighthouse SEO Audit
Lighthouse runs in Chrome DevTools (Audits tab), via the CLI, or in CI/CD. The SEO category includes a "Structured data is valid" check.
Run via CLI:
npx lighthouse https://example.com --only-categories=seo --output json | \
jq '.categories.seo.auditRefs[] | select(.id == "structured-data")'Limitations: Lighthouse's structured data check is basic - it detects invalid JSON but does not check schema.org property requirements or Google's rich result eligibility rules. Use it as a first-pass sanity check, not a replacement for the Rich Results Test.
CI/CD integration
Automate schema validation in your pipeline to catch regressions before deploy.
Option 1 - schema-dts type checking (TypeScript)
schema-dts is a Google-maintained TypeScript type definitions package for schema.org.
It catches property name errors and type mismatches at build time.
npm install schema-dtsimport { Product, WithContext } from 'schema-dts';
const productSchema: WithContext<Product> = {
"@context": "https://schema.org",
"@type": "Product",
"name": "Running Shoes Pro X",
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "129.99",
"availability": "https://schema.org/InStock"
}
};
// TypeScript will error on unknown properties or wrong value typesOption 2 - structured-data-testing-tool CLI
Google deprecated its standalone CLI, but the community-maintained structured-data-linter
can validate JSON-LD files in CI:
npm install -g structured-data-linter
structured-data-linter path/to/schema.jsonOption 3 - Playwright / Puppeteer extraction test
Extract and validate JSON-LD from rendered HTML in an end-to-end test:
// Using Playwright
import { test, expect } from '@playwright/test';
test('product page has valid JSON-LD', async ({ page }) => {
await page.goto('/products/running-shoes');
const schemas = await page.evaluate(() => {
return Array.from(document.querySelectorAll('script[type="application/ld+json"]'))
.map(s => JSON.parse(s.textContent ?? '{}'));
});
const product = schemas.find(s => s['@type'] === 'Product');
expect(product).toBeDefined();
expect(product?.name).toBeTruthy();
expect(product?.offers?.priceCurrency).toBe('USD');
expect(product?.offers?.availability).toBe('https://schema.org/InStock');
});Option 4 - GitHub Actions with Rich Results Test API
Google provides a Rich Results Test API (paid, part of Search API) for automated URL testing. For most projects, the Playwright extraction test above is sufficient without requiring API access.
Framework-specific patterns
Next.js App Router
Inject JSON-LD as a <script> tag inside the page component. Use JSON.stringify
with a replacer to prevent XSS via user-generated content in schema values.
// app/products/[slug]/page.tsx
function JsonLd({ data }: { data: object }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
export default function ProductPage({ product }: { product: Product }) {
const schema = {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"offers": {
"@type": "Offer",
"price": product.price,
"priceCurrency": "USD"
}
};
return (
<>
<JsonLd data={schema} />
<h1>{product.name}</h1>
</>
);
}For dynamic pages, generate schema data server-side using the same data fetching function that populates page content to ensure schema stays in sync with displayed data.
Nuxt 3
Use useHead() composable to inject structured data. This works with both SSR and
SSG rendering modes.
// pages/products/[slug].vue
<script setup lang="ts">
const { data: product } = await useFetch(`/api/products/${route.params.slug}`);
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: product.value?.name,
offers: {
'@type': 'Offer',
price: product.value?.price,
priceCurrency: 'USD',
availability: 'https://schema.org/InStock'
}
})
}
]
});
</script>Astro
In Astro components, inject the script tag directly in the <head> slot or inline
in the component. JSON.stringify is called at build time for static pages.
---
// src/pages/products/[slug].astro
const { product } = Astro.props;
const schema = {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"offers": {
"@type": "Offer",
"price": product.price,
"priceCurrency": "USD"
}
};
---
<html>
<head>
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
</head>
<body>
<h1>{product.name}</h1>
</body>
</html>Common validation gotchas
JSON syntax errors - A single trailing comma or unescaped quote in a description
field breaks the entire JSON-LD block. Validate JSON syntax separately from schema
compliance: JSON.parse(yourSchemaString) in the browser console is the fastest check.
Missing @context on nested types - Only the root object needs "@context": "https://schema.org".
Nested objects like Offer, AggregateRating, and PostalAddress should NOT repeat @context.
Date format mismatches - Google requires ISO 8601 dates. "2025-3-1" is invalid;
use "2025-03-01". For datetimes: "2025-03-01T09:00:00" or "2025-03-01T09:00:00+05:30".
Duration format - ISO 8601 durations use PT prefix for time: PT30M (30 minutes),
PT1H30M (1.5 hours), P1Y (1 year). "30 minutes" as a plain string is invalid.
Schema in SPAs - In Single Page Applications, ensure JSON-LD is rendered in the initial server-side HTML, not injected only by client-side JavaScript. Googlebot can execute JavaScript but prefers statically rendered structured data.
Frequently Asked Questions
What is schema-markup?
Use this skill when implementing structured data markup using JSON-LD and Schema.org vocabulary for rich search results. Triggers on adding schema markup for FAQ, HowTo, Product, Article, Breadcrumb, Organization, LocalBusiness, Event, Recipe, or any Schema.org type. Covers JSON-LD implementation, Google Rich Results eligibility, validation testing, and framework integration (Next.js, Nuxt, Astro).
How do I install schema-markup?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill schema-markup in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support schema-markup?
schema-markup works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.