What is JSON-LD? Syntax, @id and common errors
JSON-LD is the JSON format for linked data that most sites use for structured data. The syntax, why every @id should be an absolute URL, and what to check.
JSON-LD (JSON for Linking Data) is a W3C format for writing linked data as JSON, and the format Google recommends for structured data, placed in a <script type="application/ld+json"> block.
A block names its vocabulary with @context, usually https://schema.org, and what each node describes with @type. A node can carry an @id, an identifier other nodes point at, and a @graph array holds several nodes in one block. Because the block sits beside the page content rather than inside it, nothing on the rendered page changes when it is wrong.
Category: Structured Data
Why it matters
Two faults show up. A syntax error, usually a trailing comma, an unescaped quote or a raw newline inside a string, makes the whole block unreadable. A validation error means the JSON parsed but a type is missing a property a consumer expects. Identifiers fail more quietly: "@id": "#organization" resolves against each page’s own URL, so the home page and an article declare two different organizations, and the entity never builds up across the site.
How squirrelscan checks it
schema/json-ld-valid parses each block and reports syntax errors and missing or invalid properties, with the property path for each. Nodes that share an @id on the same page are checked together, which is how generators such as Yoast link their nodes. Two rules read the entity map built from the whole crawl: schema/entity-id-format warns about an @id that is still not an absolute URL after the audit tries to resolve it against its page (urn:, doi:, info:, tag: and isbn: identifiers are accepted), and schema/entity-dangling fails a reference to an @id that no crawled page declares. sameAs links are exempt, because they point outside the site by design.
| Rule | What it flags | Severity |
|---|---|---|
| Invalid JSON-LD syntax: what breaks it and how to fix | A structured data block that will not parse, or parses but omits properties schema.org requires. squirrel names the exact type and property. | warning |
| A bare #organization is one entity per page, not one entity | A relative @id resolves against the page it sits on, so the same fragment on forty pages is forty identifiers. The markup validates and the entity still never accumulates. | warning |
| A publisher that points at nothing | A reference to an @id that nothing in the crawl declares. squirrel checks against the whole site, so a publisher the homepage declares still resolves. | error |
How to fix it
Write every @id as a full URL, and declare each node you reference on every page that references it (see the dangling reference fix):
{
"@context": "https://schema.org",
"@graph": [
{ "@type": "Article", "@id": "https://example.com/post#article",
"publisher": { "@id": "https://example.com/#organization" } },
{ "@type": "Organization", "@id": "https://example.com/#organization",
"name": "Example Ltd", "url": "https://example.com/" }
]
}Serialise the block from an object with JSON.stringify, and fix a relative @id in the template that builds it rather than page by page.
Run the check
squirrel audit https://example.com --rule-include schema/json-ld-valid,schema/entity-id-format,schema/entity-danglingA signed-out local audit costs nothing.
Related terms
- Structured data: Machine-readable statements about a page’s content, usually schema.org vocabulary in JSON-LD, that search engines and agents can parse.
- @id (JSON-LD node identifier): The JSON-LD @id that gives a node a stable IRI, so other nodes and pages can reference the same entity instead of redeclaring it.