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
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