squirrelscan
GitHub

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.

Why relative identifiers do not work

"@id": "#organization" looks like it says “the organization”. It says “the organization of this page”. Resolved against the home page it means https://example.com/#organization; resolved against an article it means https://example.com/post/#organization. Those are two identifiers.

This is worse than having no @id at all, because it looks solved. A validator accepts it, a developer ticks it off, and the entity still fails to accumulate across the site for the same reason it did before.

The fix is to write the URL out in full.

What squirrel checks

The rule runs once per crawl, reads the entity map rather than the pages, and emits a single check named entity-id-format.

  • Skipped with the message No entity map was built for this audit and the reason entity-map-unavailable when the run produced no map. This is not the same as a site with no structured data, and the rule refuses to conflate them: passing here would assert it had checked something it never looked at.
  • Reports at info with the message This site declares no JSON-LD entities on any crawled page when the map exists and is empty.
  • Passes with the message Every @id is an absolute URL.
  • Warns with the message N @ids are not an absolute URL, plus (+N more entities) when more matched than are listed. The value names the widest offender as NAME: ID and the expectation is an absolute https:// URL.

An @id counts as stable when it parses as a URL with an http or https scheme and a host, or as a global identifier scheme: urn:, doi:, info: or tag:. A library’s urn:isbn:9780140328721 is unique, stable and the same on every page, so warning about it would be advice to break it. A mailto: identifier is reported, because an email address is a way of contacting a thing rather than a name for it.

Where the audit can resolve a relative @id against its page it does so, which is what makes the per-page split visible to the other entity rules rather than to this one. What reaches this check is an @id that could not be resolved at all.

Findings are capped at 10 items, and the count in the message is always the true total rather than the capped length. Run squirrel entities -f json and read the entities key for the full list.

How to fix it

Write the full URL:

{ "@id": "https://example.com/#organization" }

Not #organization and not /#organization. A protocol-relative //example.com/#organization does resolve consistently within one scheme, but it changes meaning if the site is ever served over both, so write the scheme out.

If the markup is generated, the template is probably concatenating a fragment without the site URL in front of it. Fix it there rather than page by page. See fixing structured data identity.

Rule IDschema/entity-id-format
CategoryStructured Data
ScopeSite-wide
Severitywarning
Weight4/10

Enable / disable

Disable this rule

[rules]
disable = ["schema/entity-id-format"]

Disable all Structured Data rules

[rules]
disable = ["schema/*"]

Enable only this rule

[rules]
enable = ["schema/entity-id-format"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Entity findings are site-wide and ship in every audit, next to the per-page structured data rules. See Fix SEO issues with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com, then squirrel entities to see the graph the finding came from. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close