Skip to content
← Back to blog
Tutorial

Schema markup for AI search: the JSON-LD that gets cited

Schema markup for AI search, done right: the JSON-LD for Article, FAQ, HowTo, and Organization, validated and shipped without breaking existing SEO.

By Mitrasish, Co-founderAug 7, 202614 min read
Schema markup for AI search: the JSON-LD that gets cited

Does schema markup for AI search actually work?

Schema markup for AI search does not move AI citations on its own. That question is settled: the research post on this covers what Google says and what the one controlled experiment found, and we are not re-litigating it here. This post assumes that answer and starts where most guides stop, at the actual JSON-LD. Which four schema types are worth your time, how to wire them together with @graph without breaking whatever is already on the page, and how to validate the result for a crawler that can render JavaScript and one that cannot.

If you have not read the research post, the one-line version: schema is hygiene, not a lever. It earns rich results in classic Search, helps Google resolve your brand as an entity, and does not get you quoted by ChatGPT. This is the implementation companion, part of the same answer engine optimization work. Ship it because it is cheap and correct, not because it is a citation trick.

The four schema types worth implementing

Four types cover almost everything a SaaS or dev-tool blog needs: Article, FAQPage, HowTo, and Organization. Each one maps to a real, current use, and none of them is speculative.

Article or BlogPosting: the recommended fields and why dateModified matters

Google's Article documentation recommends headline, image, author, datePublished, and dateModified, in ISO 8601 format. dateModified is the field teams skip, and it is the one that matters most for a blog you actually maintain. It is the machine-readable version of the freshness signal a refreshed post is supposed to carry. Bump it every time you materially edit a page, the same discipline this site applies through sitemap-lastmod-overrides.json rather than the frontmatter publish date.

The author field has a strict rule: "only specify the name of the author. Don't add any other piece of information," per Google's own guidance. No job title, no honorific, no publisher name folded into the string. Those get their own properties, and the full Person markup, worksFor, url, sameAs, is its own topic; see author schema for AI citations for the exact fields.

json
{
  "@type": "Article",
  "@id": "https://www.trylyra.ai/blog/schema-markup-for-ai-search/#article",
  "headline": "Schema markup for AI search: the JSON-LD that gets cited",
  "image": "https://www.trylyra.ai/blog/schema-markup-for-ai-search.webp",
  "datePublished": "2026-08-07",
  "dateModified": "2026-08-07",
  "author": { "@id": "https://www.trylyra.ai/#person-mitrasish" },
  "publisher": { "@id": "https://www.trylyra.ai/#organization" }
}

FAQPage: ship the content, know the rich result is gone

FAQPage is where most 2026 advice is out of date. In August 2023 Google restricted FAQ rich results to a short list of authoritative government and health sites. Then it went further: FAQ rich results stopped appearing in Google Search entirely, for every site, as of May 7, 2026. If your FAQPage markup is valid, nothing renders it into a SERP feature anymore, for anyone.

Ship the markup anyway, for a narrower reason than the rich result. FAQPage is structured proof that the question-and-answer content actually exists on the page, and that Q&A shape is exactly the pattern an AI answer engine extracts, rich result or not. The AirOps analysis of 16,851 ChatGPT queries put FAQPage among the highest-correlating types at a 45.6% citation rate, correlation rather than causation, and the research post breaks down that full study and why correlation is not the same as a lever. What matters for a 2026 implementation checklist is narrower: don't skip a type just because its SERP feature died. The content underneath is the point; the schema is just the label confirming it is there.

HowTo: why this blog's own posts still emit it after Google killed the rich result

Google pulled HowTo rich results from mobile in that same August 2023 change, then finished the job a few weeks later by dropping the desktop version too, on September 13, 2023. No partial exemption the way FAQ briefly had. So why does a step-by-step post on this blog still carry HowTo markup in its frontmatter? Same logic as FAQPage: the JSON-LD is a structured, machine-readable restatement of a numbered sequence that is already on the page as plain text and headings. An AI crawler that cannot render your page still gets the step list as data, not just prose it has to parse out of paragraphs. The rich result was the visible payoff; the payoff that is left is a clean, unambiguous shape for an extractor.

Match it to the page exactly, name and text per step, in the order the steps actually run, or skip it. Mismatched or padded HowTo steps are the kind of markup Google's structured data policies exist to catch.

Organization: the entity fields that make a brand resolvable

