seo-schema-markup — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited seo-schema-markup (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
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.
Use it for:
When schema is NOT the answer:
Hand off neighboring problems:
Fetch the raw served HTML and check for structured data blocks:
curl -sL https://example.com/page/ | grep -c 'application/ld+json'
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.
| 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) |
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 |
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.
Engines reconcile entities; help them by linking nodes with stable @id values:
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.
Stars, prices, or breadcrumbs vanished from the SERP. Check in this order; most cases resolve at 1 or 2:
| 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 |
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.
| 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 object, ideally an @id reference to the author node |
| Either "ratingCount" or "reviewCount" should be specified | AggregateRating missing its count | Add the real displayed count |
| Date not in ISO 8601 | Locale-formatted dates from the CMS | Output YYYY-MM-DD (or full ISO timestamps) |
| Duplicate field or duplicate type on the page | Plugin @graph plus a manually pasted block | One source of truth per page (Step 3) |
| Parsing error: missing comma, unclosed brace | Hand-edited JSON | Generate from references/jsonld-templates.md; lint before deploy |
Structured data is the densest machine-readable declaration of facts a page can make: price, availability, rating value and count, publication and modification dates, author identity, and entity relationships. LLM grounding and shopping pipelines parse these fields when composing and verifying answers; what is ambiguous in prose becomes explicit in JSON-LD.
What earns the most for AI visibility:
Two prerequisites from the seo-technical skill: AI crawlers read raw HTML only, so JSON-LD injected client-side does not exist for them; and a page blocked or unindexed never gets the chance to be parsed at all. Measuring whether any of this wins citations is the geo-tracking skill's job.
When auditing, return one table plus prioritized fixes:
| Page type | Types found | Valid | Matches visible content | Action |
|---|---|---|---|---|
| Blog post | Article (Yoast @graph) | Yes | dateModified auto-bumped | Stop date-bumping, add author Person |
When implementing, deliver for each page type:
Label every recommendation as one of: official requirement (Google documentation), eligibility requirement (rich result), or judgment call.
Definition of done for any schema task:
| Mistake | Consequence | Do instead |
|---|---|---|
| Marking up content not visible on the page | Manual action risk, trust loss | Markup mirrors the visible page |
| Inventing aggregateRating or importing third-party stars | Ineligible, penalizable | Real on-page reviews only; none displayed means no rating markup |
| Self-collected ratings on Organization or LocalBusiness | Ignored per review snippet rules | Earn ratings on third-party platforms; connect them via sameAs |
| Shipping FAQPage for rich results in 2026 | Zero SERP return on the effort | Visible question-and-answer formatting for AI extraction (geo-visibility) |
| JSON-LD injected via Tag Manager or client-side hydration | AI crawlers and source-level validators never see it | Server-render the script tag |
| Adding a manual block next to the SEO plugin's @graph | Two diverging Organizations, engines pick unpredictably | Configure the plugin; one source of truth per page |
| dateModified bumped on every deploy | Freshness signal discounted | Bump only on real content changes |
| Isolated blocks without @id linking | Fragmented entity, weaker reconciliation | One Organization node, referenced by @id everywhere |
| Templates shipped with {{PLACEHOLDERS}} left in | Broken facts in production | Search the build output for {{ before release |
| LocalBusiness on a business with no physical premises | Misleading markup, knowledge panel confusion | Organization for online-only businesses |
| Hardcoding prices and dates in templates | Markup drifts from the visible page within weeks | Bind every value to a CMS field |
| Marking category or tag pages as Article | Type noise, no surface | CollectionPage or nothing; reserve Article for actual posts |
| Adding Speakable outside news sites | No surface; it never left its limited news beta | Skip it |
| Stuffing keywords into name and description fields | Spam signal, mismatch with the visible page | Names match the visible H1 and title |
| Treating schema.org validity as Google eligibility | Missing required fields, no rich result | Validate with both validator.schema.org and the Rich Results Test |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.