Multi-Schema Pages with @graph

Most real-world pages need more than one schema type. The @graph pattern lets you combine them into a single JSON-LD block with cross-references, avoiding duplicate @context declarations.

Table of contents

  1. Why @graph?
  2. Common Page Patterns
    1. Blog / Article Page
    2. E-Commerce Product Page
    3. Local Business Page
    4. Article + FAQ
  3. Cross-Referencing with @id
    1. SchemaIds API
  4. Output
  5. Fluent API

Why @graph?

Without @graph, you’d need multiple <script> tags:

<!-- Don't do this — multiple separate blocks -->
<script type="application/ld+json">{ "@context": "...", "@type": "Article", ... }</script>
<script type="application/ld+json">{ "@context": "...", "@type": "BreadcrumbList", ... }</script>
<script type="application/ld+json">{ "@context": "...", "@type": "Organization", ... }</script>

With @graph, one block covers everything:

<!-- Do this instead -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    { "@type": "Article", ... },
    { "@type": "BreadcrumbList", ... },
    { "@type": "Organization", ... }
  ]
}
</script>

This also enables cross-referencing between entities via @id.


Common Page Patterns

Blog / Article Page

import { createGraph, createArticle, createOrganization, createBreadcrumbList } from 'schemaorg-kit';

const graph = createGraph([
  createArticle({
    headline: 'Mastering TypeScript Generics',
    author: { '@type': 'Person', name: 'Alex Chen', url: 'https://techblog.example/authors/alex' },
    datePublished: '2025-03-20',
    image: 'https://techblog.example/articles/generics-1200x630.jpg',
    publisher: { '@type': 'Organization', name: 'Tech Blog', logo: { '@type': 'ImageObject', url: 'https://techblog.example/logo.png' } },
    mainEntityOfPage: 'https://techblog.example/articles/typescript-generics',
  }),
  createBreadcrumbList([
    { name: 'Home',          url: 'https://techblog.example' },
    { name: 'TypeScript',    url: 'https://techblog.example/typescript' },
    { name: 'Mastering TypeScript Generics' },
  ]),
]);

E-Commerce Product Page

import { createGraph, createProduct, createBreadcrumbList, createOrganization } from 'schemaorg-kit';

const graph = createGraph([
  createProduct({
    name: 'Trail Runner Pro',
    image: ['https://store.example/products/tr-pro-1.jpg'],
    offers: {
      '@type': 'Offer',
      price: 129.99,
      priceCurrency: 'USD',
      availability: 'InStock',
      seller: { '@type': 'Organization', name: 'TrailGear Store' },
    },
    aggregateRating: { '@type': 'AggregateRating', ratingValue: 4.8, ratingCount: 2340, bestRating: 5 },
  }),
  createBreadcrumbList([
    { name: 'Home',     url: 'https://store.example' },
    { name: 'Running',  url: 'https://store.example/running' },
    { name: 'Shoes',    url: 'https://store.example/running/shoes' },
    { name: 'Trail Runner Pro' },
  ]),
  createOrganization({
    name: 'TrailGear Store',
    url: 'https://store.example',
    logo: 'https://store.example/logo.png',
    sameAs: ['https://instagram.com/trailgear', 'https://facebook.com/trailgear'],
  }),
]);

Local Business Page

import { createGraph, createRestaurant, createWebSite, createBreadcrumbList } from 'schemaorg-kit';

const graph = createGraph([
  createRestaurant({
    name: 'The Golden Fork',
    address: {
      streetAddress: '450 Market Street',
      addressLocality: 'San Francisco',
      addressRegion: 'CA',
      postalCode: '94105',
      addressCountry: 'US',
    },
    geo: { latitude: 37.7955, longitude: -122.3976 },
    telephone: '+1-415-555-0192',
    servesCuisine: 'Italian',
    aggregateRating: { '@type': 'AggregateRating', ratingValue: 4.6, reviewCount: 1240, bestRating: 5 },
  }),
  createWebSite({
    name: 'The Golden Fork',
    url: 'https://goldenfork.com',
  }),
  createBreadcrumbList([
    { name: 'Home',   url: 'https://goldenfork.com' },
    { name: 'Reservations', url: 'https://goldenfork.com/reservations' },
  ]),
]);

Article + FAQ

import { createGraph, createArticle, createFAQPage, createBreadcrumbList } from 'schemaorg-kit';

