{"id":50820861,"url":"https://github.com/casoon/astro-structured-data","last_synced_at":"2026-06-13T13:36:10.235Z","repository":{"id":361730416,"uuid":"1255580887","full_name":"casoon/astro-structured-data","owner":"casoon","description":"Astro integration for automatic JSON-LD structured data generation","archived":false,"fork":false,"pushed_at":"2026-06-12T08:45:04.000Z","size":351,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T13:36:09.177Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/casoon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T01:38:45.000Z","updated_at":"2026-06-12T08:45:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/casoon/astro-structured-data","commit_stats":null,"previous_names":["casoon/astro-structured-data"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/casoon/astro-structured-data","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casoon%2Fastro-structured-data","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casoon%2Fastro-structured-data/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casoon%2Fastro-structured-data/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casoon%2Fastro-structured-data/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/casoon","download_url":"https://codeload.github.com/casoon/astro-structured-data/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casoon%2Fastro-structured-data/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34286975,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-13T13:36:09.520Z","updated_at":"2026-06-13T13:36:10.229Z","avatar_url":"https://github.com/casoon.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @casoon/astro-structured-data\n\nAstro integration for automatic structured data (JSON-LD) generation. Supports Articles, FAQs, Products, Recipes, Videos, Breadcrumbs, Local Businesses, Events, Organizations, and more — with full TypeScript and Zod validation.\n\n[![npm version](https://img.shields.io/npm/v/@casoon/astro-structured-data.svg)](https://www.npmjs.com/package/@casoon/astro-structured-data)\n[![GitHub repository](https://img.shields.io/badge/github-repo-black.svg?logo=github)](https://github.com/casoon/astro-structured-data)\n[![Website](https://img.shields.io/badge/website-live-brightgreen.svg)](https://astro-structured-data.casoon.dev/)\n\n[Landing Page](https://astro-structured-data.casoon.dev/) | [GitHub Repository](https://github.com/casoon/astro-structured-data) | [npm Package](https://www.npmjs.com/package/@casoon/astro-structured-data)\n\n## Installation\n\n```bash\nnpm install @casoon/astro-structured-data\n```\n\n## Setup\n\nAdd the integration to your `astro.config.mjs`:\n\n```js\nimport { defineConfig } from 'astro/config';\nimport structuredData from '@casoon/astro-structured-data';\n\nexport default defineConfig({\n  site: 'https://example.com', // used automatically by the integration\n  integrations: [\n    structuredData({\n      generateMeta: true,\n      siteName: 'My Awesome Website',\n      locale: 'de_DE',\n      twitterSite: '@mywebsite',\n    }),\n  ],\n});\n```\n\nIf you don't set `site` in your Astro config, pass `siteUrl` explicitly:\n\n```js\nstructuredData({ \n  siteUrl: 'https://example.com',\n  generateMeta: true,\n})\n```\n\n## Configuration\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `siteUrl` | `string` | — | Absolute base URL — falls back to Astro's `site` config |\n| `useGraph` | `boolean` | `false` | Wrap all schemas in a `@graph` array |\n| `generateMeta` | `boolean` | `false` | Generate standard HTML head meta tags (og:*, twitter:*, canonical, etc.) from schemas |\n| `siteName` | `string` | — | Global site name used for `og:site_name` |\n| `locale` | `string` | — | Global locale used for `og:locale` (e.g. `de_DE`) |\n| `twitterSite` | `string` | — | Twitter site handle used for `twitter:site` (e.g. `@my_site`) |\n| `twitterCreator` | `string` | — | Fallback Twitter creator handle used for `twitter:creator` (e.g. `@author`) |\n| `warnOnMissingRecommended` | `boolean` | `true` | Log warnings during build when recommended schema.org fields are absent |\n| `defaultLocalBusiness` | `LocalBusiness` | — | Site-wide local business defaults merged into `LocalBusinessSchema` |\n| `defaultArticlePublisher` | `Organization` | — | Default publisher for `ArticleSchema` and `OrganizationSchema` |\n| `defaultBrand` | `Brand \\| string` | — | Default brand for `ProductSchema` |\n| `defaultShippingDetails` | `OfferShippingDetails` | — | Default shipping details for `ProductSchema` |\n| `defaultReturnPolicy` | `MerchantReturnPolicy` | — | Default return policy for `ProductSchema` |\n\n## Dev Toolbar\n\nThe integration registers an Astro Dev Toolbar panel (visible only in `astro dev`) that shows every `\u003cscript type=\"application/ld+json\"\u003e` block found on the current page.\n\nFor each schema it shows:\n\n- **Rich Result Preview** — a Google-style mockup rendered directly in the toolbar:\n  - *Article / BlogPosting / NewsArticle* — thumbnail, author, date snippet\n  - *FAQPage* — interactive accordion (click to expand answers)\n  - *Product* — star rating, price, in-stock badge\n  - *BreadcrumbList* — breadcrumb trail in Google style\n  - *Event* — calendar date box, location, time\n  - *JobPosting* — job card with location, employment type, salary badges\n  - *LocalBusiness* — phone, address, opening hours\n  - *SoftwareApplication* — star rating, OS, category\n- **Validation warnings** — required and recommended field checks per type\n- **📋 Copy JSON-LD** — copies the full JSON-LD to the clipboard\n- **🔍 Test on Schema.org** — opens `validator.schema.org` in a new tab\n- **Show JSON-LD Raw** — toggle the raw JSON for inspection\n\n## Automated SEO \u0026 Sitemap Integration\n\nWhen `generateMeta: true` is enabled, the integration automatically derives and renders corresponding `\u003cmeta\u003e` and `\u003clink\u003e` elements inside the page `\u003chead\u003e` during render time:\n* **Canonical**: `\u003clink rel=\"canonical\" href=\"...\"\u003e`\n* **Description**: `\u003cmeta name=\"description\" content=\"...\"\u003e`\n* **Robots**: `\u003cmeta name=\"robots\" content=\"...\"\u003e` (derived from `item.robots` or `item.noindex` / `item.nofollow`)\n* **Author**: `\u003cmeta name=\"author\" content=\"...\"\u003e` (derived from schema `item.author`)\n* **Reading Time**: `\u003cmeta name=\"reading-time\" content=\"...\"\u003e` (non-standard; derived from `item.readingTime` or parsed from ISO duration `item.timeRequired`)\n* **Alternates (hreflang)**: `\u003clink rel=\"alternate\" hreflang=\"...\" href=\"...\"\u003e` (extracted from `item.alternates` or schema translations)\n* **OpenGraph**: `og:title`, `og:description`, `og:image`, `og:image:width/height/type/alt`, `og:url`, `og:type`, `og:site_name`, `og:locale`, `article:published_time`, `article:modified_time`, `article:author`, `article:section`, `article:tag` (relative images are automatically resolved to absolute URLs using your config's `siteUrl`)\n* **Twitter Cards**: `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`, `twitter:image:alt`, `twitter:site`, `twitter:creator`\n\n### Sitemap Metadata Support\nYou can also define sitemap crawl properties directly in your components (e.g. `changefreq=\"weekly\" priority={0.8}`). \nThese properties are automatically encoded as data-attributes on the JSON-LD `\u003cscript\u003e` tag. Post-build sitemap generators like `@casoon/astro-site-files` can read these tags directly from the HTML to dynamically build/patch the sitemap entries, meaning you don't need to duplicate sitemap logic in your configs. This feature is completely decoupled and will fall back gracefully to the sitemap defaults if `@casoon/astro-site-files` is not installed or configured.\n\n## Components\n\nImport components from `@casoon/astro-structured-data/components`:\n\n```astro\n---\nimport { ArticleSchema, FAQSchema } from '@casoon/astro-structured-data/components';\n---\n```\n\n---\n\n### ArticleSchema\n\nDocs: [schema.org/Article](https://schema.org/Article) · [Google: Article](https://developers.google.com/search/docs/appearance/structured-data/article)\n\n```astro\n\u003cArticleSchema\n  title=\"My Article\"\n  description=\"Article description\"\n  datePublished=\"2024-01-01\"\n  authorName=\"Jane Doe\"\n  imageUrl=\"https://example.com/image.jpg\"\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `title` | `string` | Yes | Article headline |\n| `description` | `string` | Yes | Article description |\n| `datePublished` | `string \\| Date` | Yes | Publication date |\n| `dateModified` | `string \\| Date` | No | Last modified date |\n| `authorName` | `string \\| string[]` | Yes | Author name(s) |\n| `authorType` | `'Person' \\| 'Organization'` | No | Default: `'Person'` |\n| `authorUrl` | `string` | No | Author profile URL |\n| `authorId` | `string` | No | Author `@id` for linked data |\n| `imageUrl` | `string` | No | Article image URL |\n| `imageWidth` | `number` | No | Image width in pixels |\n| `imageHeight` | `number` | No | Image height in pixels |\n| `imageFormat` | `string` | No | Image MIME type, e.g. `'image/jpeg'` |\n| `imageCaption` | `string` | No | Image caption |\n| `publisherName` | `string` | No | Publisher name (falls back to `defaultArticlePublisher`) |\n| `publisherLogo` | `string` | No | Publisher logo URL (falls back to `defaultArticlePublisher`) |\n| `schemaType` | `'Article' \\| 'BlogPosting' \\| 'NewsArticle'` | No | Default: `'BlogPosting'` |\n| `inLanguage` | `string` | No | Content language, e.g. `'de'` |\n| `articleSection` | `string` | No | Section or category name |\n| `keywords` | `string \\| string[]` | No | Keywords |\n| `wordCount` | `number` | No | Word count |\n| `readingTimeMinutes` | `number` | No | Reading time in minutes (encoded as `timeRequired`) |\n| `isAccessibleForFree` | `boolean` | No | Default: `true` |\n| `isPartOfHeadline` | `string` | No | Parent series headline (for `isPartOf`) |\n| `isPartOfUrl` | `string` | No | Parent series URL |\n| `seriesPosition` | `number` | No | Position within the series |\n| `hasPart` | `{ headline: string; url: string; position?: number }[]` | No | Child articles in a series |\n\n---\n\n### FAQSchema\n\nDocs: [schema.org/FAQPage](https://schema.org/FAQPage) · [Google: FAQ](https://developers.google.com/search/docs/appearance/structured-data/faqpage)\n\n```astro\n\u003cFAQSchema\n  questions={[\n    { question: 'What is this?', answer: 'An Astro integration.' },\n    { question: 'How does it work?', answer: 'It injects JSON-LD.' },\n  ]}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `questions` | `{ question: string; answer: string }[]` | Yes | List of Q\u0026A pairs |\n\n---\n\n### ProductSchema\n\nDocs: [schema.org/Product](https://schema.org/Product) · [Google: Product](https://developers.google.com/search/docs/appearance/structured-data/product)\n\n```astro\n\u003cProductSchema\n  name=\"Super Gadget\"\n  description=\"The best gadget ever\"\n  imageUrl=\"https://example.com/gadget.jpg\"\n  price={29.99}\n  priceCurrency=\"EUR\"\n  availability=\"InStock\"\n  sku=\"SG-001\"\n  ratingValue={4.5}\n  reviewCount={128}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Product name |\n| `description` | `string` | Yes | Product description |\n| `imageUrl` | `string \\| string[]` | Yes | Product image URL(s) |\n| `price` | `string \\| number` | No | Price (simple offer) |\n| `priceCurrency` | `string` | No | ISO 4217 currency code, e.g. `'EUR'` |\n| `availability` | `'InStock' \\| 'OutOfStock' \\| 'PreOrder' \\| 'OnlineOnly'` | No | Offer availability |\n| `offers` | `Offer \\| Offer[]` | No | Full offer object(s) for advanced use cases |\n| `priceRange` | `PriceRange` | No | Price range for variable pricing |\n| `brand` | `string \\| Brand` | No | Brand name or object (falls back to `defaultBrand`) |\n| `sku` | `string` | No | Stock keeping unit |\n| `gtin` | `string` | No | GTIN barcode |\n| `ratingValue` | `number` | No | Aggregate rating (0–5) |\n| `reviewCount` | `number` | No | Number of reviews |\n| `reviews` | `ReviewItem[]` | No | Individual review objects |\n| `shippingDetails` | `object` | No | Shipping details (falls back to `defaultShippingDetails`) |\n| `returnPolicy` | `object` | No | Return policy (falls back to `defaultReturnPolicy`) |\n\n---\n\n### LocalBusinessSchema\n\nDocs: [schema.org/LocalBusiness](https://schema.org/LocalBusiness) · [Google: Local Business](https://developers.google.com/search/docs/appearance/structured-data/local-business)\n\n```astro\n\u003cLocalBusinessSchema\n  name=\"My Shop\"\n  telephone=\"+49 30 1234567\"\n  address={{\n    streetAddress: 'Hauptstraße 42',\n    addressLocality: 'Berlin',\n    postalCode: '10119',\n    addressCountry: 'DE',\n  }}\n  openingHours={['Mo-Fr 09:00-18:00', 'Sa 10:00-16:00']}\n/\u003e\n```\n\nAll props fall back to `defaultLocalBusiness` from the integration config.\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | No | Business name |\n| `url` | `string` | No | Business website URL |\n| `description` | `string` | No | Short business description |\n| `imageUrl` | `string` | No | Business image URL |\n| `telephone` | `string` | No | Phone number |\n| `email` | `string` | No | Email address |\n| `priceRange` | `string` | No | Price range indicator, e.g. `'$$'` |\n| `address` | `{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry }` | No | Postal address |\n| `geo` | `{ latitude: number; longitude: number }` | No | Geographic coordinates |\n| `openingHours` | `string[]` | No | Opening hours, e.g. `['Mo-Fr 09:00-18:00']` |\n| `sameAs` | `string[]` | No | Social profile / same-entity URLs (e.g. Google Business, Facebook) |\n\n---\n\n### BreadcrumbSchema\n\nDocs: [schema.org/BreadcrumbList](https://schema.org/BreadcrumbList) · [Google: Breadcrumb](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb)\n\n```astro\n\u003cBreadcrumbSchema\n  items={[\n    { name: 'Home', url: '/' },\n    { name: 'Blog', url: '/blog' },\n    { name: 'My Post', url: '/blog/my-post' },\n  ]}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `items` | `{ name: string; url: string }[]` | Yes | Ordered breadcrumb items |\n\n---\n\n### AutoBreadcrumbSchema\n\nDocs: [schema.org/BreadcrumbList](https://schema.org/BreadcrumbList) · [Google: Breadcrumb](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb)\n\nGenerates breadcrumbs automatically from the current URL path. Segments are converted from kebab-case to title case by default.\n\n```astro\n\u003cAutoBreadcrumbSchema /\u003e\n\n\u003c!-- With custom labels --\u003e\n\u003cAutoBreadcrumbSchema\n  homeLabel=\"Start\"\n  labels={{ blog: 'Articles', 'my-post': 'My Post' }}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `homeLabel` | `string` | No | Label for the root segment. Default: `'Home'` |\n| `labels` | `Record\u003cstring, string\u003e` | No | Override labels for specific URL path segments |\n| `ignoreSegments` | `string[]` | No | URL segments to skip — useful for language prefixes like `['de', 'en']` |\n| `prependBreadcrumbs` | `{ name: string; url: string }[]` | No | Breadcrumbs inserted after Home, before auto-generated segments |\n| `appendBreadcrumbs` | `{ name: string; url: string }[]` | No | Breadcrumbs appended after all auto-generated segments |\n\n---\n\n### EventSchema\n\nDocs: [schema.org/Event](https://schema.org/Event) · [Google: Event](https://developers.google.com/search/docs/appearance/structured-data/event)\n\n```astro\n\u003cEventSchema\n  name=\"Tech Meetup Berlin\"\n  startDate=\"2024-06-15T18:00:00\"\n  locationName=\"Hub Berlin\"\n  locationAddress={{\n    streetAddress: 'Alexanderplatz 1',\n    addressLocality: 'Berlin',\n    postalCode: '10178',\n    addressCountry: 'DE',\n  }}\n  attendanceMode=\"Offline\"\n  status=\"Scheduled\"\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Event name |\n| `startDate` | `string \\| Date` | Yes | Start date/time |\n| `endDate` | `string \\| Date` | No | End date/time |\n| `description` | `string` | No | Event description |\n| `imageUrl` | `string \\| string[]` | No | Event image URL(s) |\n| `locationName` | `string` | Yes | Venue name |\n| `locationAddress` | `{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry }` | Yes | Venue address |\n| `attendanceMode` | `'Offline' \\| 'Online' \\| 'Mixed'` | No | Default: `'Offline'` |\n| `status` | `'Scheduled' \\| 'Cancelled' \\| 'Postponed' \\| 'Rescheduled'` | No | Default: `'Scheduled'` |\n| `url` | `string` | No | Event page URL |\n| `price` | `number \\| string` | No | Ticket price |\n| `priceCurrency` | `string` | No | ISO 4217 currency code |\n| `availability` | `'InStock' \\| 'OutOfStock' \\| 'PreOrder' \\| 'OnlineOnly'` | No | Ticket availability |\n| `organizer` | `{ name: string; url?: string }` | No | Organizing entity (recommended by Google) |\n| `performer` | `{ name: string; url?: string }` | No | Performer or speaker at the event |\n\n---\n\n### OrganizationSchema\n\nDocs: [schema.org/Organization](https://schema.org/Organization) · [Google: Organization](https://developers.google.com/search/docs/appearance/structured-data/organization)\n\n```astro\n\u003cOrganizationSchema\n  name=\"ACME Corp\"\n  logoUrl=\"https://example.com/logo.png\"\n  sameAs={['https://twitter.com/acme', 'https://linkedin.com/company/acme']}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | No | Organization name (falls back to `defaultArticlePublisher.name`) |\n| `url` | `string` | No | Organization URL (falls back to `siteUrl`) |\n| `logoUrl` | `string` | No | Logo URL (falls back to `defaultArticlePublisher.logo`) |\n| `sameAs` | `string[]` | No | Social profile / same-entity URLs |\n| `telephone` | `string` | No | Phone number |\n| `email` | `string` | No | Email address |\n| `address` | `{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry }` | No | Postal address |\n\n---\n\n### WebSiteSchema\n\nDocs: [schema.org/WebSite](https://schema.org/WebSite) · [Google: Sitelinks Searchbox](https://developers.google.com/search/docs/appearance/structured-data/sitelinks-searchbox)\n\n```astro\n\u003cWebSiteSchema name=\"My Site\" /\u003e\n\n\u003c!-- With Sitelinks Searchbox --\u003e\n\u003cWebSiteSchema name=\"My Site\" searchQueryInput=\"q\" /\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Site name |\n| `url` | `string` | No | Site URL (falls back to `siteUrl`) |\n| `searchQueryInput` | `string` | No | URL query param name to enable Sitelinks Searchbox, e.g. `'q'` |\n\n---\n\n### WebPageSchema\n\nDocs: [schema.org/WebPage](https://schema.org/WebPage) · [Google: WebPage](https://developers.google.com/search/docs/appearance/structured-data/webpage)\n\nGeneric page schema — use for landing pages, legal pages, or any page that doesn't fit a more specific type.\n\n```astro\n\u003cWebPageSchema\n  title=\"About Us\"\n  description=\"Learn more about our company.\"\n  inLanguage=\"en\"\n  dateModified=\"2024-06-01\"\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `title` | `string` | Yes | Page title |\n| `description` | `string` | No | Page description |\n| `url` | `string` | No | Canonical URL (falls back to current page URL) |\n| `inLanguage` | `string` | No | Content language, e.g. `'de'` |\n| `datePublished` | `string` | No | Publication date |\n| `dateModified` | `string` | No | Last modified date |\n| `isAccessibleForFree` | `boolean` | No | Default: `true` |\n| `image` | `string` | No | Page image URL |\n| `imageWidth` | `number` | No | Image width in pixels |\n| `imageHeight` | `number` | No | Image height in pixels |\n| `imageFormat` | `string` | No | Image MIME type |\n| `imageCaption` | `string` | No | Image caption |\n| `author` | `{ name: string; url?: string; id?: string; type?: string }` | No | Page author |\n| `publisher` | `{ name: string; url?: string; logo?: string }` | No | Publisher (falls back to `defaultArticlePublisher`) |\n| `robots` | `string` | No | Robots directive, e.g. `'noindex'` |\n| `alternates` | `{ href: string; hreflang: string }[]` | No | Alternate language versions |\n\n---\n\n### ProfilePageSchema\n\nDocs: [schema.org/ProfilePage](https://schema.org/ProfilePage) · [Google: Profile Page](https://developers.google.com/search/docs/appearance/structured-data/profile-page)\n\n```astro\n\u003cProfilePageSchema\n  name=\"Jane Doe\"\n  description=\"Software engineer and writer\"\n  imageUrl=\"https://example.com/jane.jpg\"\n  sameAs={['https://github.com/janedoe']}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Person name |\n| `description` | `string` | No | Short bio |\n| `imageUrl` | `string` | No | Profile image URL |\n| `sameAs` | `string[]` | No | Social profile URLs |\n| `publishingPrinciples` | `string` | No | URL to editorial / publishing guidelines |\n\n---\n\n### CollectionPageSchema\n\nDocs: [schema.org/CollectionPage](https://schema.org/CollectionPage)\n\nFor product listing / archive pages.\n\n```astro\n\u003cCollectionPageSchema\n  name=\"All Products\"\n  description=\"Browse our full product catalogue\"\n  products={[\n    { name: 'Widget A', url: '/products/widget-a', imageUrl: '...', price: 9.99, priceCurrency: 'EUR' },\n    { name: 'Widget B', url: '/products/widget-b' },\n  ]}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Page / collection name |\n| `description` | `string` | Yes | Collection description |\n| `products` | `{ name: string; url: string; imageUrl?: string; price?: number \\| string; priceCurrency?: string }[]` | Yes | List of products |\n\n---\n\n### JobPostingSchema\n\nDocs: [schema.org/JobPosting](https://schema.org/JobPosting) · [Google: Job Posting](https://developers.google.com/search/docs/appearance/structured-data/job-posting)\n\n```astro\n\u003cJobPostingSchema\n  title=\"Senior Developer\"\n  description=\"\u003cp\u003eWe're looking for a senior developer...\u003c/p\u003e\"\n  datePosted=\"2024-06-01\"\n  jobLocation={{\n    streetAddress: 'Hauptstraße 1',\n    addressLocality: 'Berlin',\n    postalCode: '10115',\n    addressCountry: 'DE',\n  }}\n  employmentType=\"FULL_TIME\"\n  baseSalary={{ value: 80000, currency: 'EUR', unit: 'YEAR' }}\n/\u003e\n```\n\nFor remote positions, use `jobLocationType` instead of (or in addition to) `jobLocation`:\n\n```astro\n\u003cJobPostingSchema\n  title=\"Remote Frontend Engineer\"\n  description=\"\u003cp\u003eFully remote position open worldwide.\u003c/p\u003e\"\n  datePosted=\"2024-06-01\"\n  jobLocationType=\"TELECOMMUTE\"\n  applicantLocationRequirements=\"DE\"\n  employmentType=\"FULL_TIME\"\n/\u003e\n```\n\n\u003e **Note:** Google requires either `jobLocation` or `applicantLocationRequirements` — at least one must be provided.\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `title` | `string` | Yes | Job title |\n| `description` | `string` | Yes | Job description (HTML accepted by Google) |\n| `datePosted` | `string` | Yes | ISO date the posting was published |\n| `validThrough` | `string` | No | ISO date the posting expires |\n| `employmentType` | `'FULL_TIME' \\| 'PART_TIME' \\| 'CONTRACTOR' \\| 'TEMPORARY' \\| 'INTERN' \\| 'VOLUNTEER' \\| 'OTHER'` | No | Default: `'FULL_TIME'` |\n| `hiringOrganizationName` | `string` | No | Hiring company name (falls back to `defaultArticlePublisher`) |\n| `hiringOrganizationUrl` | `string` | No | Hiring company URL |\n| `hiringOrganizationLogo` | `string` | No | Hiring company logo URL |\n| `jobLocation` | `{ streetAddress, addressLocality, addressRegion?, postalCode, addressCountry }` | No* | Job location — required unless `applicantLocationRequirements` is set |\n| `baseSalary` | `{ value: number \\| string; currency: string; unit?: 'HOUR' \\| 'DAY' \\| 'WEEK' \\| 'MONTH' \\| 'YEAR' }` | No | Salary details |\n| `identifier` | `{ name: string; value: string }` | No | Employer-specific job ID (e.g. `{ name: 'Acme', value: 'JR-12345' }`) |\n| `directApply` | `boolean` | No | Shows \"Apply on your site\" badge in Google rich results |\n| `jobLocationType` | `'TELECOMMUTE'` | No | Set for remote positions |\n| `applicantLocationRequirements` | `string \\| string[]` | No* | Country/region where remote applicants must be located — required unless `jobLocation` is set |\n\n---\n\n### SoftwareAppSchema\n\nDocs: [schema.org/SoftwareApplication](https://schema.org/SoftwareApplication) · [Google: Software App](https://developers.google.com/search/docs/appearance/structured-data/software-app)\n\n```astro\n\u003cSoftwareAppSchema\n  name=\"My App\"\n  operatingSystem=\"Web\"\n  applicationCategory=\"BusinessApplication\"\n  price={0}\n  priceCurrency=\"EUR\"\n  ratingValue={4.8}\n  reviewCount={320}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | App name |\n| `description` | `string` | No | Short app description |\n| `url` | `string` | No | Link to the app or its landing page |\n| `operatingSystem` | `string` | No | e.g. `'Web'`, `'Windows, macOS'`. Default: `'Web'` |\n| `applicationCategory` | `string` | No | e.g. `'BusinessApplication'`, `'Game'`. Default: `'DeveloperApplication'` |\n| `price` | `string \\| number` | No | Price (use `0` for free apps) |\n| `priceCurrency` | `string` | No | ISO 4217 currency code |\n| `ratingValue` | `number` | No | Aggregate rating (0–5) |\n| `reviewCount` | `number` | No | Number of reviews |\n\n---\n\n### RecipeSchema\n\nDocs: [schema.org/Recipe](https://schema.org/Recipe) · [Google: Recipe](https://developers.google.com/search/docs/appearance/structured-data/recipe)\n\n```astro\n\u003cRecipeSchema\n  name=\"Classic Chocolate Chip Cookies\"\n  description=\"Crispy edges, chewy and soft in the center.\"\n  imageUrl=\"https://example.com/cookies.jpg\"\n  authorName=\"Baker Bob\"\n  prepTime=\"PT15M\"\n  cookTime=\"PT10M\"\n  recipeYield=\"12 cookies\"\n  recipeCategory=\"Dessert\"\n  recipeCuisine=\"American\"\n  calories={220}\n  ingredients={[\n    \"200g butter\",\n    \"150g brown sugar\",\n    \"2 eggs\",\n    \"300g flour\",\n    \"200g chocolate chips\"\n  ]}\n  instructions={[\n    { text: \"Preheat oven to 190°C.\", name: \"Preheat\" },\n    { text: \"Mix butter, sugar, and eggs. Fold in dry ingredients and chocolate chips.\", name: \"Make Dough\" },\n    { text: \"Scoop cookie balls onto sheet and bake for 10 minutes.\", name: \"Bake\" }\n  ]}\n  ratingValue={4.9}\n  reviewCount={45}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Recipe name |\n| `description` | `string` | Yes | Recipe description |\n| `imageUrl` | `string \\| string[]` | Yes | Image URL(s) |\n| `authorName` | `string \\| string[]` | Yes | Author name(s) |\n| `authorType` | `'Person' \\| 'Organization'` | No | Default: `'Person'` |\n| `prepTime` | `string` | No | ISO duration, e.g. `'PT15M'` |\n| `cookTime` | `string` | No | ISO duration, e.g. `'PT10M'` |\n| `totalTime` | `string` | No | ISO duration |\n| `recipeYield` | `string \\| number` | No | Servings or yield |\n| `recipeCategory` | `string` | No | e.g. `'Dessert'` |\n| `recipeCuisine` | `string` | No | e.g. `'American'` |\n| `calories` | `number \\| string` | No | Calorie count |\n| `ingredients` | `string[]` | Yes | List of ingredient descriptions |\n| `instructions` | `string[] \\| InstructionStep[]` | Yes | Step-by-step instructions |\n| `ratingValue` | `number` | No | Rating value (0-5) |\n| `reviewCount` | `number` | No | Number of ratings |\n| `datePublished` | `string \\| Date` | No | Publication date |\n\n---\n\n### VideoSchema\n\nDocs: [schema.org/VideoObject](https://schema.org/VideoObject) · [Google: Video](https://developers.google.com/search/docs/appearance/structured-data/video)\n\n```astro\n\u003cVideoSchema\n  name=\"Astro v6 Server Islands Tutorial\"\n  description=\"Learn how to use server islands in Astro v6.\"\n  thumbnailUrl=\"https://example.com/thumb.jpg\"\n  uploadDate=\"2026-06-01\"\n  duration=\"PT8M45S\"\n  contentUrl=\"https://example.com/video.mp4\"\n  interactionCount={15420}\n/\u003e\n```\n\n| Prop | Type | Required | Description |\n|---|---|---|---|\n| `name` | `string` | Yes | Video title |\n| `description` | `string` | Yes | Video description |\n| `thumbnailUrl` | `string \\| string[]` | Yes | Thumbnail image URL(s) |\n| `uploadDate` | `string \\| Date` | Yes | Video upload date |\n| `duration` | `string` | No | ISO duration, e.g. `'PT8M45S'` |\n| `contentUrl` | `string` | No* | URL to the actual video file — at least one of `contentUrl` or `embedUrl` required for indexing |\n| `embedUrl` | `string` | No* | URL to the embeddable video player — at least one of `contentUrl` or `embedUrl` required for indexing |\n| `interactionCount` | `number \\| string` | No | Total view counts |\n| `expires` | `string \\| Date` | No | Expiration date |\n| `publisher` | `{ name: string; logoUrl?: string }` | No | Publishing organization (recommended by Google) |\n\n---\n\n### SchemaGraph\n\nRenders all schemas registered via `useGraph: true` as a single `@graph` block. Place once in your base layout.\n\n```astro\n---\nimport { SchemaGraph } from '@casoon/astro-structured-data/components';\n---\n\u003cSchemaGraph /\u003e\n```\n\nNo props. Reads from `Astro.locals.structuredDataGraph` populated by the other components when `useGraph` is enabled.\n\n---\n\n## Zod schemas\n\nAll components ship with a matching Zod schema, exported from `@casoon/astro-structured-data/zod`. Use them in Content Collections, form validation, or any runtime validation.\n\n```ts\nimport {\n  articleZodSchema,\n  faqZodSchema,\n  productZodSchema,\n  localBusinessZodSchema,\n  eventZodSchema,\n  organizationZodSchema,\n  webPageZodSchema,\n  webSiteZodSchema,\n  profilePageZodSchema,\n  jobPostingZodSchema,\n  softwareAppZodSchema,\n  collectionPageZodSchema,\n  breadcrumbZodSchema,\n  autoBreadcrumbZodSchema,\n  recipeZodSchema,\n  videoZodSchema,\n} from '@casoon/astro-structured-data/zod';\n```\n\n### validateRecommended\n\nCheck any schema object for missing recommended fields (matching what the build warning reports):\n\n```ts\nimport { validateRecommended } from '@casoon/astro-structured-data/zod';\nimport type { SchemaType, RecommendedWarning } from '@casoon/astro-structured-data/zod';\n\nconst warnings: RecommendedWarning[] = validateRecommended('Organization', {\n  name: 'ACME Corp',\n  url: 'https://acme.com',\n});\n// → [{ field: 'sameAs', message: 'Organization should include \"sameAs\" ...' }, ...]\n```\n\nThe `type` argument uses schema.org `@type` names: `'Article'`, `'BlogPosting'`, `'NewsArticle'`, `'FAQPage'`, `'Product'`, `'LocalBusiness'`, `'Event'`, `'Organization'`, `'WebPage'`, `'WebSite'`, `'ProfilePage'`, `'JobPosting'`, `'SoftwareApplication'`, `'CollectionPage'`, `'BreadcrumbList'`.\n\nFields marked as recommended are a subset of optional props that Google's Rich Results guidelines list as strongly beneficial — omitting them won't break validation but may reduce search result richness.\n\n## Utilities\n\n```ts\nimport { calculateReadingTime } from '@casoon/astro-structured-data/utils';\n```\n\n### calculateReadingTime\n\nCalculates word count and reading time from a plain-text or HTML string. Useful for populating `wordCount` and `readingTimeMinutes` on `ArticleSchema` from MDX content.\n\n```ts\nconst { wordCount, readingTimeMinutes, timeRequired } = calculateReadingTime(content);\n// timeRequired is ISO 8601 duration, e.g. 'PT4M'\n```\n\n```astro\n---\nimport { calculateReadingTime } from '@casoon/astro-structured-data/utils';\nimport { ArticleSchema } from '@casoon/astro-structured-data/components';\nimport { getEntry } from 'astro:content';\n\nconst post = await getEntry('blog', Astro.params.slug);\nconst { wordCount, readingTimeMinutes } = calculateReadingTime(post.body);\n---\n\u003cArticleSchema\n  title={post.data.title}\n  datePublished={post.data.date}\n  authorName={post.data.author}\n  wordCount={wordCount}\n  readingTimeMinutes={readingTimeMinutes}\n/\u003e\n```\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `text` | `string` | — | Plain text or HTML string |\n| `wordsPerMinute` | `number` | `200` | Reading speed used for the calculation |\n\nReturns `{ wordCount: number; readingTimeMinutes: number; timeRequired: string }`.\n\n### Build-time warnings\n\nWhen `warnOnMissingRecommended: true` (the default), the same recommended-field logic runs automatically after every build. The integration scans all output HTML for `\u003cscript type=\"application/ld+json\"\u003e` blocks and logs one warning per missing field per type:\n\n```\n[structured-data] Organization is missing recommended field \"sameAs\" — add it for richer search results.\n```\n\nTo disable:\n\n```js\nstructuredData({ warnOnMissingRecommended: false })\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasoon%2Fastro-structured-data","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcasoon%2Fastro-structured-data","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasoon%2Fastro-structured-data/lists"}