Connected schema: the JSON-LD an assistant can actually parse
Structured data does two jobs: it names the entity behind a page, and it states what the page is about in a form a machine can read without guessing. When it is missing, an assistant has to infer both — and inference is where citations get lost.
The failure we see most often is not missing markup but disconnected markup: a WebSite
block on every page regardless of content, Organization data with no sameAs, and an
Article with no author, no datePublished and a headline that does not match the visible
heading.
Start with the entity, then the page
- One
Organization(orPerson) block for the site —name,url,logo, andsameAslinks to the profiles that prove it is the same entity elsewhere. - One block per page that states what the page is —
Article,FAQPage,Product,HowTo, orWebPage. Use the type that matches the page, not the type with the most fields. - Connect the two — the page's block should reference the site entity with
publisherorisPartOf, so the model does not read them as unrelated fragments.
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Connected schema: the JSON-LD an assistant can actually parse",
"datePublished": "2026-09-28",
"author": { "@type": "Organization", "name": "Aeolith", "url": "https://aeolith.com" },
"isPartOf": { "@type": "WebSite", "name": "Aeolith", "url": "https://aeolith.com" }
}
The four mistakes that make it unusable
- The block is injected by JavaScript. A crawler that does not execute scripts never sees it. Structured data must be in the HTML source (check A2, text in raw HTML).
headlinedoes not match the<h1>. Two different truths about the same page.- Required properties are missing for the type you chose — an
Articlewith no date, aProductwith nooffers. FAQPagemarkup disagrees with the visible questions. This one is directly detectable, and it is why our own FAQ list and its markup are generated from a single source.
Verify what you shipped
curl -sS https://example.com/page | \
python3 -c "import sys,re,json;[json.loads(b) for b in re.findall(r'<script type=\"application/ld\+json\">(.*?)</script>', sys.stdin.read(), re.S)]; print('JSON-LD parses')"
If that raises, the block is not JSON — usually a trailing comma or an unescaped quote from a template. A block that does not parse is worse than no block: it is invisible to the machine and looks like work that was done.
What the report shows
Check B2 (structured data) records the JSON-LD types actually parsed, and check B1 records the visible title and description, so you can see whether the machine-readable statement and the page agree. When they disagree, the report says which one it used.