/ โ† JSON-LD Fixes / @id Problems
In this guide
  1. What @id actually does
  2. The @id problems, and what each one causes
  3. The relative identifier trap
  4. A naming scheme that holds up
  5. The fix, step by step
  6. When two blocks share an @id on purpose
  7. Identifiers on common platforms
  8. Questions and answers

What @id actually does

In JSON-LD every entity is a node in a graph. @id is that node's identifier. It is written as a URL, but it is a name rather than a link: nothing has to load at that address, and search engines do not need to visit it. What matters is the string itself, because two nodes with exactly the same @id are treated as the same thing, and their properties are combined into one entity.

That single rule explains every problem in this guide:

The JSON Checker flags identifiers that are duplicated across different entities and shared entities that lack one, and it checks whether references in the markup lead to a defined node. The last of those has its own guide on fixing dangling references; this page is about the identifiers themselves.

The @id problems, and what each one causes

ProblemExampleResult
Same @id on different entitiesA theme gives both the WebPage and the Article https://www.example.co.uk/blog/wet-rooms/One node with two types and a mixture of properties
Organisation identified by the home page address"@id": "https://www.example.co.uk/" on the Organization, while the WebSite or home WebPage also uses itThe company, the website and the home page merge into one
Relative fragment only"@id": "#organization"Resolves against each page's address, so every page defines a different organisation
Host or protocol drifthttp://example.co.uk/#organization on some pages, https://www.example.co.uk/#organization on othersTwo organisations that never connect
Tracking parameters in the identifierAn @id built from the current URL, including ?utm_source=newsletterA new identity for every campaign link
Missing on shared entitiesAuthor written out in full on each article with no @idMany unconnected copies of the same person
Generic fragment reused for every item"@id": "https://www.example.co.uk/#product" on every product pageEvery product on the site claims to be the same product

The last row is more common than it sounds. It happens when a template hard-codes the site address plus #product instead of using the page address plus #product. A single product is described correctly on each page, but across the site the markup says there is only one product with hundreds of names and prices.

The relative identifier trap

Many hand-written blocks and some older plugins use short identifiers such as "@id": "#organization". JSON-LD treats that as a relative address and resolves it against the address of the page it appears on, in the same way a browser resolves a relative link. So the same line of code produces a different identifier on every page:

PageWrittenResolves to
Home page#organizationhttps://www.example.co.uk/#organization
Services page#organizationhttps://www.example.co.uk/services/#organization
A blog post#organizationhttps://www.example.co.uk/blog/wet-rooms/#organization

Within a single page that is harmless, because the definition and any reference to it resolve the same way. Across a site, it means your business is described as a separate entity on every page. The fix is simple: write site-wide identifiers in full, starting with https://, so they are identical wherever they appear.

๐Ÿ’ก A relative fragment is fine for something that genuinely belongs to one page, such as that page's breadcrumb trail. It is only site-wide entities, such as the organisation, the website and people, that need an absolute identifier.

A naming scheme that holds up

There is no official format for identifiers. What matters is that each is unique to one thing, identical everywhere that thing appears, and never changes. The simplest way to achieve that is to base each identifier on the canonical address of the page most closely associated with the entity, plus a short fragment naming its role.

EntitySuggested @idScope
Organization or LocalBusinesshttps://www.example.co.uk/#organizationWhole site
WebSitehttps://www.example.co.uk/#websiteWhole site
Person (author or staff)Their profile page plus #personWhole site
Branch of a chainThe branch page plus #localbusinessWhole site
WebPageThe page's canonical addressThat page
Article, Product, Event, RecipeThe page's canonical address plus #article, #product and so onThat page
BreadcrumbListThe page's canonical address plus #breadcrumbThat page

Four rules make the scheme reliable:

  1. Use the canonical address. Take it from the same value as the page's canonical link, not from the address in the browser, so query strings, tracking parameters and alternative hosts never leak in.
  2. One host, one protocol. Pick the version your site redirects to, such as https with www, and use it in every identifier, even on staging copies that are served from another address.
  3. Give each role its own fragment. The page and the thing the page is about are different entities. The WebPage takes the bare address; the Article or Product takes the address plus a fragment.
  4. Never change an identifier once it is live. If a page moves, keeping the old identifier is acceptable because it is only a name. Changing it disconnects everything already recorded about that entity.

