# Schema Markup Audit Workflow

A 9-step process for auditing and implementing structured data (JSON-LD) on any website to improve both traditional SEO and AI attribution. Takes 1-3 hours depending on site size.

---

## Why JSON-LD Only

There are three ways to embed schema.org markup: JSON-LD, Microdata, and RDFa. Use JSON-LD exclusively. It is Google's recommended format, lives in `<script>` tags decoupled from page HTML, and can be audited or edited without touching content markup. The other two formats are woven into the DOM and break when content structure changes.

---

## Two Audiences, Different Stakes

Structured data serves two distinct audiences:

**Search engine crawlers (Google, Bing):** Read schema at indexing time to determine rich result eligibility: breadcrumbs, article metadata, star ratings, product prices. This is the traditional SEO play.

**AI retrieval systems (Perplexity, ChatGPT Search, Google AI Overviews):** Read schema at retrieval time for entity resolution and attribution. When an AI system says "Jane Smith wrote this," it follows the `author @id` to a Person entity, then reads `sameAs` to verify identity across known profiles (LinkedIn, GitHub, X). If that chain is broken, attribution defaults to the domain. Your content was written by yoursite.com, not by you.

Entity graph clarity is the higher-leverage investment because it compounds. Once established, every piece of content you publish inherits the attribution chain automatically.

---

## Step 1: Classify the Site

Site type determines which schema types are required.

| Site type | Primary entity | Secondary entities |
|---|---|---|
| Personal brand / portfolio | `Person` | `WebSite`, `ProfilePage`, `BlogPosting` |
| Agency / consultancy | `Organization` + `Person` (founder) | `WebSite`, `Service`, `BlogPosting` |
| SaaS / tool | `Organization` + `SoftwareApplication` | `WebSite`, `FAQPage`, `BlogPosting` |
| Blog / content site | `Person` or `Organization` | `WebSite`, `BlogPosting`, `BreadcrumbList` |
| Local business | `LocalBusiness` (specific subtype) | `WebSite`, `Review`, `Event` |
| Ecommerce | `Organization` | `Product`, `Offer`, `AggregateRating`, `BreadcrumbList` |

**Important:** A personal brand or consultancy site is NOT `ProfessionalService`. That type is for local business directories. Agencies and consultancies are `Organization`.

---

## Step 2: Audit Existing Schema

Fetch your homepage, about page, a blog post, and any other key page types. Search each HTML source for `application/ld+json`. Build an inventory:

```
Page type   | Schema present                    | Missing / wrong
Homepage    | WebSite, Organization             | Person entity
Blog post   | BlogPosting, BreadcrumbList       | publisher is Person not Organization
About       | Person, ProfilePage               | inconsistent @id, no mainEntityOfPage
```

If any page shows "none" in the Schema column, that page is invisible to AI retrieval systems.

---

## Step 3: Check the Entity Graph

This is the most important step. Five things to verify:

**1. `@id` consistency.** The Person and Organization entities must use the same IRI fragment everywhere they appear across the site. A mismatch (`/about#person` on one page, `/about#edward` on another) creates two separate nodes in the knowledge graph for the same entity. AI systems cannot reconcile them.

Canonical IRI pattern:
- Organization: `https://yourdomain.com/#publisher`
- Person: `https://yourdomain.com/about#person`
- WebSite: `https://yourdomain.com/#website`

Once you define the entity on its canonical page, every other reference should be a pointer only: `{ "@id": "https://yourdomain.com/about#person" }`. Never repeat the full entity definition.

**2. `sameAs` completeness.** Person should link to LinkedIn, GitHub, X/Twitter, and Wikidata if available. Organization should link to its LinkedIn company page and any Crunchbase/official profiles. These are the cross-platform identity signals AI systems use to verify who you are.

**3. Author `@id` chain.** `BlogPosting.author` must reference the Person `@id`, not an inline name string. `{ "author": { "name": "Jane Smith" } }` does not work. It must be `{ "author": { "@id": "https://yourdomain.com/about#person" } }`.

**4. Publisher type.** `publisher` on articles must be an `Organization` (with `logo` as `ImageObject`) for Google Article rich result eligibility. A `Person` as publisher blocks rich results.

**5. Schema/content parity.** Never mark up data the user cannot see on the page. AI systems cross-check and flag mismatches. Google can disqualify a rich result for this.

---

## Step 4: Check the Four Silent Failure Modes

These all pass the schema.org validator and still break attribution or rich results:

1. **`Organization.logo` as a plain URL string.** Must be `{"@type": "ImageObject", "url": "https://..."}`. A plain string passes the validator but blocks Article rich result eligibility.

2. **`author` as an inline name object.** `{"name": "Jane Smith"}` is not machine-followable. Must be `{"@id": "https://yourdomain.com/about#person"}`.

3. **`publisher` pointing to a Person.** Publisher on BlogPosting must be an Organization. A Person publisher passes validation but blocks rich results.

4. **Schema injected via JavaScript after page load.** Must be server-rendered in the HTML source. Client-side injection is missed by validators and some crawlers.

---

## Step 5: Check Agentic Search Readiness

Five additional checks for AI retrieval optimization:

