Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions gallery/Article/invalid_missing_headline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"@context": "https://schema.org",
"@type": "Article",
"author": {
"@type": "Person",
"name": "Jane Doe"
},
"datePublished": "2025-01-07"
}
20 changes: 20 additions & 0 deletions gallery/Article/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to Write Great Headlines",
"author": {
"@type": "Person",
"name": "Jane Doe"
},
"publisher": {
"@type": "Organization",
"name": "Example News",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"datePublished": "2025-01-07",
"dateModified": "2025-01-07",
"image": "https://example.com/article-image.jpg"
}
6 changes: 6 additions & 0 deletions gallery/Event/invalid_missing_location.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"@context": "https://schema.org",
"@type": "Event",
"name": "The Adventures of Kira and Morrison",
"startDate": "2025-07-21T19:00-05:00"
}
9 changes: 9 additions & 0 deletions gallery/Event/invalid_missing_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"@context": "https://schema.org",
"@type": "Event",
"startDate": "2025-07-21T19:00-05:00",
"location": {
"@type": "Place",
"name": "Snickerpark Stadium"
}
}
36 changes: 36 additions & 0 deletions gallery/Event/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"@context": "https://schema.org",
"@type": "Event",
"name": "The Adventures of Kira and Morrison",
"startDate": "2025-07-21T19:00-05:00",
"endDate": "2025-07-21T23:00-05:00",
"location": {
"@type": "Place",
"name": "Snickerpark Stadium",
"address": {
"@type": "PostalAddress",
"streetAddress": "100 West Snickerpark Dr",
"addressLocality": "Snickertown",
"postalCode": "19019",
"addressRegion": "PA",
"addressCountry": "US"
}
},
"image": "https://example.com/event-image.jpg",
"description": "An amazing concert event",
"offers": {
"@type": "Offer",
"url": "https://example.com/tickets",
"price": "30",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"performer": {
"@type": "Person",
"name": "Kira Morrison"
},
"organizer": {
"@type": "Organization",
"name": "Concert Events Inc"
}
}
9 changes: 9 additions & 0 deletions gallery/Event/valid_online.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"@context": "https://schema.org",
"@type": "Event",
"name": "Online Webinar: Introduction to Schema.org",
"startDate": "2025-07-21T19:00-05:00",
"eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode",
"eventStatus": "https://schema.org/EventScheduled",
"description": "Learn about structured data"
}
5 changes: 5 additions & 0 deletions gallery/FAQPage/invalid_missing_mainEntity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context": "https://schema.org",
"@type": "FAQPage",
"name": "Frequently Asked Questions"
}
22 changes: 22 additions & 0 deletions gallery/FAQPage/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is structured data?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Structured data is a standardized format for providing information about a page and classifying the page content."
}
},
{
"@type": "Question",
"name": "Why is structured data important?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Structured data helps search engines understand your content better and can enable rich results in search."
}
}
]
}
10 changes: 10 additions & 0 deletions gallery/HowTo/invalid_missing_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"@context": "https://schema.org",
"@type": "HowTo",
"step": [
{
"@type": "HowToStep",
"text": "Do something"
}
]
}
5 changes: 5 additions & 0 deletions gallery/HowTo/invalid_missing_step.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Do Something"
}
46 changes: 46 additions & 0 deletions gallery/HowTo/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Change a Tire",
"description": "A step-by-step guide to changing a flat tire.",
"totalTime": "PT30M",
"estimatedCost": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": "0"
},
"supply": [
{
"@type": "HowToSupply",
"name": "Spare tire"
},
{
"@type": "HowToSupply",
"name": "Lug wrench"
}
],
"tool": [
{
"@type": "HowToTool",
"name": "Jack"
}
],
"step": [
{
"@type": "HowToStep",
"name": "Loosen the lug nuts",
"text": "Use the lug wrench to loosen the lug nuts on the flat tire."
},
{
"@type": "HowToStep",
"name": "Jack up the car",
"text": "Place the jack under the car frame and raise the car."
},
{
"@type": "HowToStep",
"name": "Remove the flat tire",
"text": "Remove the lug nuts and pull off the flat tire."
}
],
"image": "https://example.com/tire-change.jpg"
}
5 changes: 5 additions & 0 deletions gallery/LocalBusiness/invalid_missing_address.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Dave's Steak House"
}
12 changes: 12 additions & 0 deletions gallery/LocalBusiness/invalid_missing_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "New York",
"addressRegion": "NY",
"postalCode": "10001",
"addressCountry": "US"
}
}
36 changes: 36 additions & 0 deletions gallery/LocalBusiness/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Dave's Steak House",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "New York",
"addressRegion": "NY",
"postalCode": "10001",
"addressCountry": "US"
},
"telephone": "+1-212-555-1234",
"url": "https://www.davessteakhouse.example.com",
"image": "https://www.davessteakhouse.example.com/image.jpg",
"priceRange": "$$",
"geo": {
"@type": "GeoCoordinates",
"latitude": "40.7128",
"longitude": "-74.0060"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
],
"opens": "11:00",
"closes": "22:00"
}
]
}
5 changes: 5 additions & 0 deletions gallery/WebSite/invalid_missing_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://www.example.com"
}
5 changes: 5 additions & 0 deletions gallery/WebSite/invalid_missing_url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Example Website"
}
14 changes: 14 additions & 0 deletions gallery/WebSite/valid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Example Website",
"url": "https://www.example.com",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://www.example.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
18 changes: 18 additions & 0 deletions src/types/Answer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import BaseValidator from './base.js';