The same scheme applied to a blog post looks like this. Note that the WebPage, the article, the organisation and the author each have their own identifier, and the article points to the others by identifier instead of repeating them.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://www.example.co.uk/#organization",
      "name": "Hollin Lane Bathrooms",
      "url": "https://www.example.co.uk/",
      "logo": "https://www.example.co.uk/images/hollin-lane-logo.png"
    },
    {
      "@type": "WebSite",
      "@id": "https://www.example.co.uk/#website",
      "url": "https://www.example.co.uk/",
      "name": "Hollin Lane Bathrooms",
      "publisher": { "@id": "https://www.example.co.uk/#organization" }
    },
    {
      "@type": "Person",
      "@id": "https://www.example.co.uk/team/priya-hale/#person",
      "name": "Priya Hale",
      "url": "https://www.example.co.uk/team/priya-hale/"
    },
    {
      "@type": "WebPage",
      "@id": "https://www.example.co.uk/blog/wet-rooms/",
      "url": "https://www.example.co.uk/blog/wet-rooms/",
      "name": "Planning a Wet Room in an Older House",
      "isPartOf": { "@id": "https://www.example.co.uk/#website" }
    },
    {
      "@type": "BlogPosting",
      "@id": "https://www.example.co.uk/blog/wet-rooms/#article",
      "headline": "Planning a Wet Room in an Older House",
      "datePublished": "2026-06-11T10:00:00+01:00",
      "mainEntityOfPage": { "@id": "https://www.example.co.uk/blog/wet-rooms/" },
      "author": { "@id": "https://www.example.co.uk/team/priya-hale/#person" },
      "publisher": { "@id": "https://www.example.co.uk/#organization" }
    }
  ]
}
</script>

The fix, step by step

Step 1

Collect the identifiers from each template

Open one page built from each template, such as the home page, an article, a product, a category, a contact page and a team profile, and copy every @id value into a spreadsheet with the page address, the entity type and the tool that wrote it. Include blocks from the theme, SEO plugin, shop platform and any apps. Seeing the identifiers side by side is the quickest way to spot drift, duplicates and fragments that change from page to page.

Step 2

Choose one canonical form for the base address

Decide on the single protocol and host that your site redirects to, for example https://www.example.co.uk, and whether page addresses end with a slash. Then check every identifier in your spreadsheet against it. Anything using http, the bare domain, a staging host, a trailing slash difference or a query string needs changing, because each of those small differences creates a separate identity in the eyes of any processor reading your markup.

Step 3

Give each shared entity a single site-wide @id

For the organisation, the website, each author and each branch, choose one absolute identifier and write it in full on every page, never as a relative fragment. If your SEO plugin already outputs identifiers for these entities, adopt the plugin's values in any custom code instead of inventing new ones, so the two sources describe the same nodes. Record the final identifiers somewhere your developers can copy from, so nobody types them from memory later.

Step 4

Make page-level identifiers unique to their page

For entities that belong to one page, such as the WebPage, the article, the product and the breadcrumb, build the identifier from that page's canonical address plus a role fragment. Replace any template that hard-codes the site root plus #product or #article, because that makes every page claim the same entity. Take the address from the same variable as the canonical tag, so parameters and alternative hosts cannot slip into the identifier.

Step 5

Separate entities that share an identifier by mistake

Where two different things carry the same @id, such as a WebPage and its Article, or an Organization and the home page, give one of them a new fragment and update any references that pointed to it. Keep a shared identifier only where the two nodes really describe the same thing, for example the organisation defined by your SEO plugin and extended by your own block. Check that the merged result has no contradictory values.

Step 6

Retest pages from every template

Clear page and plugin caches, then run the JSON Checker on each template again and compare the new identifiers with your spreadsheet. The organisation and website identifiers should be identical on every page, while page-level identifiers should all differ. Confirm in the Schema Markup Validator that each entity appears once with the properties you expect, and repeat the check after theme, plugin or platform updates, which sometimes reset identifier formats.