1. **Attribution chain completeness.** Follow the full chain: `BlogPosting.author` → Person `@id` → `Person.sameAs` → known profiles. Every link must resolve.

2. **Content/schema parity.** Does `headline` match the H1? Is `dateModified` a real last-modified date or just a copy of `datePublished`? Does `description` match the opening paragraph?

3. **`speakable` opportunity.** If a long-form page has a clear extractable summary (an intro paragraph or TL;DR section), add `SpeakableSpecification` to signal which content AI assistants should prefer.

4. **FAQPage/HowTo opportunity.** These types no longer produce Google rich results (deprecated 2023) but AI systems still extract them for conversational answers. If you have genuine Q&A or step-by-step content, add the markup.

5. **`knowsAbout` and credentials.** Does the Person entity declare topic expertise that matches your content library? In YMYL verticals (health, finance, legal), are credentials present?

---

## Step 6: Check Per-Page Coverage

Required schema by page type:

**Homepage:** `WebSite` (with `@id` and `url`), primary entity (`Person` or `Organization`). `BreadcrumbList` optional.

**About / author page:** `Person` with canonical `@id`, `name`, `sameAs`, `worksFor` (reference Organization `@id`), `jobTitle`, `image` as `ImageObject`. Add `ProfilePage` wrapping the Person.

**Blog list page:** `CollectionPage`, `ItemList` (one `ListItem` per post, enables carousel rich results), `BreadcrumbList`.

**Blog post:** `BlogPosting` with `headline`, `author` (as `@id`), `publisher` (as `@id`), `datePublished`, `dateModified`, `image` (as `ImageObject`), `mainEntityOfPage`, `inLanguage`, `keywords`. Add `BreadcrumbList`. Consider `speakable` if there is a clear extractable intro.

**Service page:** `Service` nested under or referencing the Organization. `BreadcrumbList`.

**Contact page:** `ContactPage`. `BreadcrumbList`.

**Tool / app page:** `SoftwareApplication` with `name`, `description`, `applicationCategory`, `operatingSystem`, `offers`.

**Product page:** `Product` with `Offer` and `AggregateRating` if applicable.

**Video page:** `VideoObject` with `name`, `description`, `thumbnailUrl`, `uploadDate`, `duration`, `contentUrl` or `embedUrl`.

---

## Step 7: Generate a Gap Report

Categorize findings before implementing:

```
CRITICAL (breaks entity graph or rich result eligibility)
  - [page] issue → fix

SIGNIFICANT (reduces AI citation accuracy or rich result quality)
  - [page] issue → fix

MINOR (best-practice gaps, low impact)
  - [page] issue → fix

DEPRECATED (no action needed, just awareness)
  - [type] no longer produces rich results (GEO/AI value only)
```

Implement critical items first. Entity graph fixes (consistent `@id`, correct entity types) propagate across all pages at once.

---

## Step 8: Implement

**Priority order:** Entity graph first (applies site-wide), then per-page gaps.

**By framework:**

Astro: inject schema in `<head>` via a named slot. Define the schema object in frontmatter, inject with `<script type="application/ld+json" set:html={JSON.stringify(obj)} />`. Never use `client:load` for schema. It must be server-rendered.

Next.js: `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(obj) }} />` in the document `<head>`. Prefer RSC or `generateMetadata` for static prerendering. Never inject via `useEffect`.

WordPress: Use Yoast or Rank Math for most schema, or add custom JSON-LD to `wp_head` via `functions.php`. Avoid client-side plugins that inject after load.

Static HTML: Add `<script type="application/ld+json">` directly to `<head>` of each page template.

**Universal rule:** Schema must be present in the server-rendered HTML source. If it only appears after JavaScript runs, it is invisible to crawlers and some AI systems.

---

## Step 9: Validate

Run three checks after implementing:

**Google Rich Results Test** (`search.google.com/test/rich-results`): Paste any blog post URL. Should detect Article (or BlogPosting) and BreadcrumbList. If zero items detected, schema is not rendering server-side or has a syntax error.

**Schema.org Validator** (`validator.schema.org`): Paste your about page URL. Confirm Person has `sameAs` populated, `worksFor` references an Organization by `@id`, and `image` is declared as `ImageObject`.

**Manual chain trace:** Take any blog post. Find the `author @id` value in the source HTML. Navigate to that URL in your browser. Confirm the Person entity is defined there with `sameAs` pointing to your LinkedIn, X, and GitHub profiles. Then follow one of those URLs and verify it resolves to your actual profile. Every link in the chain has to hold. A valid-looking URL that simply does not define the Person entity will pass all validators and still break AI attribution.

---

## Key Rules

- Decide on canonical `@id` IRIs once and never deviate. One mismatch across one page creates a duplicate entity.
- `sameAs` means "this IS the same entity on another platform." `knowsAbout` means "this entity has expertise in these topics." Both serve AI entity authority. They are not interchangeable.
- Never fabricate schema data. `dateModified` hardcoded to `datePublished` is a small lie that degrades freshness signals and can mislead AI citation provenance.
- The entity graph (Person + Organization with `sameAs`) is the highest-leverage work. Chasing per-page rich result types with an incomplete entity graph is building on a broken foundation.

---
