“Add schema markup” is on every AEO checklist, and most teams who follow the advice get nothing for it. Not because structured data doesn't matter, it does, but because the schema most sites ship is the empty, CMS-default kind: an Article type with no author entity, an Organization with a name and nothing else. A model learns nothing from it, so it changes nothing.
This is the technical guide to the version that works: attribute-rich markup, a consistent entity graph, and a site AI crawlers can actually read. It is the “Build” stage of The Citation Engine, written out in full.
Why markup matters differently for models
Google uses structured data for rich results. A language model uses structure for something more basic: confidence. Before an engine cites you, it has to resolve three questions, can I parse this page, do I understand what this entity is, and will I look wrong repeating this claim? Markup and entity work answer the first two. Nothing else on your site does it as directly.
The evidence points one specific direction: specificity wins. Marshal's 730-citation study of ChatGPT and Gemini citations found generic CMS-default schema produced no measurable citation effect, while attribute-rich schema, with populated pricing, ratings and specification fields, outperformed it by roughly 20 percentage points (61.7% vs 41.6%). One study, so hold it loosely, but it matches what we see in client data: empty types do nothing; populated attributes move.
The schema hierarchy: what to ship, in order
| Priority | Markup | The attributes that matter | Why |
|---|---|---|---|
| 1 | Organization (sitewide, with @id) | description, founder, sameAs[], knowsAbout, areaServed | Your entity's anchor. Every other node should reference it. |
| 2 | Product / Service + Offer | real pricing, features, audience, ratings where true | The populated attributes are the 20-point gap. Empty types are theater. |
| 3 | FAQPage (on real Q&A) | questions phrased as buyers ask them, self-contained answers | Pre-chunked Q→A pairs, the easiest thing for a model to lift. |
| 4 | Article / BlogPosting | author as a Person entity with @id, dates, publisher | Attribution and freshness, two citation risk-checks, answered in markup. |
| 5 | Person (founder/authors) | jobTitle, worksFor, knowsAbout, sameAs to real profiles | E-E-A-T has to resolve to a real, corroborated human. |
| 6 | HowTo / BreadcrumbList | real steps; clean hierarchy | Structure extras: helpful, not decisive. Ship after 1–5. |
Two rules across all of it. Never mark up what isn't visibly true on the page, models and Google both treat mismatches as a trust problem. And connect everything with @id references so your Organization, Person and Service nodes form one graph instead of disconnected islands. Here is the anchor node, the one to ship first, as copy-paste JSON-LD:
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://yourdomain.com/#organization",
"name": "YourBrand",
"url": "https://yourdomain.com",
"description": "One canonical sentence: what you are and what you are best at.",
"founder": { "@type": "Person", "@id": "https://yourdomain.com/#founder-name" },
"sameAs": [
"https://www.linkedin.com/company/yourbrand/",
"https://www.g2.com/products/yourbrand/reviews",
"https://www.crunchbase.com/organization/yourbrand",
"https://github.com/yourbrand"
],
"knowsAbout": ["your category", "the job you solve", "adjacent expertise"]
}Every other node on the site, Person, Service, BlogPosting, then points back at that @id as provider, worksFor or publisher. One graph, one entity, no ambiguity.
- Paste the JSON-LD above into your site's global layout or head template inside a script tag with type application/ld+json, so it renders on every page. In WordPress, a schema plugin or your theme's header works; in Next.js, the root layout.
- Fill every field with real values before shipping. An empty sameAs array or a placeholder description is exactly the generic markup that measured as doing nothing.
- Validate at validator.schema.org (parsing) and search.google.com/test/rich-results (Google eligibility). Fix errors, ignore warnings about optional fields you chose to omit.
- Then verify it survives your stack: fetch the live page with curl or view-source and confirm the JSON-LD is in the raw HTML, not injected by JavaScript after load.
validator.schema.org parses your Organization node with zero errors, the same @id appears in every other node's publisher or provider field, and the markup is visible in raw page source.
Entity clarity: the layer schema alone can't fix
A model doesn't rank your page, it recalls your entity: a structured sense of who you are, what you do, and what you are best at. That sense is assembled from everywhere your brand appears. If your homepage says “revenue intelligence platform,” your G2 profile says “sales analytics tool,” and your LinkedIn says “AI copilot for GTM teams,” the model's picture of you is fuzzy, and fuzzy entities lose to clear ones, because recommending a thing you half-understand is exactly the risk a model avoids.
The work is unglamorous and decisive: write one canonical description of what you are and what you are best at, then propagate it, verbatim where possible, to every surface: site, schema, G2, Crunchbase, LinkedIn, GitHub, directories. Add sameAs links from your Organization markup to each profile so the graph is explicit. Where it's warranted, establish the entity in public knowledge bases (Wikidata and the profiles models actually retrieve). This is also why Ahrefs' 75,000-brand study found brand web mentions correlate with AI Overview visibility at 0.664 versus 0.218 for backlinks, corroboration across independent surfaces is the signal, and your entity graph is how a model connects those mentions to you.
Crawlability: none of it matters if the bot can't read you
Before any of the above pays off, AI crawlers have to reach and parse your pages. The failure modes we find on real audits, in order of frequency:
- Silent blocks. A WAF, CDN bot rule or robots directive blocking GPTBot, PerplexityBot, ClaudeBot or Google-Extended, often left over from a blanket “block bots” decision nobody revisited. Check your logs for verified hits from each.
- JavaScript-only content. Most AI crawlers read raw HTML and execute little or no JS. If your body copy, pricing or docs render client-side, the model gets an empty shell. Server-render or pre-render anything you want quoted.
- Unchunkable pages. Walls of text with no heading hierarchy give retrieval systems nothing clean to lift. Direct answers near the top, self-contained sections, real headings. (How to write those passages is its own discipline, covered in content AI will quote.)
- Stale facts. Old pricing and abandoned positioning don't just mislead buyers, they get repeated by models and are expensive to correct. Freshness is a citation signal.
And one thing not to spend a sprint on: llms.txt. Google's John Mueller put it plainly: “no AI system currently uses llms.txt”, and Gary Illyes has confirmed Google won't crawl it. Harmless to have, but it is a checkbox, not a strategy; crawl access, rendering and structure are what move.
- robots.txt: open yourdomain.com/robots.txt and search for GPTBot, PerplexityBot, ClaudeBot and Google-Extended. No mention means allowed; a Disallow under any of them means blocked.
- WAF and CDN: on Cloudflare, Security > Bots, check whether “Block AI bots” is on, it ships enabled on some plans. On other CDNs, search the firewall rules for user-agent conditions.
- Logs: grep 30 days of access logs for the four user agents. Real GPTBot resolves by reverse-DNS to an openai.com host; spoofed ones don't, so verify before celebrating or panicking.
- Render: pick your three most important pages, hit Ctrl+U, and search for the main claim, the price and the H1 text in the raw HTML. Then repeat with JavaScript disabled in DevTools.
All four AI crawlers are allowed in robots.txt and your firewall, verified bot hits appear in your logs, and your money pages show their claims and pricing in raw HTML with JavaScript off.
- WATCH OUT
The most expensive version of this failure is the silent one: a blanket "block bots" firewall rule from two years ago that nobody remembers. It produces zero errors, zero alerts, and zero AI citations, forever, until someone checks.
- NO BUDGET?
Every check above is free: view-source, robots.txt, DevTools and grep. The paid crawlers add scheduling and scale, not capability, so run the manual pass first and buy tooling only when the site is too big to spot-check.
The 30-day implementation order
- Verify crawl access for the four major AI bots in your logs; fix silent blocks first, everything else is downstream.
- Ship the Organization + Person graph sitewide with @id and sameAs to every real profile.
- Write the canonical entity description and propagate it to site, schema and every third-party profile.
- Add attribute-rich Product/Service + Offer markup to money pages, with real, current pricing and features.
- Add FAQPage to pages with real questions, phrased the way buyers actually ask.
- Fix render: server-render anything you want quoted; confirm with a raw-HTML fetch, not a browser.
This is exactly the supply-side audit we run, every page scored for citation-readiness with a RICE-ranked fix list, in our AEO engagements. If you just want to know where you stand, the free checker takes two minutes.
