Astro integration for automatic structured data (JSON-LD) generation. Supports Articles, FAQs, Products, Breadcrumbs, Local Businesses, Events, Organizations, and more — with full TypeScript and Zod validation.
npm install @casoon/astro-structured-dataAdd the integration to your astro.config.mjs:
import { defineConfig } from 'astro/config';
import structuredData from '@casoon/astro-structured-data';
export default defineConfig({
site: 'https://example.com', // used automatically by the integration
integrations: [
structuredData(),
],
});If you don't set site in your Astro config, pass siteUrl explicitly:
structuredData({ siteUrl: 'https://example.com' })| Option | Type | Required | Description |
|---|---|---|---|
siteUrl |
string |
No | Absolute base URL — falls back to Astro's site config |
useGraph |
boolean |
No | Wrap all schemas in a @graph array |
defaultLocalBusiness |
LocalBusiness |
No | Site-wide local business defaults merged into LocalBusinessSchema |
defaultArticlePublisher |
Organization |
No | Default publisher for ArticleSchema and OrganizationSchema |
defaultBrand |
Brand | string |
No | Default brand for ProductSchema |
defaultShippingDetails |
OfferShippingDetails |
No | Default shipping details for ProductSchema |
defaultReturnPolicy |
MerchantReturnPolicy |
No | Default return policy for ProductSchema |
Import components from @casoon/astro-structured-data/components:
---
import { ArticleSchema, FAQSchema } from '@casoon/astro-structured-data/components';
---Docs: schema.org/Article · Google: Article
<ArticleSchema
title="My Article"
description="Article description"
datePublished="2024-01-01"
authorName="Jane Doe"
imageUrl="https://example.com/image.jpg"
/>| Prop | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Article headline |
description |
string |
Yes | Article description |
datePublished |
string | Date |
Yes | Publication date |
dateModified |
string | Date |
No | Last modified date |
authorName |
string | string[] |
Yes | Author name(s) |
authorType |
'Person' | 'Organization' |
No | Default: 'Person' |
imageUrl |
string | string[] |
Yes | Article image URL(s) |
publisherName |
string |
No | Publisher name (falls back to defaultArticlePublisher) |
publisherLogo |
string |
No | Publisher logo URL (falls back to defaultArticlePublisher) |
Docs: schema.org/FAQPage · Google: FAQ
<FAQSchema
questions={[
{ question: 'What is this?', answer: 'An Astro integration.' },
{ question: 'How does it work?', answer: 'It injects JSON-LD.' },
]}
/>| Prop | Type | Required | Description |
|---|---|---|---|
questions |
{ question: string; answer: string }[] |
Yes | List of Q&A pairs |
Docs: schema.org/Product · Google: Product
<ProductSchema
name="Super Gadget"
description="The best gadget ever"
imageUrl="https://example.com/gadget.jpg"
price={29.99}
priceCurrency="EUR"
availability="InStock"
sku="SG-001"
ratingValue={4.5}
reviewCount={128}
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Product name |
description |
string |
Yes | Product description |
imageUrl |
string | string[] |
Yes | Product image URL(s) |
price |
string | number |
No | Price (simple offer) |
priceCurrency |
string |
No | ISO 4217 currency code, e.g. 'EUR' |
availability |
'InStock' | 'OutOfStock' | 'PreOrder' | 'OnlineOnly' |
No | Offer availability |
offers |
Offer | Offer[] |
No | Full offer object(s) for advanced use cases |
priceRange |
PriceRange |
No | Price range for variable pricing |
brand |
string | Brand |
No | Brand name or object (falls back to defaultBrand) |
sku |
string |
No | Stock keeping unit |
gtin |
string |
No | GTIN barcode |
ratingValue |
number |
No | Aggregate rating (0–5) |
reviewCount |
number |
No | Number of reviews |
reviews |
ReviewItem[] |
No | Individual review objects |
shippingDetails |
object |
No | Shipping details (falls back to defaultShippingDetails) |
returnPolicy |
object |
No | Return policy (falls back to defaultReturnPolicy) |
Docs: schema.org/LocalBusiness · Google: Local Business
<LocalBusinessSchema
name="My Shop"
telephone="+49 30 1234567"
address={{
streetAddress: 'Hauptstraße 42',
addressLocality: 'Berlin',
postalCode: '10119',
addressCountry: 'DE',
}}
openingHours={['Mo-Fr 09:00-18:00', 'Sa 10:00-16:00']}
/>All props fall back to defaultLocalBusiness from the integration config.
| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Business name |
imageUrl |
string |
No | Business image URL |
telephone |
string |
No | Phone number |
priceRange |
string |
No | Price range indicator, e.g. '$$' |
address |
{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry } |
No | Postal address |
geo |
{ latitude: number; longitude: number } |
No | Geographic coordinates |
openingHours |
string[] |
No | Opening hours, e.g. ['Mo-Fr 09:00-18:00'] |
Docs: schema.org/BreadcrumbList · Google: Breadcrumb
<BreadcrumbSchema
items={[
{ name: 'Home', url: '/' },
{ name: 'Blog', url: '/blog' },
{ name: 'My Post', url: '/blog/my-post' },
]}
/>| Prop | Type | Required | Description |
|---|---|---|---|
items |
{ name: string; url: string }[] |
Yes | Ordered breadcrumb items |
Docs: schema.org/BreadcrumbList · Google: Breadcrumb
Generates breadcrumbs automatically from the current URL path. Segments are converted from kebab-case to title case by default.
<AutoBreadcrumbSchema />
<!-- With custom labels -->
<AutoBreadcrumbSchema
homeLabel="Start"
labels={{ blog: 'Articles', 'my-post': 'My Post' }}
/>| Prop | Type | Required | Description |
|---|---|---|---|
homeLabel |
string |
No | Label for the root segment. Default: 'Home' |
labels |
Record<string, string> |
No | Override labels for specific URL path segments |
Docs: schema.org/Event · Google: Event
<EventSchema
name="Tech Meetup Berlin"
startDate="2024-06-15T18:00:00"
locationName="Hub Berlin"
locationAddress={{
streetAddress: 'Alexanderplatz 1',
addressLocality: 'Berlin',
postalCode: '10178',
addressCountry: 'DE',
}}
attendanceMode="Offline"
status="Scheduled"
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Event name |
startDate |
string | Date |
Yes | Start date/time |
endDate |
string | Date |
No | End date/time |
description |
string |
No | Event description |
imageUrl |
string | string[] |
No | Event image URL(s) |
locationName |
string |
Yes | Venue name |
locationAddress |
{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry } |
Yes | Venue address |
attendanceMode |
'Offline' | 'Online' | 'Mixed' |
No | Default: 'Offline' |
status |
'Scheduled' | 'Cancelled' | 'Postponed' | 'Rescheduled' |
No | Default: 'Scheduled' |
price |
number | string |
No | Ticket price |
priceCurrency |
string |
No | ISO 4217 currency code |
availability |
'InStock' | 'OutOfStock' | 'PreOrder' | 'OnlineOnly' |
No | Ticket availability |
Docs: schema.org/Organization · Google: Organization
<OrganizationSchema
name="ACME Corp"
logoUrl="https://example.com/logo.png"
sameAs={['https://twitter.com/acme', 'https://linkedin.com/company/acme']}
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Organization name (falls back to defaultArticlePublisher.name) |
url |
string |
No | Organization URL (falls back to siteUrl) |
logoUrl |
string |
No | Logo URL (falls back to defaultArticlePublisher.logo) |
sameAs |
string[] |
No | Social profile / same-entity URLs |
telephone |
string |
No | Phone number |
email |
string |
No | Email address |
address |
{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry } |
No | Postal address |
Docs: schema.org/WebSite · Google: Sitelinks Searchbox
<WebSiteSchema name="My Site" />
<!-- With Sitelinks Searchbox -->
<WebSiteSchema name="My Site" searchQueryInput="q" />| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Site name |
url |
string |
No | Site URL (falls back to siteUrl) |
searchQueryInput |
string |
No | URL query param name to enable Sitelinks Searchbox, e.g. 'q' |
Docs: schema.org/ProfilePage · Google: Profile Page
<ProfilePageSchema
name="Jane Doe"
description="Software engineer and writer"
imageUrl="https://example.com/jane.jpg"
sameAs={['https://github.com/janedoe']}
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Person name |
description |
string |
No | Short bio |
imageUrl |
string |
No | Profile image URL |
sameAs |
string[] |
No | Social profile URLs |
publishingPrinciples |
string |
No | URL to editorial / publishing guidelines |
Docs: schema.org/CollectionPage
For product listing / archive pages.
<CollectionPageSchema
name="All Products"
description="Browse our full product catalogue"
products={[
{ name: 'Widget A', url: '/products/widget-a', imageUrl: '...', price: 9.99, priceCurrency: 'EUR' },
{ name: 'Widget B', url: '/products/widget-b' },
]}
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Page / collection name |
description |
string |
Yes | Collection description |
products |
{ name: string; url: string; imageUrl?: string; price?: number | string; priceCurrency?: string }[] |
Yes | List of products |
Docs: schema.org/JobPosting · Google: Job Posting
<JobPostingSchema
title="Senior Developer"
description="<p>We're looking for a senior developer...</p>"
datePosted="2024-06-01"
jobLocation={{
streetAddress: 'Hauptstraße 1',
addressLocality: 'Berlin',
postalCode: '10115',
addressCountry: 'DE',
}}
employmentType="FULL_TIME"
baseSalary={{ value: 80000, currency: 'EUR', unit: 'YEAR' }}
/>| Prop | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Job title |
description |
string |
Yes | Job description (HTML accepted by Google) |
datePosted |
string |
Yes | ISO date the posting was published |
validThrough |
string |
No | ISO date the posting expires |
employmentType |
'FULL_TIME' | 'PART_TIME' | 'CONTRACTOR' | 'TEMPORARY' | 'INTERN' | 'VOLUNTEER' | 'OTHER' |
No | Default: 'FULL_TIME' |
hiringOrganizationName |
string |
No | Hiring company name |
hiringOrganizationUrl |
string |
No | Hiring company URL |
hiringOrganizationLogo |
string |
No | Hiring company logo URL |
jobLocation |
{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry } |
Yes | Job location |
baseSalary |
{ value: number | string; currency: string; unit?: 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR' } |
No | Salary details |
Docs: schema.org/SoftwareApplication · Google: Software App
<SoftwareAppSchema
name="My App"
operatingSystem="Web"
applicationCategory="BusinessApplication"
price={0}
priceCurrency="EUR"
ratingValue={4.8}
reviewCount={320}
/>| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | App name |
operatingSystem |
string |
No | e.g. 'Web', 'Windows, macOS'. Default: 'Web' |
applicationCategory |
string |
No | e.g. 'BusinessApplication', 'Game'. Default: 'DeveloperApplication' |
price |
string | number |
No | Price (use 0 for free apps) |
priceCurrency |
string |
No | ISO 4217 currency code |
ratingValue |
number |
No | Aggregate rating (0–5) |
reviewCount |
number |
No | Number of reviews |
Renders all schemas registered via useGraph: true as a single @graph block. Place once in your base layout.
---
import { SchemaGraph } from '@casoon/astro-structured-data/components';
---
<SchemaGraph />No props. Reads from Astro.locals.structuredDataGraph populated by the other components when useGraph is enabled.
Reusable Zod schemas for all types — useful in Content Collections or form validation:
import {
articleZodSchema,
faqZodSchema,
productZodSchema,
localBusinessZodSchema,
eventZodSchema,
} from '@casoon/astro-structured-data/zod';MIT