Organization is the type on this list with the best return per hour, because you write it once and it covers the whole site. Google's Organization documentation recommends name, logo, url, and sameAs links to your verified profiles, the fields that disambiguate your brand from every other business with a similar name and feed a knowledge panel if you earn one. It is also the field an AI answer engine leans on when it is deciding whether "Lyra" in a query means this company or something else entirely.

json
{
  "@type": "Organization",
  "@id": "https://www.trylyra.ai/#organization",
  "name": "Lyra",
  "url": "https://www.trylyra.ai/",
  "logo": "https://www.trylyra.ai/logo-mark.svg",
  "sameAs": ["https://www.linkedin.com/company/trylyra"]
}

John Mueller, on Google's Search Relations team, put the whole category in perspective: "Structured data won't make your site rank better. It's used for displaying the search features listed in developers.google.com/search/docs... you're unlikely to see any visible change from it in Google Search." Organization is no exception. It will not move your ranking. It is how you tell Google, and any model that walks the same graph, which entity you are.

One type deliberately left off this list: DefinedTerm, built for glossary and dictionary entries. It is narrow enough to earn its own post rather than a paragraph here; see building glossary pages AI engines quote verbatim if that is the page type you are working on. The same is true of Offer and Product schema on a pricing page, covered in pricing page SEO: real types, just outside this checklist's four.

Implementing JSON-LD without breaking your existing SEO schema

The four types above are only half the job. The other half is wiring them into a page that probably already has schema on it, without duplicating data or shipping two conflicting <script> tags that a validator flags and Google ignores.

One script tag per type vs @graph: when to nest entities with @id

The naive approach is one <script type="application/ld+json"> tag per type: one for Article, one for Organization, one for BreadcrumbList. It works, and for a single-type page it is fine. The problem shows up the moment two entities need to reference the same thing, an Article's publisher and that same Person's worksFor both pointing at the same Organization, for instance. Repeat the Organization object in both places and you now have two copies of your name, logo, and sameAs that can drift out of sync the next time someone edits one and not the other.

@graph fixes this. It is a JSON-LD array that holds every entity on the page as a sibling, each with a stable @id, and lets other entities reference that @id instead of nesting a full copy. Google's own structured data documentation demonstrates the underlying nesting pattern directly, showing a MusicVenue's PostalAddress's Country as a legitimately nested chain of entities. @graph and @id referencing are the standard JSON-LD mechanism for expressing that same kind of relationship without repeating data, one script tag, one source of truth per entity:

json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://www.trylyra.ai/#organization",
      "name": "Lyra",
      "url": "https://www.trylyra.ai/",
      "logo": "https://www.trylyra.ai/logo-mark.svg",
      "sameAs": ["https://www.linkedin.com/company/trylyra"]
    },
    {
      "@type": "Person",
      "@id": "https://www.trylyra.ai/#person-mitrasish",
      "name": "Mitrasish",
      "url": "https://www.linkedin.com/in/mmitrasish/"
    },
    {
      "@type": "Article",
      "@id": "https://www.trylyra.ai/blog/schema-markup-for-ai-search/#article",
      "headline": "Schema markup for AI search: the JSON-LD that gets cited",
      "author": { "@id": "https://www.trylyra.ai/#person-mitrasish" },
      "publisher": { "@id": "https://www.trylyra.ai/#organization" },
      "datePublished": "2026-08-07",
      "dateModified": "2026-08-07"
    }
  ]
}

If your framework already emits its own JSON-LD, for a theme, a plugin, or a previous engineer's one-off script, do not just add a second, separate <script> tag with an overlapping Organization or Article node. Two Article entities describing the same URL is exactly the kind of inconsistency the policies flag. Find the existing block and fold your new entities into its @graph, or replace it outright if it is stale.

The rendering trap: JSON-LD injected client-side is invisible to AI crawlers

This is the mistake that quietly costs teams the most. If your JSON-LD is written into the page by client-side JavaScript after the React or Vue app hydrates, a crawler that does not execute JavaScript never sees it, full stop. Vercel's analysis of its network traffic found GPTBot fetches JavaScript files on about 11.5% of its requests and ClaudeBot on about 23.84%, but "they don't execute them. They can't read client-side rendered content." Fetching the file and running it are different things, and these crawlers only do the first. Across the sample analyzed, GPTBot logged 569 million fetches and Claude 370 million, against 4.5 billion for Googlebot, which does render JavaScript. The volume gap alone is a reason not to assume Google's behavior generalizes to every crawler reading your site.

