/ AI Schema Fixes / AI Schema References

How to Fix Lost Entity References in AI Schema

AI schema generators often output authors and publishers as bare strings rather than linked Person/Organization entities. "author": "Jane Baker" is just a name — AI engines can't connect Jane to other articles she wrote, her LinkedIn, or her credentials. "author": {"@type": "Person", "@id": "..."} is an entity that author trust signals compound across. This guide covers the closed-graph pattern.

1. String vs entity reference

Bad: string author

{
  "@type": "Article",
  "headline": "How to choose a CRM",
  "author": "Jane Baker",                   ❌ string
  "publisher": "Acme Corp"                  ❌ string
}

OK: inline entity

{
  "@type": "Article",
  "headline": "How to choose a CRM",
  "author": {                               ⚠️ inline (works, doesn't link)
    "@type": "Person",
    "name": "Jane Baker"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Acme Corp"
  }
}

Best: entity with @id, defined once, referenced everywhere

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Person",
      "@id": "https://example.com/authors/jane-baker#person",
      "name": "Jane Baker",
      "jobTitle": "Sales Operations Lead",
      "url": "https://example.com/authors/jane-baker",
      "sameAs": [
        "https://linkedin.com/in/janebaker",
        "https://twitter.com/janebaker"
      ]
    },
    {
      "@type": "Organization",
      "@id": "https://example.com/#organization",
      "name": "Acme Corp",
      "url": "https://example.com",
      "logo": {
        "@type": "ImageObject",
        "url": "https://example.com/logo.png"
      }
    },
    {
      "@type": "Article",
      "headline": "How to choose a CRM",
      "author": { "@id": "https://example.com/authors/jane-baker#person" },
      "publisher": { "@id": "https://example.com/#organization" }
    }
  ]
}

2. Common bare-string fields

3. Post-process generator output

If your generator can't be configured to use @id references, transform after generation:

function entityifyAuthors(schema, knownPeople, knownOrgs) {
  function walk(obj) {
    if (!obj || typeof obj !== 'object') return obj;
    
    // Fields that should be entities
    for (const field of ['author', 'publisher', 'brand', 'creator', 'editor']) {
      if (typeof obj[field] === 'string') {
        const name = obj[field];
        // Look up known entity by name
        const person = knownPeople[name];
        const org = knownOrgs[name];
        if (person) {
          obj[field] = { '@id': person.id };
        } else if (org) {
          obj[field] = { '@id': org.id };
        } else {
          // No match — at least make it an inline entity
          obj[field] = {
            '@type': field === 'publisher' ? 'Organization' : 'Person',
            'name': name
          };
        }
      }
    }
    
    for (const v of Object.values(obj)) {
      if (typeof v === 'object') walk(v);
    }
    return obj;
  }
  return walk(schema);
}

// Usage
const knownPeople = {
  'Jane Baker': { id: 'https://example.com/authors/jane-baker#person' },
  // ... load from CMS
};
const knownOrgs = {
  'Acme Corp': { id: 'https://example.com/#organization' }
};
const fixed = entityifyAuthors(generatedSchema, knownPeople, knownOrgs);

4. Site-wide entity catalogue

Define each entity once, in one place, used everywhere:

// /lib/schema-entities.js
export const ORG = {
  '@type': 'Organization',
  '@id': 'https://example.com/#organization',
  'name': 'Acme Corp',
  'url': 'https://example.com',
  'logo': {
    '@type': 'ImageObject',
    'url': 'https://example.com/logo.png',
    'width': 600,
    'height': 60
  },
  'sameAs': [
    'https://linkedin.com/company/acme',
    'https://twitter.com/acmecorp'
  ]
};

export const WEBSITE = {
  '@type': 'WebSite',
  '@id': 'https://example.com/#website',
  'url': 'https://example.com',
  'name': 'Acme',
  'publisher': { '@id': 'https://example.com/#organization' }
};

// Per-author records, generated from CMS
export function personEntity(author) {
  return {
    '@type': 'Person',
    '@id': `https://example.com/authors/${author.slug}#person`,
    'name': author.name,
    'url': `https://example.com/authors/${author.slug}`,
    'jobTitle': author.jobTitle,
    'image': author.imageUrl,
    'sameAs': author.profiles  // [linkedin, twitter, github]
  };
}

5. Build the @graph per page

// In your article render path
import { ORG, WEBSITE, personEntity } from '@/lib/schema-entities';

function articleGraph(article) {
  const author = personEntity(article.author);
  
  return {
    '@context': 'https://schema.org',
    '@graph': [
      ORG,
      WEBSITE,
      author,
      {
        '@type': 'Article',
        '@id': `${article.url}#article`,
        'headline': article.title,
        'datePublished': article.publishedAt,
        'dateModified': article.modifiedAt,
        'author': { '@id': author['@id'] },
        'publisher': { '@id': ORG['@id'] },
        'isPartOf': { '@id': WEBSITE['@id'] },
        'mainEntityOfPage': article.url
      }
    ]
  };
}

// Output as one JSON-LD block per page
<script type="application/ld+json">
  {JSON.stringify(articleGraph(article))}
</script>

6. External entity references

Reference well-known entities by their canonical URL — Wikidata, official sites:

{
  "@type": "Article",
  "about": {
    "@type": "Thing",
    "@id": "https://en.wikipedia.org/wiki/Customer_relationship_management"
  },
  "mentions": [
    { "@id": "https://www.salesforce.com/#organization" },
    { "@id": "https://www.hubspot.com/#organization" }
  ]
}

// AI engines and Google often verify external @id targets
// Where they resolve to actual schema or recognised entities, signal strengthens

7. Validate the closed graph

Step 1
Check every @id reference resolves
function validateGraphReferences(graph) {
  const defined = new Set();
  const referenced = new Set();
  
  function walk(obj) {
    if (!obj || typeof obj !== 'object') return;
    if (obj['@id'] && Object.keys(obj).length > 1) {
      // Has @id and other properties = definition
      defined.add(obj['@id']);
    } else if (obj['@id']) {
      // Only @id = reference
      referenced.add(obj['@id']);
    }
    for (const v of Object.values(obj)) {
      if (typeof v === 'object') walk(v);
    }
  }
  walk(graph);
  
  const unresolved = [...referenced].filter(id => !defined.has(id));
  return { defined, referenced, unresolved };
}
Step 2
Rich Results Test
search.google.com/test/rich-results — should report Article with Person author (not string), Organization publisher (not string). If still strings, check generator output and post-processing.
💡 The compound win: when every article references the same Person @id for Jane, Google and AI engines build ONE entity from Jane's collected work. After 20 articles, Jane is a known author entity — citation weight rises, AI engines start surfacing her by name when asked about her topics. Without @id, each article's "Jane Baker" is a new string with no history.

🤖 Re-run AI Schema Generator

Validate entity references in fresh output.

Run AI Schema Generator →
Related Guides: AI Schema Fixes  ·  Fix Schema Duplicates  ·  Fix Author Trust  ·  Fix JSON-LD Coverage
💬 Got a problem?