const graph = createGraph([
  createArticle({
    headline: '10 Tips for Better Sleep',
    author: { '@type': 'Person', name: 'Dr. Sleep' },
    datePublished: '2025-02-01',
    image: 'https://healthblog.example/sleep-tips.jpg',
  }),
  createFAQPage([
    { question: 'How many hours of sleep do adults need?', answer: '7–9 hours per night is recommended for most adults.' },
    { question: 'What is sleep hygiene?', answer: 'Sleep hygiene refers to habits and practices that support consistent, quality sleep.' },
    { question: 'Can naps replace lost nighttime sleep?', answer: 'Short naps (20–30 min) can improve alertness, but they don\'t fully compensate for poor nighttime sleep.' },
  ]),
  createBreadcrumbList([
    { name: 'Home',   url: 'https://healthblog.example' },
    { name: 'Sleep',  url: 'https://healthblog.example/sleep' },
    { name: '10 Tips for Better Sleep' },
  ]),
]);

Cross-Referencing with @id

Use @id to reference the same entity in multiple places without repeating all its fields. The SchemaIds helper generates consistent IDs and { "@id": "..." } reference objects.

All reference fields accept { "@id": "..." } objects — including isPartOf, location, breadcrumb, mainEntity, publisher, seller, hiringOrganization, containedInPlace, department, and more. No as any needed anywhere.

import {
  SchemaIds, createGraph, createOrganization,
  createWebSite, createWebPage, createArticle, createBreadcrumbList,
} from 'schemaorg-kit';

const ids = new SchemaIds('https://example.com');

const graph = createGraph([
  createOrganization({
    '@id': ids.organization(),
    name: 'Example Corp',
    url: 'https://example.com',
    logo: 'https://example.com/logo.png',
  }),
  createWebSite({
    '@id': ids.website(),
    name: 'Example',
    url: 'https://example.com',
    publisher: ids.ref('organization'),
  }),
  createWebPage({
    '@id': ids.webpage(),
    url: 'https://example.com/blog/hello',
    isPartOf: ids.ref('website'),
    breadcrumb: ids.ref('breadcrumb'),
  }),
  createArticle({
    headline: 'Our Latest Innovation',
    datePublished: '2025-05-01',
    image: 'https://example.com/innovation.jpg',
    author: ids.ref('organization'),
    publisher: ids.ref('organization'),
    isPartOf: ids.ref('website'),
    mainEntityOfPage: ids.ref('webpage'),
  }),
  createBreadcrumbList([
    { name: 'Home', url: 'https://example.com' },
    { name: 'Blog', url: 'https://example.com/blog' },
    { name: 'Our Latest Innovation' },
  ]),
]);

Every @id reference resolves to the full entity definition elsewhere in the @graph.

SchemaIds API

const ids = new SchemaIds('https://example.com');

// Well-known IDs
ids.organization()  // "https://example.com/#organization"
ids.website()       // "https://example.com/#website"
ids.webpage()       // "https://example.com/#webpage"
ids.article()       // "https://example.com/#article"
ids.person()        // "https://example.com/#person"
// ... and more for all entity types

// Custom fragments
ids.custom('logo')           // "https://example.com/#logo"
ids.custom('contactpoint')   // "https://example.com/#contactpoint"

// Page-scoped IDs
ids.forPath('/about', 'webpage')  // "https://example.com/about#webpage"

// Cross-reference objects — works with all reference fields:
// publisher, author, isPartOf, location, breadcrumb, mainEntity,
// seller, hiringOrganization, department, containedInPlace, and more
ids.ref('organization')  // { "@id": "https://example.com/#organization" }

See the Core API docs for the full list of methods.


Output

const graph = createGraph([org, article]);

// Single <script> tag:
graph.toScript();

// JSON-LD object (for React, etc.):
graph.toJsonLd();
// → { "@context": "https://schema.org", "@graph": [...] }

// Plain array of schema objects:
graph.toArray();
// → [{ "@type": "Organization", ... }, { "@type": "Article", ... }]

Fluent API

Build a graph incrementally:

import { createGraph, createOrganization, createWebSite } from 'schemaorg-kit';

const graph = createGraph([])
  .add(createOrganization({ name: 'Acme', url: 'https://acme.tech' }))
  .add(createWebSite({ name: 'Acme', url: 'https://acme.tech' }))
  .add({ '@type': 'WebPage', name: 'Home', url: 'https://acme.tech' });  // plain objects work too