Outputs one connected JSON-LD graph per page covering Organization, LocalBusiness, WebSite, Article, BlogPosting, Service, WebPage, FAQPage and BreadcrumbList. Business details are stored in a global settings screen, and each configured collection gets a sidebar group to override or disable the type. A Next.js server component renders the script tag, and a builder function is available for non-React pages. Requires Payload 3.82.1 or later, Next.js 15 or 16 and React 19.
Schema.org structured data (JSON-LD) for Payload CMS: Organization, WebSite, Article, BlogPosting, Service, WebPage, FAQPage and BreadcrumbList, editable in the admin.
Installation
npm i @lavinedev/payload-plugin-structured-data0
Stars
0
Forks
0
Open Issues
Today
Last Updated
16
Weekly Downloads
v0.1.1
npm Version
98.9 KB
Unpacked Size
0
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
Trends appear after 7 days of data. Daily snapshots have not started yet.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1408920671-root)<a href="https://payloaddirectory.dev/plugins/1408920671-root"><img src="https://payloaddirectory.dev/badge/1408920671-root.svg" alt="Payload Directory"></a>README
A Payload CMS 3 plugin that outputs Schema.org structured data (JSON-LD) for your pages. Business details live in a settings screen in the admin. You choose a default type per collection in config. Editors can override it or switch it off per document.
It outputs nine types: Organization, LocalBusiness, WebSite, Article, BlogPosting, Service, WebPage, FAQPage and BreadcrumbList.
Each page gets one <script type="application/ld+json"> holding a connected @graph. Nodes link to each other by @id, for example the article's publisher points at <siteUrl>/#organization.
Requires Payload ^3.82.1, Next.js 15 or 16, React 19 and Node 20.9 or later.
pnpm add @lavinedev/payload-plugin-structured-data
Add the plugin to payload.config.ts:
import { structuredDataPlugin } from '@lavinedev/payload-plugin-structured-data'
export default buildConfig({
// ...
plugins: [
structuredDataPlugin({
siteUrl: process.env.NEXT_PUBLIC_SERVER_URL,
collections: {
posts: {
type: 'BlogPosting',
path: (doc) => `/blog/${doc.slug}`,
fields: { headline: 'title', image: 'meta.image', author: 'authors' },
},
pages: {
type: 'WebPage',
path: (doc) => (doc.slug === 'home' ? '/' : `/${doc.slug}`),
faq: { block: 'faq', items: 'faqs', question: 'question', answer: 'answer' },
},
},
}),
],
})
siteUrl is required. If it is empty, Payload throws a clear error when the config loads. Trailing slashes are removed.
The sidebar preview is an admin component, registered as @lavinedev/payload-plugin-structured-data/rsc#StructuredDataPreview. Payload finds it through the import map. After you add the plugin, run:
payload generate:importmap
Commit the updated importMap.js if your project tracks it. Without this step the preview does not appear in the admin.
| Key | Type | What it does |
|---|---|---|
siteUrl | string | Required. The public origin, such as https://example.com. Every URL in the output is built from it. |
collections | Record<slug, CollectionOptions> | The collections that get structured data. Each one gets a sidebar group in the admin. A slug that does not exist in your config throws. |
mediaCollection | string | Upload collection for the logo and image. Default media. |
imageSize | string | Image size name to prefer, such as og. Falls back to the original file. |
search | { urlTemplate: string } | Adds a SearchAction to WebSite, for example https://example.com/search?q={search_term_string}. Google retired the sitelinks search box in November 2024, so this no longer changes Google results. It is still valid Schema.org, and other tools and AI assistants can read it. |
disabled | boolean | Keeps the fields so your schema stays stable, but outputs nothing. |
| Key | Type | What it does |
|---|---|---|
type | 'Article' | 'BlogPosting' | 'Service' | 'WebPage', or (doc) => type | null | The collection's default type. A function lets one collection mix types, for example Service under /services/ and WebPage elsewhere. Return null to output no page node. |
path | (doc) => string | The document's public path, starting with /. Use / for the home page. The plugin joins it to siteUrl. |
fields | Partial<Record<PageField, string | string[]>> | Maps Schema.org properties to field paths. Dot paths work. A list means the first non-empty value wins, for example datePublished: ['completedAt', 'publishedAt', 'createdAt']. |
faq | { block, items, question, answer, blocksField? } | Builds a FAQPage from a blocks field. block is the block slug. items is the array field inside the block that holds the rows. question and answer are the field names in each row. blocksField is the blocks field on the document and defaults to layout. All matching blocks on a page merge into one FAQPage. Answers must be plain text (a text or textarea field). Rich text answers are skipped. Since 2023 Google shows FAQ rich results only for well-known government and health sites. The markup stays valid Schema.org, and other tools and AI assistants still read it. |
breadcrumbs | (doc) => { name, path }[] | Builds the breadcrumb trail yourself, for collections outside nested docs. Include Home as the first item. |
PageField is one of headline, description, image, datePublished, dateModified and author.
Most collections need few or no fields entries. These defaults follow Payload's website template:
| Property | Default path |
|---|---|
headline | title |
description | meta.description |
image | meta.image, then heroImage |
datePublished | publishedAt, then createdAt |
dateModified | updatedAt |
author | populatedAuthors |
In a Next.js server component, pass your Payload config and the document:
import config from '@payload-config'
import { StructuredData } from '@lavinedev/payload-plugin-structured-data/rsc'
export default async function PostPage({ post }) {
return (
<>
<StructuredData config={config} collection="posts" doc={post} />
{/* the rest of the page */}
</>
)
}
doc accepts any Payload document type, including your generated interfaces such as Post.
For routes that are not CMS documents, leave out collection and doc. You get the site nodes only (Organization or LocalBusiness, plus WebSite):
<StructuredData config={config} />
For a page that does not use React, call the builder yourself:
import {
buildStructuredData,
getPluginOptions,
serializeJsonLd,
} from '@lavinedev/payload-plugin-structured-data'
const options = getPluginOptions(payload.config)
if (!options) throw new Error('structuredDataPlugin is not configured')
const { graph, warnings } = buildStructuredData({ options, settings, collection: 'posts', doc })
const json = graph ? serializeJsonLd(graph) : null
getPluginOptions returns null when the plugin is not in your config, so check for that. settings is the structured-data-settings global, read with payload.findGlobal. Put json inside a <script type="application/ld+json"> tag.
Settings. The plugin adds a global with the slug structured-data-settings. In the admin it sits under Settings, then Structured data. It holds:
sameAs)A local business needs an address. Without one, the plugin outputs Organization and warns.
Settings changes do not revalidate pages that Next.js has already cached. Those pages show the new details after their next revalidation or your next deploy.
Sidebar group. Every configured collection gets a Structured data group in the sidebar:
BlogPosting with no headline is skipped, for example.Organization or WebSite node. Page nodes still render, without the publisher link.depth high enough when you load the document.posts/123: BlogPosting skipped, no headline (field "title" is empty). They are silent in production. The admin preview shows the same warnings.< as \u003c, so a value containing </script> cannot close the tag.The @id values are <siteUrl>/#organization and <siteUrl>/#website for the site, and <pageUrl>#webpage, #article, #service, #faq and #breadcrumb for a page.
The plugin adds a global and a sidebar group to your collections. On Postgres or SQLite, create and run a migration before you deploy:
payload migrate:create
payload migrate
MongoDB needs no migration.
Product, Event, Recipe and Review. Google's rules for these are strict and mistakes can cost you rich results. We will add them on demand.siteUrl per install. Multi-tenant support (@payloadcms/plugin-multi-tenant) is planned.MIT.
Made by Lavine Web & AI Solutions.
payload plugin seo jsonld
JSON-LD builders for Payload v3 shops: Product, BreadcrumbList, ItemList, WebSite and Organization
7333/wk1v3
payload seo analyzer
Offline on-page SEO analysis, admin dashboard, sitemaps and JSON-LD for Payload 3 and Next.js sites
70100/wk16v3
sitemap
Chunked, lazily cached XML sitemaps with hook-driven invalidation and robots.txt helpers
6123/wk15v3
advanced seo plugin
SEO meta group, global defaults, hreflang alternates, OG image generation and JSON-LD builders for Payload collections
3045/wk3v3
seo
Manage SEO metadata for your content from within the Payload admin panel
100169.7k/wk45.4kv3 v4
broken link checker
Scan Payload collection pages for broken links and report them in a dedicated collection
79411/wk114v3