The fix is the same one that shows up everywhere in a git-based, statically generated blog: emit the <script> tag in the server-rendered HTML response, not in a useEffect or a client-only component. A crawler that fetches the URL and stops there, which is what most AI crawlers do, needs the markup to already be in that first response. This is also why we treat JavaScript rendering as a separate audit from schema correctness: markup that validates perfectly in a browser can still be a blank page to the bot that matters most for AI citation.

Escaping JSON-LD safely when you inject it with a templating layer

If your JSON-LD is built by hand, string-concatenating a title or an excerpt pulled from a CMS into a template literal, you have a second, quieter bug waiting: a closing </script> sequence inside user-authored content can terminate your script tag early and break the page. The reliable fix is to never hand-build the JSON string. Build a plain object, then serialize it with your language's JSON serializer, and escape the forward slash in any literal </script> the serializer would otherwise pass through untouched. In JavaScript, that is JSON.stringify(data).replace(/</g, "\\u003c") before you write it into the template, which neutralizes both <script> and <!-- injection without touching any of the actual data. Treat this the same way you would treat any other untrusted string reaching an HTML response: escape at the boundary, not by hoping the input is clean.

Validating schema for both Google and AI crawlers

A schema block that looks right is not the same as a schema block Google parsed or an AI crawler received. Three checks answer three different questions, and none of them substitutes for the others.

Rich Results Test vs the Schema.org validator: what each one actually checks

The Schema.org validator checks whether your markup is structurally correct against the spec: right types, right property names, right nesting. It does not know or care what Google does with any of it. Google's Rich Results Test checks a narrower, different question: whether this specific page qualifies for a specific Google rich result, using Google's own rules on top of the spec. A block can pass the Schema.org validator and still fail Rich Results Test, if it is spec-valid but missing a property Google requires for eligibility, and the reverse rarely happens. Run both. They are not redundant; they catch different classes of mistakes.

Reading the Search Console structured data reports without guessing

Google Search Console's structured data reports aggregate errors and warnings across your whole site, not one URL, so they are where you catch a template bug that shipped on every post at once instead of finding it one page at a time. Read the error type first, not just the count: a spike in "missing field" errors after a deploy usually means a template change dropped a property, while a slow, steady trickle of warnings on old URLs is more often stale data worth a batch fix rather than an emergency.

Confirming an AI crawler actually received the markup

None of the tools above tell you whether GPTBot or ClaudeBot actually got your JSON-LD, because none of them are those crawlers. The direct check is to curl the live URL with the crawler's own user agent and read the raw HTML back, the same way you would check for it with your eyes:

bash
curl -A "GPTBot" https://www.trylyra.ai/blog/schema-markup-for-ai-search/ | grep -A2 "application/ld+json"

If the <script type="application/ld+json"> block shows up in that output, it arrived in the first response, before any JavaScript ran, which is what a non-rendering crawler actually gets. If it is missing, your markup is client-side and invisible to that bot regardless of what the Rich Results Test says, because that tool renders the page the way Chrome does. For a fuller picture over time rather than a single URL, reading AI crawler activity straight from server logs shows whether GPTBot and ClaudeBot are hitting the page at all, which is the precondition for any of this markup being read in the first place. And if you have not separately audited which crawlers you are even letting in, robots.txt for AI bots is the companion check: perfect JSON-LD behind a Disallow line never gets fetched to begin with.

The schema markup for AI search checklist: what dev-tool and SaaS blogs should ship

Run this on every post before it ships, and sitewide whenever the template changes.

CheckWhat it confirms
Article/BlogPosting has headline, image, author, datePublished, dateModifiedGoogle's recommended properties are present and dated
author.name holds only a name, no title or honorificMatches Google's Article field guidance exactly
FAQPage and HowTo markup match the visible Q&A and steps on the pageNo mismatch that could trigger a manual action
Organization is emitted sitewide with name, logo, url, sameAsThe brand resolves as one entity, not a name string
Multiple entities share @id references inside one @graph, not duplicatedNo two conflicting copies of the same Organization or Article
JSON-LD appears in the server-rendered HTML, not injected after hydrationNon-rendering AI crawlers can actually see it
Any CMS-sourced string is passed through a JSON serializer, not concatenatedNo injection risk from a stray </script> in a title
Markup passes the Schema.org validator and the Rich Results TestSpec-valid and Google-eligible, checked separately
Search Console shows no new structured-data errors after deployThe live site matches what the tools showed locally
A curl with an AI crawler's user agent returns the JSON-LD in the raw HTMLThe crawler that matters for AI citation actually receives it

