The decision this protects
The decision is which things on this site are entities, and which single page speaks for each one. On a site with one office and one owner that decision makes itself. On a site with eight branches, a founder and a logo it does not, and if it is left to whoever builds each page you get eight Organizations with four phone formats and a founder attached to nothing, which is what I found at a childcare group with eight branches before filling the first version of this worksheet. Every cell left blank here becomes a contradiction in the graph later.
Before you start
- The full URL list from the sitemap, so you know which pages can declare an entity.
- The legal name and the trading name, and a decision on which goes in
nameand which inlegalName. - The address and phone of every location, from whatever the business treats as authoritative, and who maintains it.
- The people who will be named in the graph and the one page each is described on.
- The output of
idledger.pyfrom my multi-branch schema guide, so you know what the site declares today.
Stated limits
- It decides identity and structure, not rich result eligibility, which depends on fields it does not track.
- It does not choose the schema type for you. It records the type you chose and asks you to justify it.
- It covers organisations, locations, people and logos. Products, events and articles follow the same rules with different property lists.
- It assumes one site on one domain.
1. List the entities
An entity is anything a second node will point at, or that a search engine would treat as a distinct thing with its own name. An address or a set of opening hours is a property of a location and gets no row.
| Field | What to record | Why it matters |
|---|---|---|
| Entity label | A plain-English name such as “the group”, “Rosewood branch”, “the founder”, “the logo” | Keeps the client conversation unambiguous before any @id exists |
| Schema type | The most specific schema.org type that is true, such as EducationalOrganization, ChildCare, Person, ImageObject | The type sets which properties are valid; a LocalBusiness subtype is both Organization and Place |
| Why it is a separate row | One sentence, such as “eight branches share a name but not an address” | If the sentence will not write, the entity is a property of another row |
| Visible on the site as | Where a visitor sees this thing described in text | Google’s guidelines require markup to describe visible content |
Judgment prompt: Would a parent searching for this thing expect it as its own result, or as a detail on something else’s result? If the second, move it back into a property.
2. Mint the @id and fix where it is declared
An @id is an absolute URL that names the node, minted once, written down here and copied from here into every block. Each @id is declared, with its full set of properties, on exactly one page, and every other mention is a reference containing only {"@id": "..."}.
| Field | What to record | Why it matters |
|---|---|---|
| Declared on | The canonical URL of the one page that carries the full node, such as https://example.com/locations/rosewood |
Two declarations of one @id with different phone strings merge into one entity with two phone numbers |
| Fragment | A short stable token, such as #org, #branch, #founder, #logo |
The fragment is part of the identifier, so #branch on two page URLs yields two identifiers |
| Absolute @id | Declaring page URL plus fragment, copied exactly, such as https://example.com/about#founder |
A relative #org resolves against whichever page it sits on and silently creates a new entity on every page |
| Legacy blocks to remove | Any existing block that declares this entity without the @id or with a different one | Old markup competes with the new graph until deleted and the cache cleared |
Judgment prompt: If this page were deleted next year, which page would inherit the entity, and are you prepared to rename every reference on the site to match?
3. Referenced from
A reference never repeats the name, the phone or the address.
| Field | What to record | Why it matters |
|---|---|---|
| Referenced from | Every other page whose markup points at this @id, by URL or template name | Eight branches means eight references to the parent, and the count is checkable against the sitemap |
| Property used | The property on the referencing node, such as parentOrganization, founder, logo |
Its expected type must match the row’s schema type |
| Reference shape | Confirm it is {"@id": "..."} alone, or record the deliberate exception |
A reference that also carries a name is a second declaration, and the ledger script will flag it |
Judgment prompt: Compare the number of pages in “referenced from” with the number of pages that mention this entity in visible text. If they differ, one of the two lists is wrong.
4. Links between entities
Every arrow in the graph is a property on one row pointing at the @id of another row. Record both ends.
| Field | What to record | Why it matters |
|---|---|---|
| parentOrganization | For each location, the @id of the organisation it belongs to | The upward link that makes a branch read as a branch rather than a separate business |
| subOrganization | On the parent, the full list of location @ids, references only | A branch missing here is a branch the parent does not claim |
| founder and employee | On the organisation, the @id of each Person row, never a name string | A name string is a new anonymous Person on every page; an @id is one person |
| brand | Where the trading brand differs from the legal entity, the @id of a Brand row | Keeps the brand name out of name where it would conflict with legalName |
| logo and image | The @id of the single ImageObject row | One logo with width, height and URL, instead of eight slightly different image URLs |
| sameAs | The profile URLs and the one row that carries them | Profiles belong to the entity they describe, usually the organisation, rarely a branch |
Judgment prompt: Read each arrow aloud as a sentence, such as “Rosewood branch has parentOrganization the group”. If it is untrue or the target row does not exist, delete the arrow or add the row.
5. Source of truth for name, address and phone
The original site had four phone formats because four people typed a phone number. Name where each value comes from and the one format it is written in, so the markup is generated rather than typed.
| Field | What to record | Why it matters |
|---|---|---|
| Name source | Where the exact name string comes from, such as the Google Business Profile listing |
The markup name must match what a visitor sees and what citations say |
| Address source | The document or system the address is copied from | Two branches on the childcare site had inherited the head office address from a copied template |
| Phone source and format | Where the number comes from and the single format rule, such as country code, space, area code, space, number | Four formats of one number read as four numbers |
| Owner and last verified | Who updates the value, and when it was last checked | Without an owner the markup drifts from the business within a year |
| Generated or typed | Rendered from a locations table or JSON file, or hand-written | Hand-written blocks are where the four phone formats came from |
Judgment prompt: If one branch’s phone number changed tomorrow, name the single cell someone would edit. If the answer is more than one cell, the source of truth is not yet single.
The defensible output
One table, one row per entity, in which every @id appears in exactly one “Declared on” cell and every @id named in “Links to” appears as a row of its own. Run idledger.py against the built site and its ledger should report PASS with the counts this table predicts. The other seven branches are rows identical in shape to the Rosewood row.
| Entity | Schema type | @id | Declared on | Referenced from | Links to | NAP source |
|---|---|---|---|---|---|---|
| The group | EducationalOrganization | https://example.com/#org |
/ |
8 branch pages, /about |
founder to #founder; subOrganization to 8 branch @ids; logo and image to #logo; sameAs to 3 profile URLs |
Google Business Profile, owner [VERIFY: name], verified [VERIFY: date] |
| Rosewood branch | ChildCare | https://example.com/locations/rosewood#branch |
/locations/rosewood |
/ |
parentOrganization to #org; image to #logo |
Locations table row 1, generated, owner [VERIFY: name] |
| The founder | Person | https://example.com/about#founder |
/about |
/ |
worksFor to #org |
About page copy, no address or phone |
| The logo | ImageObject | https://example.com/#logo |
/ |
8 branch pages | none | Theme file path, 512 by 512 |
A table that passes has no @id in two “Declared on” cells, no arrow pointing at an @id that is not a row, and a “NAP source” that names a system rather than a person’s memory.
Where I could be wrong
Google’s documentation does not promise that @id references across pages are stitched into one entity. If Google reads each page in isolation, a bare reference on a branch page tells it nothing about the parent’s name, and a minimal repeated declaration with @id, @type and name might serve the branch better, in which case the “reference shape” exception applies to every branch row.
The one-declaring-page rule assumes every entity has a natural home page. A founder who appears only in a footer has to borrow one, and that @id rests on a URL chosen for convenience.
Section 5 assumes the business has a source of truth to name. Some do not, and the honest entry is then “none yet” with an owner assigned to create it, and the markup should wait until it exists.
Sources
- W3C, JSON-LD 1.1, node identifiers. https://www.w3.org/TR/json-ld11/#node-identifiers
- schema.org, Organization type and properties. https://schema.org/Organization
- schema.org, LocalBusiness type. https://schema.org/LocalBusiness
- Google Search Central, General structured data guidelines. https://developers.google.com/search/docs/appearance/structured-data/sd-policies
- Google Search Central, Organization structured data. https://developers.google.com/search/docs/appearance/structured-data/organization
- Schema Markup Validator. https://validator.schema.org/
- Google, Rich Results Test. https://search.google.com/test/rich-results