/ โ† JSON-LD Fixes / Dangling References
In this guide
  1. How a reference works
  2. How references end up dangling
  3. Listing every dangling reference in a minute
  4. A reference must land on the right kind of thing
  5. A broken graph and its repair
  6. The fix, step by step
  7. References to entities on other pages
  8. Keeping the graph closed
  9. Questions and answers

How a reference works

JSON-LD gives you three ways to fill a property such as author. They look similar but mean quite different things:

FormExampleWhat a machine learns
Plain text"author": "Tom Reid"A string. No type, no link to anything else.
Nested object"author": {"@type": "Person", "name": "Tom Reid"}A typed entity, described right there. It cannot be reused elsewhere unless it also has an @id.
Reference"author": {"@id": "https://www.example.co.uk/team/tom-reid/#person"}Nothing on its own. It means "the entity defined elsewhere with this identifier".

A reference is an object containing only @id. It is the most economical form when an entity is used several times, because the full description is written once and pointed to from every property that needs it. The catch is that a reference is only as good as the definition it points to. If there is no node with that identifier, the property is effectively empty.

Checking that every reference has a matching definition is what the hub calls the closed-graph rule. The JSON Checker enforces it for each page, so a dangling reference shows up as a finding even though the JSON parses and the types are valid.

How references end up dangling

Dangling references are almost never written on purpose. They appear when one part of the markup changes and another part does not:

Where identifiers themselves are inconsistent or duplicated across the site, fixing them properly is covered in the guide on duplicate or missing @id. This guide concentrates on the pointer side: making each reference land on something.

Listing every dangling reference in a minute

On a page with a large graph it is hard to see broken references by eye. The JSON Checker reports them, and if you want to check a page you are working on, this script lists them directly. Paste it into the browser console (F12, then the Console tab) on the live page:

const defined = new Set();
const referenced = new Set();
function walk(value) {
  if (Array.isArray(value)) { value.forEach(walk); return; }
  if (value && typeof value === 'object') {
    const keys = Object.keys(value);
    if (typeof value['@id'] === 'string') {
      (keys.length === 1 ? referenced : defined).add(value['@id']);
    }
    keys.forEach(k => { if (k !== '@id') walk(value[k]); });
  }
}
document.querySelectorAll('script[type="application/ld+json"]').forEach(s => {
  try { walk(JSON.parse(s.textContent)); }
  catch (e) { console.warn('Block does not parse:', s); }
});
console.log('Dangling:', [...referenced].filter(id => !defined.has(id)));

It treats any object with more properties than @id as a definition and any object with only @id as a reference, then prints the references with no matching definition. It compares identifiers exactly as written, so a relative fragment and its absolute equivalent are reported as different. That is deliberate, because a mismatch of that kind is worth investigating anyway.

๐Ÿ’ก Run it with and without being logged in, and after any cookie banner has been accepted. Some plugins output different markup to logged-in users, and some blocks are only added by script after consent.

A reference must land on the right kind of thing

A reference can resolve and still be wrong. If author points to your WebSite node, the graph is closed but it now says a website wrote the article. Each property expects particular types, listed on its schema.org page. These are the reference properties the checker sees most, with the targets that make sense:

PropertySensible targetMistake to look for
authorPerson, or Organization for corporate contentPointing at the WebSite or WebPage
publisherOrganization or PersonPointing at the WebSite
isPartOfThe containing work, usually the WebSite for a WebPagePointing at the Organization
mainEntityOfPageThe WebPage that carries the entityPointing at the article itself
about, mainEntityThe thing the page is aboutPointing at the page
itemReviewedThe product, business or work under reviewPointing at the review or the reviewer
brandBrand or OrganizationPointing at the seller when it is not the brand
parentOrganization, worksForOrganizationPointing at a branch's own node
locationPlace or a LocalBusinessPointing at the event

These errors usually come from copying a reference line and forgetting to change the identifier. A quick read of each reference alongside the type of its target catches them.

A broken graph and its repair