Repeating this by hand on every post is exactly the kind of checklist that decays once a team gets busy, which is why it belongs in CI rather than memory. If your pipeline runs pull requests, gating them on broken links and invalid JSON-LD turns this table into an automated check instead of a habit someone eventually skips.

Schema is hygiene, and hygiene still has to be correct: valid, matched to visible content, server-rendered, and checked in more than one tool. Lyra ships every post with clean, policy-compliant JSON-LD, server-rendered so both Googlebot and the AI crawlers that never run JavaScript see the same page. She opens it as a pull request you review, so nothing auto-publishes and the markup is one more thing you can check in the diff before it ships. If you want to see what that looks like on your own blog, the plans start with a free tier, no card required.

Getting the JSON-LD right on one post is easy. Doing it correctly on every post, forever, is the part that decays. Lyra ships policy-compliant, server-rendered structured data on every draft, as a pull request you merge.

Try Lyra → · Talk to the founder

Step by step

The short version

  1. 01

    Pick the four schema types and skip the rest

    Ship Article (or BlogPosting), FAQPage, HowTo where it applies, and Organization sitewide. Skip invented 'AI schema' types; they aren't part of the schema.org vocabulary or Google's spec.

  2. 02

    Combine them in one @graph block

    Write a single JSON-LD script tag per page with an @graph array. Give the Organization and Person nodes stable @id values so the Article's publisher and author fields can reference them instead of repeating the same data.

  3. 03

    Render it server-side, not client-side

    Emit the script tag in the initial HTML response your framework returns, not in markup injected after hydration. Non-rendering crawlers only see what arrives in that first response.

  4. 04

    Escape it before you interpolate anything

    If a templating layer builds the JSON-LD string from CMS content, escape '</script>' sequences and run the object through JSON.stringify rather than hand-building a string, or a stray closing tag in a title can break the page.

  5. 05

    Validate in three places, not one

    Run the Schema.org validator for spec correctness, the Rich Results Test for Google eligibility, and URL Inspection in Search Console to confirm Google actually parsed the live page. Then curl the URL with an AI crawler's user agent to confirm the markup exists before any JavaScript runs.

FAQ

Frequently asked

What is the best JSON-LD schema for AI search?+

There is no AI-specific schema type. The four worth shipping are Article (or BlogPosting) with a real dateModified, FAQPage for the content shape even though the rich result is gone, HowTo for the same reason, and Organization to make your brand a resolvable entity. Combine them under one @graph so they reference each other by @id instead of duplicating data.

Does @graph in JSON-LD improve AI citations?+

No. @graph is a way to link multiple schema entities on one page by @id instead of nesting duplicate copies of the same data. It keeps your markup smaller and internally consistent, which matters for maintainability and avoiding validator errors, but Google's own research and Ahrefs' controlled test found no meaningful citation lift from schema itself, structural choice included.

Can AI crawlers like GPTBot read JSON-LD injected by JavaScript?+

No. GPTBot and ClaudeBot fetch JavaScript files but do not execute them, so any JSON-LD written into the DOM after hydration is invisible to them. Vercel's network data shows GPTBot fetches JS on about 11.5% of requests and ClaudeBot on about 23.84%, with zero evidence either one runs it. Emit JSON-LD in the server-rendered HTML response, not client-side.

How do I check if Google actually received my structured data?+

Run URL Inspection in Google Search Console on the live page. A test tool like the Rich Results Test or the Schema.org validator only confirms the markup is valid; URL Inspection confirms Google's own crawler fetched and parsed that exact markup on that exact URL, which is a different and more important question.

Built by the tool you're reading about

This post is the kind of thing Lyra ships on her own.

Lyra finds the topics worth ranking for, writes them in your repo's voice, fact-checks every claim, and opens a pull request scored and ready to merge. You review and hit merge. Want to see what she'd write for you? Start free with three posts, no card.

JSON-LD Schema MarkupStructured Data for AI CitationsArticle FAQPage HowTo SchemaStructured Data Checklist SaaSOrganization Schema Markup