# ProfilePage.mainEntity as a bare @id reference — the Search Console error that hides in valid schema

Search Console flags ProfilePage and AboutPage with &#39;Invalid object type for field mainEntity&#39; when the referenced Person/Organization lives on another URL. The schema is valid. The Rich Results Test is green. Here is the fix and why it is necessary.

Author: J.A. Watte
Published: May 16, 2026
Source: https://jwatte.com/blog/blog-tool-profilepage-mainentity-typed-reference/

---

Google Search Console flagged a real estate site I work on yesterday with this error on the /about page:

> Invalid object type for field 'mainEntity'

The page validated clean on schema.org's validator. It validated clean on Google's Rich Results Test. The JSON-LD parsed without complaint in every browser console. Search Console flagged it anyway, and the rich-result on the page got marked ineligible.

The pattern is common. If you put your Person or Organization entity on the homepage and reference it from a separate /about page, you probably have this bug, and Search Console will tell you about it eventually.

## What the bug looks like

The homepage carries the canonical Person definition:

```json
{
  "@context": "https://schema.org",
  "@type": "Person",
  "@id": "https://example.com/#seth-watte",
  "name": "Seth Watte",
  "url": "https://example.com/",
  "jobTitle": "Real Estate Agent",
  "worksFor": {"@id": "https://example.com/#organization"}
}
```

The /about page has the ProfilePage rich-result wrapper, which references that Person by `@id`:

```json
{
  "@context": "https://schema.org",
  "@type": "ProfilePage",
  "@id": "https://example.com/about/#profilepage",
  "mainEntity": {"@id": "https://example.com/#seth-watte"},
  "dateCreated": "2026-04-01",
  "dateModified": "2026-05-13"
}
```

That reads correctly to a JSON-LD parser. The Person at `https://example.com/#seth-watte` is defined on the homepage. The /about page's ProfilePage points back at it. Schema.org's data model explicitly allows cross-document `@id` references.

Search Console disagrees. The validator emits "Invalid object type for field 'mainEntity'" because, on the /about page in isolation, the reference is just `{"@id": "https://example.com/#seth-watte"}`. There is no `@type` hint on the reference itself. There is no inline definition of the Person on this page. Search Console's strict validator cannot determine what `mainEntity` is supposed to be, so it flags the field as having an invalid object type.

## Why the validators give different answers

Schema.org's validator answers the question "is the JSON-LD structurally valid and do the field types match the spec?" It walks the document and the answer is yes. A Person is allowed as `mainEntity` on a ProfilePage. The reference is structurally a valid `@id` pointer.

Google's Rich Results Test answers a similar question, scoped to whether the JSON-LD is eligible to produce a rich result. It runs more checks than schema.org but it still does not fail this pattern.

Google Search Console's rich-result report uses a stricter validator. It is the same validator that decides whether your ProfilePage actually appears in search results. It treats every page as a self-contained document for validation purposes. If the type of `mainEntity` cannot be established from this document alone, it flags the field.

The contradiction is real. Schema.org says cross-document `@id` references are valid. Search Console's rich-result validator wants the type either inline on the reference or defined inline on the same page. You have to satisfy both.

## Fix A: type the reference inline

The cheapest fix is to add the `@type` alongside the `@id` on the reference itself:

```json
{
  "@type": "ProfilePage",
  "@id": "https://example.com/about/#profilepage",
  "mainEntity": {
    "@id": "https://example.com/#seth-watte",
    "@type": "Person"
  },
  "dateCreated": "2026-04-01T00:00:00-06:00",
  "dateModified": "2026-05-13T00:00:00-06:00"
}
```

The reference now carries enough information for Search Console to resolve the type without fetching the homepage. The `@id` still points at the canonical Person on the homepage, so the Knowledge Graph treats it as one entity. Both validators are satisfied.

Note that I also fixed the date-only issue at the same time. Search Console flags `"dateModified": "2026-05-13"` with "Invalid datetime value for dateModified" for the same class of strict-validator reason. See the [companion post on ISO 8601 datetime fields](/blog/blog-tool-jsonld-datetime-iso8601/) for the timezone-offset rules.

