Install
$ agentstack add skill-thibaultbm-claude-seo-geo-seo-schema-markup ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Schema markup that still pays in 2026
Structured data is the most machine-readable layer of a page: prices, ratings, dates, authors, and entity relationships declared in a format engines parse without guessing. Google reads it for rich results and Merchant listings; LLM grounding pipelines read it as a fact source. Between 2023 and 2026 Google removed several rich result types, so effort that used to pay (FAQ, HowTo) now buys nothing in the SERP. This skill implements what still earns surfaces, skips what is dead, and keeps every claim inside Google's guidelines.
One principle governs everything: markup describes the visible page. It never adds facts the user cannot see.
Company knowledge first (Obsidian)
If the working environment contains an Obsidian vault or any local knowledge base (a folder of .md notes, often with a .obsidian directory), read the relevant notes before acting: brand and product facts, target keywords, competitors, and the SEO action log of what was already tried. Ground every recommendation in that context instead of asking the user for facts the vault already holds. At the end of the session, append the actions taken to the vault's SEO action log so the next session starts informed. Vault structure, read-first and write-back protocols: the obsidian-brain skill.
When to use this skill
Use it for:
- Implementing JSON-LD on any page type (home, blog post, product, location, author, video, event)
- Auditing existing markup for validity, eligibility, and honesty
- Rich result work: review stars, prices, availability, breadcrumbs, video
- Entity work: Organization, sameAs, author Person graphs for E-E-A-T
- Deciding which schema types deserve effort in 2026 (status table below)
- Debugging Rich Results Test errors or rich results that disappeared
When schema is NOT the answer:
- "Our FAQ dropdowns vanished from Google": that is Google removing the feature (May 2026), not a markup bug. Check the status table before debugging.
- "Make us rank higher with schema": structured data disambiguates and unlocks result features; it is not a ranking lever on its own. Set that expectation early.
- "The page is not indexed at all": fix crawl and indexation first with the seo-technical skill; schema on an unindexed page is decoration.
Hand off neighboring problems:
- Crawlability, JavaScript rendering, AI crawler access: use the seo-technical skill (its references/ai-crawlers.md is the canonical crawler reference)
- Writing content that AI engines quote: use the geo-visibility skill
- Google Business Profile and local rankings: use the seo-local skill
- Page structure and copy for products, services, collections: use the seo-content-product-page, seo-content-service-page, and seo-content-collection-page skills
Workflow
Step 1: Inventory what is already there
Fetch the raw served HTML and check for structured data blocks:
# How many JSON-LD blocks does the server actually send curl -sL https://example.com/page/ | grep -c 'application/ld+json'
# Legacy microdata that may conflict with JSON-LD curl -sL https://example.com/page/ | grep -c 'itemscope'
Why raw HTML: JSON-LD injected client-side (Google Tag Manager, SPA hydration) is unreliable. Googlebot usually picks it up after rendering, but AI crawlers never execute JavaScript (see the seo-technical skill), and any validator pointed at the source misses it. Structured data belongs in the server response. For a quick automated sweep of on-page basics alongside schema, run the bundled audit script from the seo-geo-audit skill (scripts/seo_audit.py), which checks robots.txt AI bot rules, sitemap, and on-page basics.
Record per page type: which schema types exist, whether they validate, and whether the values match the visible page. The third question catches the violations that matter; the first two are mechanical.
Step 2: Map page types to schema types
| Page type | Implement | Skip | |---|---|---| | Every page | Organization reference (@id), BreadcrumbList | FAQPage for rich results | | Homepage | Organization (full node) plus WebSite | | | Blog post | Article or BlogPosting, author Person, BreadcrumbList | HowTo | | Product page | Product, Offer, AggregateRating and Review when real reviews are displayed | Invented ratings | | Service page | Service or WebPage, Organization reference | Self-serving review stars | | Location page | LocalBusiness, most specific subtype available (pair with the seo-local skill) | LocalBusiness on a business with no physical premises | | Author page | Person or ProfilePage | | | Video page | VideoObject | | | Event page | Event | | | SaaS pricing page | Product or Service with Offer when prices are public | "From" prices not shown on the page | | Careers page | JobPosting (active Google jobs surface) | |
Step 3: Check what the CMS already outputs (field notes)
Most schema problems in real audits are duplication problems, not absence problems. Check before adding anything:
| Platform | What you typically find | What to do | |---|---|---| | WordPress with Yoast or Rank Math | A complete @graph already output: Organization, WebSite, Article, BreadcrumbList | Configure the plugin (logo, sameAs, author pages) instead of pasting a second block; two diverging Organizations is the classic self-inflicted wound | | Shopify | Theme ships Product JSON-LD by default | Verify values against the visible page (sale price versus compare-at price, missing brand or sku); fix the theme markup rather than adding a duplicate Product | | Webflow | No automatic schema | Add per-template embeds bound to CMS fields so values update with content | | Headless or SPA builds | Markup present in the repo, absent from the served HTML | Move it into the server response; confirm with the curl check from Step 1 |
Step 4: Apply the 2026 status table
Spend implementation effort top-down by verdict. The full table is in Rules and thresholds; the two headline facts: FAQPage rich results no longer exist for anyone (removed May 7, 2026), and HowTo has had no surface since 2023. Do not let a client pay for either expecting SERP features.
Step 5: Build one entity graph, not isolated blocks
Engines reconcile entities; help them by linking nodes with stable @id values:
- Organization: @id {{SITE_URL}}/#organization, declared fully once (homepage or sitewide), referenced everywhere else
- Each author: @id {{SITE_URL}}/team/{{slug}}/#person
- Each Article: author points to the Person @id, publisher points to the Organization @id
The shape of a wired blog post, abbreviated (full templates in references/jsonld-templates.md):
{ "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://example.com/#organization", "name": "Example Co" }, { "@type": "Person", "@id": "https://example.com/team/jane/#person", "name": "Jane Doe", "worksFor": { "@id": "https://example.com/#organization" } }, { "@type": "Article", "headline": "Example headline", "datePublished": "2026-04-02", "dateModified": "2026-05-28", "author": { "@id": "https://example.com/team/jane/#person" }, "publisher": { "@id": "https://example.com/#organization" } } ] }
Why it matters: three pages each declaring a slightly different Organization (name variants, different logos) fragment the entity. One canonical node referenced by @id keeps Google's and LLMs' view of the company consistent, and it is the cheapest E-E-A-T plumbing available: every article provably attached to a named author, every author attached to the organization.
Organization completeness, since this node feeds knowledge panels: logo at 112x112 px or larger on a crawlable URL, legal identifiers where applicable (vatID, iso6523Code), founder and foundingDate when public. Registry-grade identifiers give LLMs unambiguous hooks to reconcile the entity against company databases.
Step 6: Implement as JSON-LD
- One script tag of type application/ld+json per graph, in head or body (Google accepts both).
- JSON-LD is the format Google recommends over microdata and RDFa: decoupled from the HTML, it survives redesigns and template changes (https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data).
- If microdata already exists (theme-generated), either remove it or keep its values strictly identical to the JSON-LD. Conflicting duplicate markup is worse than either alone because engines pick unpredictably.
- Bind values to CMS fields, never hardcode prices or dates into templates: hardcoded values drift from the visible page within weeks (field observation).
Step 7: Validate twice, then verify in production
- https://validator.schema.org checks vocabulary and syntax (catches typos, wrong types, broken nesting).
- https://search.google.com/test/rich-results checks Google eligibility (catches missing required fields per rich result type). Both are free, web-based, and need no API key.
- After deploy: curl the production URL and confirm the JSON-LD survived the build. SPA and static-site builds sometimes strip it or move it to client-side injection, which AI crawlers never see.
- Over the following weeks: watch the GSC Enhancements reports per type (error counts, valid item trends). A rich result that disappears with zero new errors usually means Google changed the feature, not the site; check the status table before debugging.
Step 8: Scale across hundreds of pages
- Generate from data, never by hand: bind template fields to the product database or CMS collections. Hand-edited JSON drifts from the page and breaks on the first locale-formatted date.
- Roll out one template at a time (all product pages, then all articles), validate a sample of 3 to 5 URLs per template, and watch the matching GSC Enhancements report for a week before starting the next type.
- Spot-check after every theme or plugin update: updates silently duplicate or reorder markup more often than they break it visibly (field observation from 115+ audits).
- Keep a one-page registry of which template outputs which types and where the code lives. The next audit starts there instead of rediscovering everything.
Troubleshooting lost rich results (runbook)
Stars, prices, or breadcrumbs vanished from the SERP. Check in this order; most cases resolve at 1 or 2:
- Did Google remove the feature for everyone? Check the status table: FAQ (May 2026), HowTo (2023), sitelinks search box (2024) are gone regardless of markup quality.
- GSC Enhancements: new errors, or valid items dropping to zero, indicate a deploy that stripped or broke the block. Confirm with the Step 1 curl against production.
- Policy trip: self-serving reviews, markup describing content that was edited off the visible page, or ratings that no longer match displayed reviews.
- SERP-level reduction: when the markup validates and competitors lost the same feature on the same queries, Google reduced rich result density there; nothing to fix on the site.
- Manual action: GSC Manual Actions, structured data section, after aggressive or invisible markup. Clean, then request reconsideration.
Rules and thresholds
The 2026 status table
| Type | Status | Verdict | |---|---|---| | FAQPage | Rich results restricted to government and health sites in August 2023, removed entirely on May 7, 2026 (https://www.searchenginejournal.com/google-drops-faq-rich-results-from-search/574429/) | No SERP gain for anyone. The markup stays valid and harmless. What helps AI extraction is the visible question-and-answer format of the content, not the markup (see geo-visibility) | | HowTo | Rich results removed in 2023, no surface since (https://developers.google.com/search/blog/2023/08/howto-faq-changes) | Skip | | Article, BlogPosting | No visual badge, but supplies machine-readable author, datePublished, dateModified | Implement on every post; honest dateModified is the freshness signal answer engines read | | Product, Offer, AggregateRating, Review | Active rich results (price, stars, availability) and the basis of Merchant listings (https://developers.google.com/search/docs/appearance/structured-data/product) | Implement on every product page, visible data only | | Organization plus sameAs | The entity anchor: logo, founder, identifiers, official profiles (https://developers.google.com/search/docs/appearance/structured-data/organization) | Implement sitewide; this is the node LLM entity reconciliation leans on | | WebSite | Sitelinks search box retired in 2024 (https://developers.google.com/search/blog/2024/10/sitelinks-search-box), still names the site entity | Implement, minimal effort | | BreadcrumbList | Active breadcrumb display in results | Implement sitewide | | LocalBusiness | Active (maps and knowledge panel context) | Implement on location pages; pair with the seo-local skill | | Person | No direct rich result; E-E-A-T and author entity plumbing | Implement for every named author | | VideoObject | Active video surfaces | When a video is on the page | | Event | Active event surfaces | When events exist |
Hard rules
- Visible content only. Markup must describe content the user can see on that page. Invisible or inflated markup is a structured data manual action waiting to happen (https://developers.google.com/search/docs/appearance/structured-data/sd-policies).
- Never invent ratings or reviews. No aggregateRating without real reviews displayed on the page. Self-serving review snippets are explicitly ineligible: LocalBusiness and Organization cannot carry ratings they collected and display about themselves (https://developers.google.com/search/docs/appearance/structured-data/review-snippet).
- Dates tell the truth. dateModified changes when content meaningfully changes, never on every rebuild. Engines that detect systematic date-bumping discount the signal entirely.
- Values match the page exactly: same price, same currency, same availability as the visible offer, byte for byte where possible.
- Google eligibility comes from Google's per-type documentation, not from schema.org validity alone. A block can be perfectly valid Schema.org and still miss a required field for the rich result.
- Images referenced in markup (logo, product, article) must live on crawlable URLs you control and meet Google's minimum sizes. A hotlinked image that disappears voids the rich result silently.
High-impact fields per type (eligibility checklist)
The fields that most often make the difference between valid markup and an actual rich result:
| Type | Must have for the surface | Highest-value optional fields | |---|---|---| | Product | name, offers with price, priceCurrency, availability | aggregateRating and review (when real), brand, sku, gtin, image, priceValidUntil | | AggregateRating | ratingValue plus ratingCount or reviewCount | bestRating when the scale is not 1 to 5 | | Article | headline, image, datePublished | dateModified, author with url, mainEntityOfPage | | Organization | name, url, logo | sameAs, founder, foundingDate, contactPoint, legal identifiers (iso6523Code, vatID) | | LocalBusiness | name, address | telephone, geo, openingHoursSpecification, priceRange, specific subtype | | BreadcrumbList | itemListElement with position and name; item URL on all but the last element | | | VideoObject | name, thumbnailUrl, uploadDate | duration, contentUrl, description |
When in doubt, the Rich Results Test reports exactly which required field is missing for the targeted surface; trust it over memory.
Decoding frequent validator errors
| Error (Rich Results Test) | Actual cause | Fix | |---|---|---| | Missing field "aggregateRating" or "review" (warning) | Product has no rating surface filled | Acceptable when no reviews exist; never fill it with invented values to clear the warning | | Invalid object type for field "author" | author set to a plain string | Use a Person
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Thibaultbm
- Source: Thibaultbm/claude-seo-geo
- License: MIT
- Homepage: https://sorank.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.