This review page has three problems. The review points to a product identifier that nothing defines, the author reference uses the bare domain while the Person uses www, and the publisher points to the WebSite rather than the organisation.

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://www.example.co.uk/#website",
      "url": "https://www.example.co.uk/",
      "name": "Fernhill Cycle Reviews"
    },
    {
      "@type": "Person",
      "@id": "https://www.example.co.uk/about/dan-okoro/#person",
      "name": "Dan Okoro"
    },
    {
      "@type": "Review",
      "itemReviewed": { "@id": "https://www.example.co.uk/reviews/trail-lamp-400/#product" },
      "author": { "@id": "https://example.co.uk/about/dan-okoro/#person" },
      "publisher": { "@id": "https://www.example.co.uk/#website" },
      "reviewRating": { "@type": "Rating", "ratingValue": 4, "bestRating": 5 }
    }
  ]
}

The repaired graph defines the product and the organisation, corrects the host in the author reference, and points the publisher at the Organization:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://www.example.co.uk/#organization",
      "name": "Fernhill Cycle Reviews",
      "url": "https://www.example.co.uk/"
    },
    {
      "@type": "WebSite",
      "@id": "https://www.example.co.uk/#website",
      "url": "https://www.example.co.uk/",
      "name": "Fernhill Cycle Reviews",
      "publisher": { "@id": "https://www.example.co.uk/#organization" }
    },
    {
      "@type": "Person",
      "@id": "https://www.example.co.uk/about/dan-okoro/#person",
      "name": "Dan Okoro",
      "url": "https://www.example.co.uk/about/dan-okoro/"
    },
    {
      "@type": "Product",
      "@id": "https://www.example.co.uk/reviews/trail-lamp-400/#product",
      "name": "Trail Lamp 400 bike light",
      "brand": { "@type": "Brand", "name": "Brightridge" }
    },
    {
      "@type": "Review",
      "@id": "https://www.example.co.uk/reviews/trail-lamp-400/#review",
      "itemReviewed": { "@id": "https://www.example.co.uk/reviews/trail-lamp-400/#product" },
      "author": { "@id": "https://www.example.co.uk/about/dan-okoro/#person" },
      "publisher": { "@id": "https://www.example.co.uk/#organization" },
      "reviewRating": { "@type": "Rating", "ratingValue": 4, "bestRating": 5 }
    }
  ]
}
</script>

Run the console script on the repaired version and the dangling list comes back empty. Every pointer now lands on a defined node of a type that fits the property.

The fix, step by step

Step 1

Extract the definitions and the references

Run the JSON Checker on the page, or paste the console script from this guide into developer tools on the live page. Either way you want two lists: identifiers that are defined by a node with its own properties, and identifiers that are only pointed to by an object containing nothing but @id. Include every JSON-LD block, whichever tool wrote it, and test one page from each template because each can behave differently.

Step 2

Match every reference to a definition

Go down the list of references and find the exact definition for each one, character for character. Mark each reference as resolved, missing entirely or close but different. A close match, such as one with or without www, a trailing slash, http instead of https, or a relative fragment instead of a full address, tells you the target exists but one side is using the wrong identifier, which is a much smaller fix than adding a node.

Step 3

Add the target entities that are missing

For each reference with no definition, add a node with that identifier, the right @type and at least the properties that make it useful, usually name and url, and a logo for an organisation. Put it in the same @graph as the reference where you can. If the entity is only defined on the home page, output a short definition on every page that refers to it rather than relying on the home page copy.

Step 4

Correct identifiers that almost match

Where a reference and a definition differ only in host, protocol, slash or relative form, change the reference to use the definition's exact identifier, unless the definition is the one that breaks your site-wide naming scheme. Fix it in the template or setting that builds the value, not on one page. If a template builds identifiers from an author or page address, add a guard so an empty value does not produce a bare fragment.

Step 5

Check each target is a sensible type

With every reference resolving, read each one alongside the type of the node it lands on. An author should resolve to a Person or Organization, a publisher to an Organization, isPartOf on a WebPage to the WebSite, and itemReviewed to the thing reviewed. A reference that lands on the wrong kind of entity is usually a copied line with an unchanged identifier, and it is just as misleading as one that lands nowhere.

Step 6

Retest every template after the change