When two blocks share an @id on purpose

Reusing an identifier is not always a mistake. It is the correct way to say "this is the same thing" across blocks, and it lets you add to an entity that another tool defines without replacing it. If your SEO plugin describes the organisation with https://www.example.co.uk/#organization, your own block can add opening hours or a sameAs list to the same node by using the same identifier.

Two cautions apply. First, the combined node must still make sense: if the plugin says the organisation is called one thing and your block says another, the entity ends up with two names. That is the subject of the guide on fixing conflicting schema blocks. Second, if the two blocks give the node different types, it becomes both types. That is intended when a business is genuinely a LocalBusiness and an Organization, but not when a theme labels the home page as an organisation.

Not every tool merges nodes across separate script blocks on one page in the same way. Placing the pieces in one @graph where you control the output removes any doubt; where you cannot, identical identifiers are still the best signal you can give.

Identifiers on common platforms

Most SEO plugins and shop platforms generate identifiers automatically, and it is usually better to work with their format than against it. Formats change between versions, so always read the live output rather than relying on documentation or this summary.

โš ๏ธ Switching SEO plugins almost always changes every identifier on the site. That is not a disaster, because search engines rebuild their understanding over time, but check that any custom code or app that referred to the old identifiers is updated in the same change.
Questions and answers

1. Does the @id address have to be a real page?

No. An @id is a name for an entity, written in the form of a web address so that it is globally unique. Nothing needs to load at that address, and search engines are not expected to visit it. That said, basing identifiers on real canonical page addresses is sensible, because it guarantees uniqueness, makes them easy to read, and ties each entity to the page most closely about it. Just make sure the same entity always gets exactly the same string.

2. Why is #organization on its own a problem?

A bare fragment is a relative address, so JSON-LD resolves it against the address of the page it is on. On the home page it becomes the home address plus the fragment; on the contact page it becomes the contact address plus the fragment. Your organisation therefore gets a different identifier on every page, and the separate copies never connect. Write site-wide identifiers in full, starting with https and your chosen host, so they are identical everywhere they appear.

3. Should the WebPage and the Article have the same @id?

No. The page is the document at an address, while the article is the piece of writing it contains, and schema.org treats them as separate entities with different properties. Give the WebPage the canonical address and the Article the same address plus a fragment such as #article, then link them with mainEntityOfPage on the article or mainEntity on the page. Sharing one identifier merges the two into a single node that carries both types and a jumble of properties.

4. Do small nested objects like addresses and offers need an @id?

Usually not. An address, an offer, a rating or an opening hours specification normally belongs to one entity and is never referred to from anywhere else, so leaving it as a nested object without an identifier is correct and keeps the markup simpler. Identifiers matter for things that are shared or referred to, such as your organisation, your website, people, and the main entity of each page. Adding identifiers everywhere adds maintenance without adding meaning.

5. I changed SEO plugin and all my @id values changed. Is that harmful?

It is not harmful in the sense of a penalty, and search engines will pick up the new identifiers as they recrawl. The risk is breakage around the edges: custom code, a shop app or a hand-written block that still refers to the old identifiers now points at nothing. After a switch, search your theme, snippets and apps for the old identifier strings and update them, then run the JSON Checker across each template to confirm every reference resolves.

6. Can two separate script blocks describe the same entity with one @id?

Yes, and that is the proper way to extend an entity defined by a plugin you cannot edit. Both blocks use the identical @id and each contributes properties. Keep the values consistent, because contradictions in the combined node are treated as a conflict. Not every tool combines nodes across separate script blocks identically, so where you control all the markup, putting everything into a single @graph is the more predictable arrangement and easier to check by eye.

7. Should my @id include www or not?

It should match whichever version your site redirects to and uses in its canonical tags. If the live site is https with www, every identifier should use https with www. The important point is consistency: mixing the bare domain and the www version, or http and https, creates two identities for the same thing. Pick one, build identifiers from the same setting the canonical tag uses, and check staging environments do not leak their own host into the markup.

See every identifier on your page

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 Dangling References  ·  Fixing Conflicting Schema Blocks  ·  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.