## Fix B: define the entity inline on the same page

The stronger pattern is to define the Person inline on the /about page directly, while keeping the same `@id` so it stays one entity for the Knowledge Graph:

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Person",
      "@id": "https://example.com/#seth-watte",
      "name": "Seth Watte",
      "url": "https://example.com/",
      "jobTitle": "Real Estate Agent",
      "worksFor": {"@id": "https://example.com/#organization"}
    },
    {
      "@type": "ProfilePage",
      "@id": "https://example.com/about/#profilepage",
      "mainEntity": {"@id": "https://example.com/#seth-watte"},
      "dateCreated": "2026-04-01T00:00:00-06:00",
      "dateModified": "2026-05-13T00:00:00-06:00"
    }
  ]
}
```

JSON-LD merges nodes by `@id`. The Person defined on the homepage and the Person defined inline on the /about page collapse into one entity in the Knowledge Graph because they share the same `@id`. Search Console gets a typed `Person` inline on the document, so the strict validator passes. AI Mode gets a Person inline on the page that the ProfilePage describes, which is the binding direction it actually prefers when it picks a canonical bio page for a name query.

Fix B is more lines of HTML and more bytes per page, but it is the pattern I recommend when the page IS the bio page for the entity. ProfilePage exists specifically to flag a page as the canonical home of a person or organization. Defining that entity inline on its own canonical page is more consistent with how AI Mode walks the graph than relying on a cross-document reference.

## Cases where Fix A is right and Fix B is wrong

The author bio page is one case where Fix B fits. Other cases need Fix A.

If a blog post emits an Article with `author: {"@id": "https://example.com/#seth-watte"}` and the page is not the author's bio page, do not inline the full Person definition on the Article page. Inlining the Person on every Article page means the canonical Person definition exists in fifty different places, with potential drift in alternateName, sameAs, and other fields. Use Fix A on Article pages. Reference the Person by `@id` with an inline `@type: Person` hint, and let the canonical definition live on the bio page only.

The shape of the rule. Inline-define an entity on exactly one page (the page that IS the entity's canonical home). Reference it with `@id` plus inline `@type` hint everywhere else. Search Console is happy. The Knowledge Graph treats it as one entity. AI Mode follows the canonical-bio binding when name-only queries fire.

## How to find every occurrence

The mega analyzer detector for this bug walks every JSON-LD block on the page, collects the set of `@id` values that are inline-defined with a `@type`, then walks ProfilePage / AboutPage / WebPage / CollectionPage / Article / BlogPosting / NewsArticle nodes and checks every `mainEntity` / `about` / `author` / `publisher` field. If the field value is `{"@id": "..."}` with no `@type` and the referenced `@id` is not in the inline-defined set, it surfaces the field as a Search Console risk.

You can run the same check by hand in DevTools:

```js
const blocks = [...document.querySelectorAll('script[type="application/ld+json"]')];

const definedIds = new Set();
const checkRefs = [];
const BIND_FIELDS = ['mainEntity', 'about', 'author', 'publisher'];
const PAGE_TYPES = /^(ProfilePage|AboutPage|WebPage|CollectionPage|Article|BlogPosting|NewsArticle|ItemPage)$/;

function walk(n, fn) {
  if (!n || typeof n !== 'object') return;
  if (Array.isArray(n)) return n.forEach(x => walk(x, fn));
  fn(n);
  for (const k of Object.keys(n)) if (n[k] && typeof n[k] === 'object' && k !== '@type') walk(n[k], fn);
}

blocks.forEach(b => {
  try {
    const data = JSON.parse(b.textContent);
    walk(data, n => {
      const tt = Array.isArray(n['@type']) ? n['@type'] : [n['@type']].filter(Boolean);
      if (n['@id'] && tt.length > 0) definedIds.add(n['@id']);
    });
    walk(data, n => {
      const tt = Array.isArray(n['@type']) ? n['@type'] : [n['@type']].filter(Boolean);
      if (tt.some(t => PAGE_TYPES.test(t))) {
        BIND_FIELDS.forEach(f => {
          const v = n[f];
          const check = vv => {
            if (vv && typeof vv === 'object' && !Array.isArray(vv) && vv['@id'] && !vv['@type']) {
              checkRefs.push({parent: tt[0], field: f, ref: vv['@id'], inline: definedIds.has(vv['@id'])});
            }
          };
          if (Array.isArray(v)) v.forEach(check); else check(v);
        });
      }
    });
  } catch (e) {}
});

