@operationnation/sanity-plugin-schema-markup
This is a Sanity Studio v3 plugin.
What it is
The @operationnation/sanity-plugin-schema-markup
is a Sanity Studio plugin designed to simplify the process of generating Schema Markup, also known as structured data, for various types of content. This plugin is particularly useful for enhancing the structured data of your content, making it more accessible and understandable for search engines. You can read more about Schema Markup on the official website.
Key Features
-
Schema Markup Generation: The plugin allows you to easily generate schema markup for different types such as articles, recipes, reviews, and more.
-
Sanity Studio Integration: Seamlessly integrate schema markup generation into your Sanity Studio workflow, making it a part of your content creation process.
-
Dynamic Schema Markup: The plugin supports the dynamic creation of schema markup, enabling you to tailor the structured data based on your specific content and requirements.
-
React Component: Use the <SchemaScript />
component in the FE to inject a JSON-LD script into the <head>
of the document.
Installation
To get started, install the plugin using npm:
npm install @operationnation/sanity-plugin-schema-markup
Usage in Sanity Studio
Add it as a plugin in sanity.config.ts
(or .js):
import { defineConfig } from 'sanity';
import { schemaMarkup } from '@operationnation/sanity-plugin-schema-markup';
export default defineConfig({
plugins: [schemaMarkup()]
});
You can then add the schemaMarkup
field to any Sanity Document you want it to be in.
const myDocument = {
type: 'document',
name: 'myDocument',
fields: [
{
title: 'Schema Markup',
name: 'schemaMarkup',
type: 'schemaMarkup'
}
]
};
Usage on the front-end with React
Create a shared SchemaMarkup
component that can be used in any page in your React app.
import { SchemaScript } from '@operationnation/sanity-plugin-schema-markup';
import { Schema } from '../types/Types';
type Props = {
schema: Schema[];
};
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;
const SchemaMarkup = ({ schema }: Props) => {
return (
<SchemaScript schema={schema} projectId={projectId as string} dataset={dataset as string} />
);
};
export default SchemaMarkup;
Then you can use the SchemaMarkup
component where ever you are consuming the schemaMarkup
data from your Sanity Groq or GraphQL query. So lets say we have a Post page where we conduct our Groq query and the schemaMarkup
object is on a post object. We can simply pass schemaMarkup
directly into the <SchemaMarkup/>
component.
const post = await getPost(client, params.slug)
return (
<Container>
<section className="post">
{post.schemaMarkup && <SchemaMarkup schema={post.schemaMarkup} />}
{post.mainImage ? (
<Image
className="post__cover"
src={urlForImage(post.mainImage).url()}
height={231}
width={367}
alt=""
/>
) : (
<div className="post__cover--none" />
)}
<div className="post__container">
<h1 className="post__title">{post.title}</h1>
....
</Container>
)
Create schema markup script dynamically (optional)
Note: It's important to note that this dynamic creation of the schema markup script is optional. If your implementation doesn't require any custom mapping or configuration, you can skip this section and use the default schema patterns provided by the library. However, if you need to customize the schema markup based on your specific needs, this dynamic approach allows you to do so.
By creating the schema markup script dynamically, you have the flexibility to define the schema type, properties, and values based on your data and business logic. If your schema data constantly changing or you are trying to get the data from an api then you may need to create the schema script dynamically. By creating the schema markup script dynamically, you have the flexibility to define the schema type, properties, and values based on your data and business logic.
If your implementation requires any custom mapping or configuration, you can always create your schema markup how you see fit.
When creating schema markup script, you need to follow the schema type patterns:
Click here to see all schema patterns
import { SchemaScript, createImgUrl } from '@operationnation/sanity-plugin-schema-markup';
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;
const SchemaMarkup = ({ data })=> {
const { getImgUrl } = createImgUrl( projectId, dataset );
const articleSchemaType = {
type: 'Article',
headline: data.title,
description: data.excerpt,
datePublished: data.publishedAt,
dateModified: data.modifiedAt,
author: {
type: 'Person',
name: data.author.name,
url: data.domain
},
publisher: {
type: 'Organization',
name: data.companyName,
logo: data.logo
},
image: data.images.map(image=>getImgUrl(image.asset._ref))
};
return (
<SchemaScript schema={[articleSchemaType]} projectId={projectId as string} dataset={dataset as string} />
);
}
In the code snippet, the SchemaMarkup
component is defined, which takes in data
as props. Inside the component, a schema object of type Article
is created dynamically based on the provided data.
The createImgUrl
function from the library is used to generate the image URLs for the image
property of the schema object.
All schema type patterns
Article
Article Schema Markup Type
const article = {
type: 'string',
id: 'string',
publisher: {
name: 'string',
logo: 'string',
type: 'string',
id: 'string'
},
headline: 'string',
datePublished: 'string',
image: ['string', 'string'],
author: {
name: 'string',
url: 'string',
type: 'string',
id: 'string'
},
description: 'string',
dateModified: 'string'
};
BreadcrumbList
BreadcrumbList Schema Markup Type
const breadcrumb = {
type: 'BreadcrumbList',
id: 'string',
itemListElement: [
{
type: 'ListItem',
id: 'string',
position: 1,
name: 'string',
item: 'url'
},
{
type: 'ListItem',
id: 'string',
position: 2,
name: 'string',
item: 'url'
}
]
};
FAQPage
FAQPage Schema Markup Type
const faqPage = {
type: 'FAQPage',
id: 'string',
mainEntity: [
{
type: 'Question',
name: 'string',
acceptedAnswer: {
type: 'Answer',
text: 'string'
}
},
{
type: 'Question',
name: 'string',
acceptedAnswer: {
type: 'Answer',
text: 'string'
}
}
]
};
HowTo
HowTo Schema Markup Type
const howTo = {
type: 'HowTo',
id: 'string',
name: 'string',
description: 'string',
image: 'url',
totalTime: 'string',
estimatedCost: {
type: 'MonetaryAmount',
currency: 'string',
value: 'string'
},
supply: [
{
type: 'HowToSupply',
name: 'string'
}
],
tool: [
{
type: 'HowToTool',
name: 'string'
}
],
step: [
{
type: 'HowToStep',
text: 'string',
image: 'url',
name: 'string',
url: 'url'
}
]
};
ImageObject
ImageObject Schema Markup Type
const imageObject = {
type: 'ImageObject',
id: 'string',
author: 'string',
contentLocation: 'string',
contentUrl: 'url',
datePublished: 'date',
description: 'string',
name: 'string',
caption: 'string'
};
LocalBusiness
LocalBusiness Schema Markup Type
const localBusiness = {
type: 'string',
id: 'string',
name: 'string',
logo: 'string',
image: 'string',
url: 'string',
telephone: 'string',
priceRange: 'string',
address: {
type: 'PostalAddress',
streetAddress: 'string',
addressLocality: 'string',
addressRegion: 'string',
postalCode: 'string',
addressCountry: 'string'
},
geo: {
type: 'GeoCoordinates',
latitude: 0.0,
longitude: 0.0
},
hasMap: 'string',
openingHoursSpecification: [
{
type: 'OpeningHoursSpecification',
dayOfWeek: ['string', 'string'],
opens: 'string',
closes: 'string'
}
],
sameAs: ['string', 'string']
};
Organization
Organization Schema Markup Type
const organization = {
type: 'string',
id: 'string',
name: 'string',
alternateName: 'string',
url: 'string',
logo: 'string',
contactPoint: [
{
type: 'string',
id: 'string',
telephone: 'string',
contactType: 'string',
contactOption: ['string', 'string'],
areaServed: ['string', 'string'],
availableLanguage: ['string', 'string']
}
],
sameAs: ['string', 'string']
};
Person
Person Schema Markup Type
const person = {
type: 'string',
id: 'string',
name: 'string',
url: 'string',
image: 'string',
jobTitle: 'string',
description: 'string',
email: 'string',
telephone: 'string',
birthDate: 'string',
gender: 'string',
spouse: 'string',
parent: 'string',
worksFor: {
type: 'Organization',
name: 'string'
},
address: {
type: 'PostalAddress',
streetAddress: 'string',
addressLocality: 'string',
addressRegion: 'string',
postalCode: 'string',
addressCountry: 'string'
},
sameAs: ['string', 'string']
};
Product
Product Schema Markup Type
const product = {
type: 'Product',
id: 'string',
name: 'string',
image: 'string',
description: 'string',
brand: {
type: 'Brand',
name: 'string'
},
sku: 'string',
gtin8: 'string',
gtin13: 'string',
gtin14: 'string',
mpn: 'string',
offers: {
type: 'Offer',
url: 'string',
priceCurrency: 'string',
price: 'string',
priceValidUntil: 'date',
availability: 'url',
itemCondition: 'url'
},
aggregateRating: {
type: 'AggregateRating',
ratingValue: 'string',
bestRating: 'string',
worstRating: 'string',
ratingCount: 'string',
reviewCount: 'string'
},
review: [
{
type: 'Review',
name: 'string',
reviewBody: 'string',
reviewRating: {
type: 'Rating',
ratingValue: 'string',
bestRating: 'string',
worstRating: 'string'
},
datePublished: 'date',
author: { type: 'Person', name: 'string' },
publisher: { type: 'Organization', name: 'string' }
}
]
};
Recipe
Recipe Schema Markup Type
const recipe = {
type: 'Recipe',
id: 'string',
name: 'string',
image: ['url', 'url'],
description: 'string',
keywords: 'string',
author: {
type: 'Person',
name: 'string'
},
datePublished: '2023-11-08',
prepTime: 'string',
cookTime: 'string',
totalTime: 'string',
recipeCategory: 'string',
recipeCuisine: 'string',
recipeYield: 'string',
nutrition: {
type: 'NutritionInformation',
servingSize: 'string',
calories: 'string',
fatContent: 'string'
},
recipeIngredient: ['string', 'string'],
recipeInstructions: [
{
type: 'HowToStep',
name: 'string',
text: 'string',
url: 'url',
image: 'url'
}
],
aggregateRating: {
type: 'AggregateRating',
ratingValue: 'string',
bestRating: 'string',
worstRating: 'string',
ratingCount: 'string',
reviewCount: 'string'
},
review: [
{
type: 'Review',
name: 'string',
reviewBody: 'string',
reviewRating: {
type: 'Rating',
ratingValue: 'string',
bestRating: 'string',
worstRating: 'string'
},
datePublished: 'date',
author: { type: 'Person', name: 'string' },
publisher: { type: 'Organization', name: 'string' }
}
],
video: {
type: 'VideoObject',
name: 'string',
description: 'string',
thumbnailUrl: ['url', 'url'],
uploadDate: 'date',
contentUrl: 'url',
embedUrl: 'url'
}
};
Review
Review Schema Markup Type
const review = {
type: 'Review',
id: 'string',
author: {
type: 'string',
name: 'string'
},
itemReviewed: {
type: 'string',
name: 'string'
},
reviewRating: {
type: 'Rating',
ratingValue: '5'
},
name: 'string',
reviewBody: 'string',
publisher: {
type: 'Organization',
name: 'string'
}
};
Service
Service Schema Markup Type
const service = {
type: 'Service',
id: 'string',
serviceType: 'string',
provider: {
type: 'string',
name: 'string'
},
areaServed: {
type: 'State',
name: 'string'
},
hasOfferCatalog: {
type: 'OfferCatalog',
name: 'string',
itemListElement: [
{
type: 'OfferCatalog',
name: 'string',
itemListElement: [
{
type: 'Offer',
itemOffered: {
type: 'Service',
name: 'string'
}
},
{
type: 'Offer',
itemOffered: {
type: 'Service',
name: 'string'
}
}
]
}
]
}
};
SocialMediaPosting
SocialMediaPosting Schema Markup Type
const socialMediaPosting = {
type: 'string',
id: 'string',
potentialAction: 'string',
url: 'string',
publisher: { name: 'string', logo: 'string', type: 'string', id: 'string' },
headline: 'string',
datePublished: 'string',
image: 'string',
author: {
name: 'string',
url: 'string',
type: 'string',
id: 'string'
},
description: 'string',
dateModified: 'string',
sharedContent: [
{
type: 'string',
headline: 'string',
url: 'string',
author: {
type: 'string',
name: 'string'
}
}
]
};
VideoObject
VideoObject Schema Markup Type
const videoObject = {
type: 'VideoObject',
id: 'string',
name: 'string',
description: 'string',
thumbnailUrl: ['url', 'url'],
uploadDate: 'date',
duration: 'string',
contentUrl: 'url',
embedUrl: 'url',
potentialAction: {
type: 'SeekToAction',
target: 'string={seek_to_second_number}',
'startOffset-input': 'required name=seek_to_second_number'
},
publisher: {
type: 'string',
name: 'string',
logo: 'url'
}
};
WebPage
WebPage Schema Markup Type
const webPage = {
type: 'string',
id: 'string',
name: 'string',
description: 'string',
image: 'string',
breadcrumb: 'string',
publisher: {
type: 'string',
name: 'string'
},
license: 'url'
};
WebSite
WebSite Schema Markup Type
const website = {
type: 'WebSite',
id: 'string',
name: 'string',
url: 'string',
potentialAction: {
type: 'SearchAction',
target: '{search_term_string}',
'query-input': 'required name=search_term_string'
}
};
Contributing
We have taken the liberty of adding the most commonly used Schema Markup types from the spec. If there are some missing you wish to be added, feel free to create a PR. Contributions are welcome.
License
Copyright ©2023 Operation Nation LLC. See LICENSE.
Develop & test
This plugin uses @sanity/plugin-kit
with default configuration for build & watch scripts.
See Testing a plugin in Sanity Studio
on how to run this plugin with hotreload in the studio.