Clear page and plugin caches, then run the JSON Checker and the console script again on a page from every template, logged in and logged out. The dangling list should be empty on all of them. Also paste one page into the Schema Markup Validator and open each reference property to check it now shows the target's details. Repeat the check after plugin updates, which can change identifier formats on one side only.

References to entities on other pages

Because an identifier is global, a page can in principle refer to an entity that is only described on another page, and some sites rely on this: the organisation is defined in full on the home page and every other page just points to it. In linked-data terms that is legitimate. In practice it is fragile, for three reasons:

The robust approach is to include at least a short definition on every page that refers to an entity: identifier, type, name and address. It costs a few lines and removes the dependency. Most SEO plugins already do this for the organisation and website on every page, which is one reason their output rarely has dangling references until custom code is added.

โš ๏ธ Do not solve a dangling reference by deleting the reference and leaving the property out, unless the property genuinely does not apply. An article with no author information at all is worse off than one whose author reference has been repaired.

Keeping the graph closed

For the full set of JSON-LD findings see JSON-LD Fixes. If you use AI-generated markup, the separate guide on authors and publishers in generated schema covers the references those tools tend to lose.

Questions and answers

1. What exactly is a dangling reference?

It is a property in your JSON-LD whose value is an object containing only an @id, where no entity on the page has that identifier. For example, an Article might have an author pointing to a Person identifier while the Person block is missing or uses a slightly different address. The JSON is valid and the types may be correct, but the property carries no usable information because the pointer leads nowhere. Fixing it means adding or correcting the target entity.

2. Why do Google tools not warn me about dangling references?

The Rich Results Test concentrates on whether a page qualifies for particular rich results. A missing author or publisher target often does not block eligibility, so it is not reported as an error, and the property may simply show as empty or be left out of the view. That makes dangling references easy to miss. Tools that check the whole graph, such as the JSON Checker, report them because they test whether each reference resolves.

3. Is it fine to reference an Organization defined only on my home page?

In linked-data terms it is allowed, because identifiers are global. In practice it is unreliable: validators assess each page alone, search engines generally expect a page's structured data to describe that page, and many tools only ever fetch one page. Output at least a short definition of the organisation, with its identifier, type, name and address, on every page that refers to it. Most SEO plugins already do this, so check whether yours covers it.

4. Should I use references or just nest the full entity each time?

Both are valid. Nesting the full entity is self-contained and never dangles, but repeating a large Organization or Person on many properties makes the markup long and easy to let drift out of step. References keep one definition and point to it, which is tidier and makes the connections explicit, at the cost of needing the definition to be present. A common pattern is references within one @graph, with every target defined in that graph.

5. Can a reference point to an entity in a different script block on the same page?

It can, and many tools will resolve it, because both blocks are on the same page and use the same identifier. Not every processor combines separate blocks in the same way, though, so there is a small risk the link is missed. Where you control both pieces of markup, putting the reference and its target in one @graph is more predictable. Where one block comes from a plugin, matching its identifier exactly is the best you can do.

6. My reference and definition look identical. Why is it still dangling?

Compare them character by character. The usual differences are www against the bare domain, http against https, a trailing slash, different capitalisation in the path, or a relative fragment such as #person against a full address. Invisible characters pasted from a document, such as a non-breaking space, can also cause it. Copy one identifier and search the page source for it exactly; if the search does not find the other, they are not the same string.

7. Does fixing dangling references help with rich results?

Sometimes directly and always indirectly. Where a rich result depends on the referenced entity, such as a review needing the item it reviews, a dangling reference can stop the feature working. More often it weakens the information around the result, such as who wrote an article or which organisation published it. Google has said structured data is not a general ranking factor, so the benefit is clearer understanding and eligibility rather than a ranking boost.

Check that every reference resolves

The JSON Checker parses every JSON-LD block on your page, validates the syntax, checks each type against schema.org, verifies required fields, detects conflicting types and tests the result against Google rich result criteria.

Open JSON-LD Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All JSON-LD Fixes  ·  Fixing Duplicate or Missing @id  ·  Fixing Invalid schema.org Types  ·  JSON-LD Schema Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.