const unresolved = checkRefs.filter(r => !r.inline);
console.log({total: checkRefs.length, unresolved});
```

Anything in the `unresolved` array is what Search Console flags.

## Why this matters for AI search

Schema validators are answering one question. Search Console is answering a stricter version of that same question. AI search retrievers (Google AI Mode, Gemini, Perplexity) are answering a different question entirely.

When AI Mode resolves a name-only query to one URL, it follows a chain of bindings. Person at homepage `@id`. Person.mainEntityOfPage object pointing at the /about page's ProfilePage `@id`. ProfilePage.mainEntity pointing back at the Person `@id`. The bidirectional loop tells AI Mode that the /about page is the canonical bio for this Person.

The bug under discussion breaks the loop one way. ProfilePage cannot establish that mainEntity is a Person without fetching the homepage. Search Console flags it as a rich-result error. AI Mode's confidence drops because the binding is one-sided. Either fix restores the loop. Fix A by inline-typing the reference. Fix B by inline-defining the entity.

If you operate a site where the same Person or Organization is referenced from multiple URLs, the audit habit to build is grep every `<script type="application/ld+json">` block for bare `{"@id": "..."}` references on `mainEntity` / `about` / `author` / `publisher` fields, then check whether the referenced `@id` is defined as a typed node on the same page. If not, fix it before Search Console finds it.

## When the bug shipped on this site

This bug shipped on the yoseth.com /about page on 2026-05-13 as part of a ProfilePage migration that consolidated the bio pattern across sites. The migration moved Person definitions to the homepage to avoid duplication, then referenced them from /about with bare `@id` pointers. The validators all passed. Search Console reported the error three days later when the next crawl finished. The detector in the mega analyzer was added the same day Search Console flagged it, so other sites that copied the same pattern will surface this bug before deploy now.

## Related reading

- [The companion ISO 8601 datetime post](/blog/blog-tool-jsonld-datetime-iso8601/) — the other Search Console strict-validator error that ships in the same regression
- [Nine AI Mode entity-binding bugs that pass every schema validator](/blog/blog-ai-mode-binding-bugs-validators-miss/) — the predecessor post; this is bug ten
- [Your schema validates. Google AI Mode still cannot find you.](/blog/blog-ai-mode-entity-anchors-knowledge-graph/) — broader entity-anchor pattern
- [Schema validator vs Rich Results vs Search Console — three validators, three different verdicts](/blog/blog-tool-rich-results-eligibility-audit/) — why the three validators diverge
- [The Mega Analyzer](/blog/blog-tool-mega-analyzer/) — where this detector lives in the audit chain

## Fact-check notes and sources

- Schema.org Person and ProfilePage types: [schema.org/Person](https://schema.org/Person), [schema.org/ProfilePage](https://schema.org/ProfilePage)
- JSON-LD 1.1 `@id` cross-document reference spec: [www.w3.org/TR/json-ld11/#node-identifiers](https://www.w3.org/TR/json-ld11/#node-identifiers)
- Google ProfilePage rich-result guidance: [developers.google.com/search/docs/appearance/structured-data/profile-page](https://developers.google.com/search/docs/appearance/structured-data/profile-page)
- Search Console rich-result error reference: [support.google.com/webmasters/answer/7552505](https://support.google.com/webmasters/answer/7552505)

---

*This post is informational, not legal or SEO-consulting advice. Examples reference my own sites and sites I work on, with no third party named as a negative case study.*


---

Canonical HTML: https://jwatte.com/blog/blog-tool-profilepage-mainentity-typed-reference/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/blog-tool-profilepage-mainentity-typed-reference.webp