export default class AnswerValidator extends BaseValidator {
getConditions() {
return [this.required('text')].map((c) => c.bind(this));
}
}
26 changes: 26 additions & 0 deletions src/types/Article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import BaseValidator from './base.js';

export default class ArticleValidator extends BaseValidator {
getConditions() {
return [
this.required('headline'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation, headline is only a recommended property.

https://developers.google.com/search/docs/appearance/structured-data/article


this.recommended('author', 'arrayOrObject'),
this.recommended('dateModified', 'date'),
this.recommended('datePublished', 'date'),
this.recommended('image', 'arrayOrObject'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be an URL as string as well.

this.recommended('publisher', 'object'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute is optional according to the documentation. Basically recommending that the publisher of an article should not be listed as part of the author attribute, but using publisher.

https://developers.google.com/search/docs/appearance/structured-data/article

].map((c) => c.bind(this));
}
}
51 changes: 51 additions & 0 deletions src/types/Event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import BaseValidator from './base.js';

export default class EventValidator extends BaseValidator {
getConditions() {
return [
this.required('name'),
this.required('startDate', 'date'),
this.locationOrAttendanceMode,

this.recommended('description'),
this.recommended('endDate', 'date'),
this.recommended('eventAttendanceMode'),
this.recommended('eventStatus'),
this.recommended('image', 'arrayOrObject'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also be an URL as string.

this.recommended('offers', 'arrayOrObject'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adjust the Offer type to selectively add recommended attributes availability, price, priceCurrency, validFrom and url for when used within an Event type. The Offer type already contains this logic for use with Product as a reference.

this.recommended('organizer', 'object'),
this.recommended('performer', 'arrayOrObject'),
].map((c) => c.bind(this));
}

locationOrAttendanceMode(data) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const hasLocation = data.location !== undefined && data.location !== null;
const hasOnlineAttendanceMode =
data.eventAttendanceMode &&
(data.eventAttendanceMode.includes('OnlineEventAttendanceMode') ||
data.eventAttendanceMode.includes('MixedEventAttendanceMode'));

if (!hasLocation && !hasOnlineAttendanceMode) {
return {
issueMessage:
'Either "location" or online "eventAttendanceMode" is required',
severity: 'ERROR',
path: this.path,
fieldName: 'location',
fieldNames: ['location', 'eventAttendanceMode'],
};
}
return null;
}
}
18 changes: 18 additions & 0 deletions src/types/FAQPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import BaseValidator from './base.js';

export default class FAQPageValidator extends BaseValidator {
getConditions() {
return [this.required('mainEntity', 'arrayOrObject')].map((c) => c.bind(this));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please additionally check that mainEntity contains at least one item.

https://developers.google.com/search/docs/appearance/structured-data/faqpage#faq-page

}
}
Loading