Overview
Schema.org defines a collection of schemas - structured data models - that webmasters embed in their HTML pages to describe content in a machine-readable format. Search engines including Google, Microsoft, Yahoo, and Yandex created Schema.org in 2011 as a collaborative project to standardize semantic markup across the web.
The vocabulary consists of types (like Person, Product, Event, Organization) and properties (like name, description, price, startDate) that describe entities and their attributes. Web developers add this markup to HTML using formats like JSON-LD, Microdata, or RDFa, allowing search engines to extract structured information about page content.
Schema.org markup produces rich results in search engine listings - product ratings, event dates, recipe cooking times, article authors, and business hours appear directly in search results. Social media platforms use Schema.org data for Open Graph previews. Email clients recognize event and reservation schemas for automatic calendar integration. Voice assistants parse Schema.org markup to answer spoken queries.
The vocabulary extends beyond basic types through hierarchical inheritance. Article inherits from CreativeWork, which inherits from Thing. This hierarchy allows specific types to inherit properties from parent types while adding specialized attributes. A BlogPosting inherits all Article properties while adding blog-specific fields.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Wireless Headphones",
"description": "High-fidelity wireless headphones",
"brand": {
"@type": "Brand",
"name": "AudioTech"
},
"offers": {
"@type": "Offer",
"price": "149.99",
"priceCurrency": "USD"
}
}
This markup tells search engines exactly what the page describes - a product with specific attributes - rather than forcing them to infer meaning from unstructured text.
Key Principles
Schema.org operates on several fundamental principles that shape how developers implement structured data markup.
Type System and Hierarchy
The vocabulary organizes types in a hierarchical tree rooted at Thing. Every schema type inherits from Thing directly or through intermediate types. This inheritance means all types share common properties like name, description, url, and image while specialized types add domain-specific properties.
Type specificity matters. A Restaurant inherits from FoodEstablishment, which inherits from LocalBusiness, which inherits from Organization, which inherits from Thing. Each level adds relevant properties - Restaurant includes menu and servesCuisine, LocalBusiness adds openingHours and address, Organization includes founder and employee.
Property Constraints and Expected Types
Properties define expected types that specify which values the property accepts. The author property expects Person or Organization. The location property expects Place or PostalAddress or Text. Developers must provide values matching expected types or search engines may ignore the markup.
Properties can accept multiple types, but not all values are equivalent. A text string URL differs from a structured URL object. A text address differs from a PostalAddress object with structured components. Search engines prefer structured data over plain text when both options exist.
JSON-LD as Primary Format
Schema.org supports three formats for embedding markup: JSON-LD, Microdata, and RDFa. JSON-LD (JavaScript Object Notation for Linked Data) has emerged as the preferred format because it separates markup from HTML content, making maintenance simpler and reducing errors.
JSON-LD appears in script tags within the HTML head or body. Multiple script tags can contain different Schema.org objects for complex pages. The @context property specifies the Schema.org vocabulary, and @type identifies the schema type.
Graph Structure and Entity References
Complex pages often describe multiple related entities. Schema.org handles relationships through entity references using @id properties. An author property can reference a Person entity defined elsewhere on the page rather than duplicating person information.
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Person",
"@id": "#author",
"name": "Jane Smith",
"jobTitle": "Senior Developer"
},
{
"@type": "BlogPosting",
"headline": "Understanding Schema.org",
"author": {"@id": "#author"}
}
]
}
The @graph property contains an array of related entities. References using @id create connections between entities without duplication.
Validation and Required Properties
Schema.org defines properties as optional, recommended, or required depending on the type. Product markup should include name, image, and offers for search engines to display rich results. Event markup requires name, startDate, and location. Missing required properties causes validation errors and prevents rich results.
Search engines validate markup against their guidelines, which sometimes exceed base Schema.org requirements. Google's requirements for JobPosting differ from general Schema.org specifications. Developers must consult platform-specific documentation alongside Schema.org vocabulary.
Ruby Implementation
Ruby web applications implement Schema.org markup through several approaches depending on the framework and application architecture.
Rails View Helpers
Rails applications typically generate Schema.org JSON-LD in view templates using helper methods that construct structured data objects.
# app/helpers/schema_helper.rb
module SchemaHelper
def schema_org_product(product)
{
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
image: image_url(product.primary_image),
brand: {
'@type': 'Brand',
name: product.brand.name
},
offers: {
'@type': 'Offer',
price: product.price.to_s,
priceCurrency: 'USD',
availability: product.in_stock? ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'
}
}.to_json
end
end
Views embed this JSON-LD in script tags:
<script type="application/ld+json">
<%= raw schema_org_product(@product) %>
</script>
The raw helper prevents Rails from HTML-escaping the JSON, which would break the markup. The to_json method serializes the Ruby hash to valid JSON-LD.
Object-Oriented Schema Builders
More complex applications benefit from dedicated classes that encapsulate Schema.org generation logic.
# app/services/schema_org/product_builder.rb
module SchemaOrg
class ProductBuilder
def initialize(product)
@product = product
end
def to_hash
{
'@context': 'https://schema.org',
'@type': 'Product',
name: @product.name,
description: @product.description,
sku: @product.sku,
image: image_urls,
brand: brand_schema,
offers: offers_schema,
aggregateRating: rating_schema,
review: review_schemas
}.compact
end
def to_json(*args)
to_hash.to_json(*args)
end
private
def image_urls
@product.images.map { |img| Rails.application.routes.url_helpers.image_url(img) }
end
def brand_schema
return nil unless @product.brand
{
'@type': 'Brand',
name: @product.brand.name
}
end
def offers_schema
{
'@type': 'Offer',
url: Rails.application.routes.url_helpers.product_url(@product),
price: @product.price.to_s,
priceCurrency: @product.currency,
availability: availability_url,
priceValidUntil: (@product.price_valid_until || 1.year.from_now).iso8601
}
end
def availability_url
if @product.in_stock?
'https://schema.org/InStock'
elsif @product.discontinued?
'https://schema.org/Discontinued'
else
'https://schema.org/OutOfStock'
end
end
def rating_schema
return nil unless @product.reviews.any?
{
'@type': 'AggregateRating',
ratingValue: @product.average_rating.to_s,
reviewCount: @product.reviews.count
}
end
def review_schemas
@product.reviews.recent(5).map do |review|
{
'@type': 'Review',
author: {
'@type': 'Person',
name: review.author_name
},
datePublished: review.created_at.iso8601,
reviewBody: review.content,
reviewRating: {
'@type': 'Rating',
ratingValue: review.rating.to_s
}
}
end
end
end
end
This builder pattern separates Schema.org logic from models and views, making it testable and reusable. The compact method removes nil values, preventing empty properties in the output.
Gem-Based Solutions
The schema_dot_org gem provides a DSL for building Schema.org markup declaratively.
# Using schema_dot_org gem
require 'schema_dot_org'
schema = SchemaDotOrg.new do
product do
name 'Wireless Headphones'
description 'Premium wireless headphones'
image 'https://example.com/headphones.jpg'
brand organization do
name 'AudioTech'
end
offers offer do
price '149.99'
price_currency 'USD'
availability 'https://schema.org/InStock'
end
end
end
schema.to_json_ld
The gem handles type names, property names, and nesting, reducing boilerplate code. However, gems add dependencies and may lag behind Schema.org vocabulary updates.
Dynamic Schema Selection
Applications serving multiple content types need dynamic schema selection based on the content being displayed.
# app/services/schema_org/builder_factory.rb
module SchemaOrg
class BuilderFactory
def self.for(record)
case record
when Product
ProductBuilder.new(record)
when Article
ArticleBuilder.new(record)
when Event
EventBuilder.new(record)
when Person
PersonBuilder.new(record)
else
GenericBuilder.new(record)
end
end
end
end
# In controller or view
schema_builder = SchemaOrg::BuilderFactory.for(@resource)
@schema_markup = schema_builder.to_json
This factory pattern centralizes builder selection, making it easy to add new types and maintain consistency across the application.
Practical Examples
Article with Author and Publisher
Blog posts and articles require structured data for article search features and AMP compatibility.
# app/services/schema_org/article_builder.rb
class SchemaOrg::ArticleBuilder
def initialize(article)
@article = article
end
def to_hash
{
'@context': 'https://schema.org',
'@type': 'Article',
headline: @article.title,
alternativeHeadline: @article.subtitle,
description: @article.excerpt,
image: image_object,
datePublished: @article.published_at.iso8601,
dateModified: @article.updated_at.iso8601,
author: author_schema,
publisher: publisher_schema,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': Rails.application.routes.url_helpers.article_url(@article)
}
}
end
private
def image_object
return [] unless @article.featured_image
[{
'@type': 'ImageObject',
url: image_url(@article.featured_image),
width: @article.featured_image.width,
height: @article.featured_image.height
}]
end
def author_schema
{
'@type': 'Person',
name: @article.author.full_name,
url: Rails.application.routes.url_helpers.author_url(@article.author)
}
end
def publisher_schema
{
'@type': 'Organization',
name: 'Tech Blog',
logo: {
'@type': 'ImageObject',
url: 'https://example.com/logo.png',
width: 600,
height: 60
}
}
end
end
Article schema requires specific image dimensions for AMP validity. The publisher logo must meet Google's size requirements (600x60 pixels or proportional).
Event with Location and Performer
Events display rich cards in search results with dates, locations, and ticket information.
# app/services/schema_org/event_builder.rb
class SchemaOrg::EventBuilder
def initialize(event)
@event = event
end
def to_hash
{
'@context': 'https://schema.org',
'@type': 'Event',
name: @event.name,
description: @event.description,
startDate: @event.starts_at.iso8601,
endDate: @event.ends_at.iso8601,
eventStatus: event_status,
eventAttendanceMode: attendance_mode,
location: location_schema,
performer: performer_schemas,
organizer: organizer_schema,
offers: offers_schemas
}.compact
end
private
def event_status
case @event.status
when 'scheduled' then 'https://schema.org/EventScheduled'
when 'cancelled' then 'https://schema.org/EventCancelled'
when 'postponed' then 'https://schema.org/EventPostponed'
else 'https://schema.org/EventScheduled'
end
end
def attendance_mode
case @event.attendance_mode
when 'offline' then 'https://schema.org/OfflineEventAttendanceMode'
when 'online' then 'https://schema.org/OnlineEventAttendanceMode'
when 'mixed' then 'https://schema.org/MixedEventAttendanceMode'
end
end
def location_schema
if @event.online?
{
'@type': 'VirtualLocation',
url: @event.virtual_url
}
else
{
'@type': 'Place',
name: @event.venue.name,
address: {
'@type': 'PostalAddress',
streetAddress: @event.venue.street_address,
addressLocality: @event.venue.city,
addressRegion: @event.venue.state,
postalCode: @event.venue.zip_code,
addressCountry: @event.venue.country_code
}
}
end
end
def performer_schemas
@event.performers.map do |performer|
{
'@type': 'Person',
name: performer.name
}
end
end
def organizer_schema
{
'@type': 'Organization',
name: @event.organizer.name,
url: @event.organizer.website
}
end
def offers_schemas
@event.ticket_types.map do |ticket_type|
{
'@type': 'Offer',
name: ticket_type.name,
price: ticket_type.price.to_s,
priceCurrency: 'USD',
availability: ticket_availability(ticket_type),
validFrom: @event.sales_start_at.iso8601,
url: ticket_purchase_url(ticket_type)
}
end
end
def ticket_availability(ticket_type)
ticket_type.sold_out? ? 'https://schema.org/SoldOut' : 'https://schema.org/InStock'
end
end
Event schemas support both physical and virtual events. The eventAttendanceMode property distinguishes between offline, online, and hybrid events. Location uses VirtualLocation for online events and Place with PostalAddress for physical events.
Recipe with Nutrition Information
Recipe markup displays cooking times, ingredient lists, and nutritional information in search results.
# app/services/schema_org/recipe_builder.rb
class SchemaOrg::RecipeBuilder
def initialize(recipe)
@recipe = recipe
end
def to_hash
{
'@context': 'https://schema.org',
'@type': 'Recipe',
name: @recipe.name,
description: @recipe.description,
image: @recipe.images.map(&:url),
author: {
'@type': 'Person',
name: @recipe.author.name
},
datePublished: @recipe.published_at.iso8601,
prepTime: duration_iso8601(@recipe.prep_time_minutes),
cookTime: duration_iso8601(@recipe.cook_time_minutes),
totalTime: duration_iso8601(@recipe.total_time_minutes),
recipeYield: @recipe.servings.to_s,
recipeCategory: @recipe.category,
recipeCuisine: @recipe.cuisine,
keywords: @recipe.tags.join(', '),
recipeIngredient: @recipe.ingredients.map(&:description),
recipeInstructions: instruction_schemas,
nutrition: nutrition_schema,
aggregateRating: rating_schema
}.compact
end
private
def duration_iso8601(minutes)
return nil if minutes.nil?
"PT#{minutes}M"
end
def instruction_schemas
@recipe.steps.map.with_index do |step, index|
{
'@type': 'HowToStep',
position: index + 1,
text: step.instruction,
image: step.image&.url
}.compact
end
end
def nutrition_schema
return nil unless @recipe.nutrition_info
info = @recipe.nutrition_info
{
'@type': 'NutritionInformation',
calories: "#{info.calories} calories",
proteinContent: "#{info.protein_grams}g",
fatContent: "#{info.fat_grams}g",
carbohydrateContent: "#{info.carbohydrate_grams}g",
fiberContent: "#{info.fiber_grams}g",
sodiumContent: "#{info.sodium_mg}mg"
}.compact
end
def rating_schema
return nil unless @recipe.reviews.any?
{
'@type': 'AggregateRating',
ratingValue: @recipe.average_rating.to_s,
ratingCount: @recipe.reviews.count
}
end
end
Recipe schemas use ISO 8601 duration format for times. PT30M represents 30 minutes, PT1H30M represents 1 hour 30 minutes. Nutrition values include units as part of the text string.
Integration & Interoperability
Search Engine Integration
Each major search engine interprets Schema.org markup differently and supports different subsets of the vocabulary.
Google Search Console validates Schema.org markup through the Rich Results Test tool. Google supports Product, Recipe, Event, Article, FAQ, HowTo, JobPosting, LocalBusiness, and Video schemas for rich results. Other schema types get indexed but don't produce enhanced search results.
Bing Webmaster Tools includes a markup validator similar to Google's. Bing emphasizes LocalBusiness markup and supports fewer types than Google. Testing markup in both platforms prevents compatibility issues.
Yandex supports Schema.org through its Webmaster Tools platform. Yandex has stronger support for Organization and Person schemas than Google, particularly for Russian-language sites.
Social Media Open Graph Integration
Social media platforms use Open Graph protocol rather than Schema.org, but applications often need both. Rails applications can generate both simultaneously.
# app/helpers/meta_tags_helper.rb
module MetaTagsHelper
def social_and_schema_tags(resource)
tags = []
# Open Graph tags
tags << tag.meta(property: 'og:title', content: resource.title)
tags << tag.meta(property: 'og:description', content: resource.description)
tags << tag.meta(property: 'og:image', content: resource.image_url)
tags << tag.meta(property: 'og:url', content: resource_url(resource))
# Twitter Card tags
tags << tag.meta(name: 'twitter:card', content: 'summary_large_image')
tags << tag.meta(name: 'twitter:title', content: resource.title)
# Schema.org JSON-LD
schema_builder = SchemaOrg::BuilderFactory.for(resource)
tags << tag.script(schema_builder.to_json.html_safe, type: 'application/ld+json')
safe_join(tags, "\n")
end
end
This helper combines Open Graph, Twitter Card, and Schema.org markup in a single method. The html_safe method allows the JSON-LD to render without escaping.
Email Client Integration
Gmail, Outlook, and other email clients parse Schema.org markup in HTML emails to create interactive features. Flight reservations, package tracking, event invitations, and restaurant reservations get special treatment when properly marked up.
# app/mailers/reservation_mailer.rb
class ReservationMailer < ApplicationMailer
def confirmation(reservation)
@reservation = reservation
@schema = reservation_schema
mail(to: @reservation.email, subject: 'Reservation Confirmed')
end
private
def reservation_schema
{
'@context': 'https://schema.org',
'@type': 'LodgingReservation',
reservationNumber: @reservation.confirmation_code,
reservationStatus: 'https://schema.org/ReservationConfirmed',
underName: {
'@type': 'Person',
name: @reservation.guest_name
},
reservationFor: {
'@type': 'LodgingBusiness',
name: @reservation.property.name,
address: {
'@type': 'PostalAddress',
streetAddress: @reservation.property.address,
addressLocality: @reservation.property.city,
addressRegion: @reservation.property.state,
postalCode: @reservation.property.zip
}
},
checkinTime: @reservation.checkin_date.iso8601,
checkoutTime: @reservation.checkout_date.iso8601
}.to_json
end
end
The email view embeds this schema in a script tag. Gmail displays a card with check-in dates, location, and confirmation number when it detects LodgingReservation markup.
Knowledge Graph Integration
Google Knowledge Graph uses Schema.org markup to populate knowledge panels - the information boxes that appear for entities like organizations, people, and places. Consistent markup across multiple pages increases the likelihood of Knowledge Graph inclusion.
# app/services/schema_org/organization_builder.rb
class SchemaOrg::OrganizationBuilder
def initialize(organization)
@org = organization
end
def to_hash
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': Rails.application.routes.url_helpers.root_url,
name: @org.name,
alternateName: @org.alternate_names,
url: Rails.application.routes.url_helpers.root_url,
logo: logo_schema,
contactPoint: contact_points,
sameAs: social_profiles,
address: address_schema,
founder: founder_schemas
}.compact
end
private
def logo_schema
{
'@type': 'ImageObject',
url: @org.logo_url,
width: 600,
height: 60
}
end
def contact_points
@org.contact_methods.map do |contact|
{
'@type': 'ContactPoint',
telephone: contact.phone,
contactType: contact.contact_type,
availableLanguage: contact.languages
}
end
end
def social_profiles
[
@org.twitter_url,
@org.facebook_url,
@org.linkedin_url,
@org.instagram_url
].compact
end
def address_schema
{
'@type': 'PostalAddress',
streetAddress: @org.street_address,
addressLocality: @org.city,
addressRegion: @org.state,
postalCode: @org.zip_code,
addressCountry: @org.country
}
end
end
The @id property establishes the canonical identifier for the organization. The sameAs property links social media profiles, helping Google connect different online presences to the same entity.
Tools & Ecosystem
Validation Tools
Google Rich Results Test validates Schema.org markup and shows how Google interprets the data. The tool accepts URLs or raw HTML snippets. Results indicate which schema types Google recognizes and any errors preventing rich results.
Schema Markup Validator from Schema.org provides official validation against the Schema.org specification. This validator checks technical correctness regardless of search engine support.
Structured Data Testing Tool from Bing validates markup specifically for Bing search features. Testing in multiple validators ensures cross-platform compatibility.
Ruby Gems
The schema_dot_org gem provides a DSL for building Schema.org markup programmatically. The gem handles vocabulary updates and provides validation against Schema.org specifications.
# Gemfile
gem 'schema_dot_org'
# Usage
schema = SchemaDotOrg.new do
local_business do
name 'Coffee Shop'
telephone '555-1234'
address postal_address do
street_address '123 Main St'
address_locality 'Portland'
address_region 'OR'
postal_code '97201'
end
end
end
The structured-data gem offers similar functionality with a different API design. Both gems reduce boilerplate but add dependencies to maintain.
The json-ld gem handles JSON-LD processing and validation. While not Schema.org-specific, it helps when working with complex JSON-LD graphs.
Browser Extensions
Schema Markup Validator browser extension highlights Schema.org markup on any webpage. The extension shows parsed markup in a sidebar, making debugging easier during development.
SEO META in 1 CLICK extension displays all meta tags including Schema.org markup for the current page. The extension aggregates Open Graph, Twitter Card, and Schema.org data in a single view.
Development Tools
The markup generator at Schema.org provides interactive forms for creating common schema types. Generated markup serves as a starting point for custom implementations.
JSON-LD Playground allows testing and debugging JSON-LD syntax. The tool validates JSON-LD structure and shows the expanded graph representation.
Rails console testing helps validate Schema.org output during development:
# In Rails console
product = Product.first
builder = SchemaOrg::ProductBuilder.new(product)
puts JSON.pretty_generate(JSON.parse(builder.to_json))
# Validate required properties
schema = builder.to_hash
required = [:name, :image, :offers]
missing = required.reject { |prop| schema[prop].present? }
puts "Missing required properties: #{missing.join(', ')}" if missing.any?
This validation catches missing required properties before deployment.
Monitoring and Analytics
Google Search Console reports on structured data through the Enhancements section. The report shows which pages have valid markup, which have errors, and which don't appear in rich results.
Performance reports filter by rich result type, showing clicks and impressions for Product, Recipe, Event, and other enhanced results. This data helps measure Schema.org impact on traffic.
Schema.org analytics tracking involves custom Google Analytics events when users interact with rich results:
// Track rich result clicks
document.querySelectorAll('.rich-result-link').forEach(function(link) {
link.addEventListener('click', function() {
gtag('event', 'rich_result_click', {
'event_category': 'schema_org',
'event_label': link.dataset.schemaType
});
});
});
This tracking connects Schema.org implementation to user behavior metrics.
Common Pitfalls
Missing Required Properties
Schema.org types have required properties that must be present for rich results. Product requires name, image, and offers. Event requires name, startDate, and location. Omitting required properties causes validation failures.
# Incorrect - missing required properties
{
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Widget'
# Missing: image, offers
}
# Correct - includes required properties
{
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Widget',
image: 'https://example.com/widget.jpg',
offers: {
'@type': 'Offer',
price: '29.99',
priceCurrency: 'USD'
}
}
Validation errors don't prevent indexing but prevent rich results from appearing. Testing markup in Google Rich Results Test catches missing properties before deployment.
Incorrect Property Types
Properties expect specific value types. Providing a string when the property expects an object causes parsing errors.
# Incorrect - author as string
{
'@type': 'Article',
author: 'Jane Smith' # String instead of Person object
}
# Correct - author as Person object
{
'@type': 'Article',
author: {
'@type': 'Person',
name: 'Jane Smith'
}
}
Some properties accept multiple types. The identifier property accepts Text or PropertyValue or URL. When multiple types are valid, choose the most specific type available.
Invalid Date Formats
Schema.org requires ISO 8601 format for dates and times. Common date format mistakes break validation.
# Incorrect date formats
startDate: '01/15/2025' # US format
startDate: '15-01-2025' # European format
startDate: 'January 15, 2025' # Human-readable format
# Correct ISO 8601 formats
startDate: '2025-01-15' # Date only
startDate: '2025-01-15T14:30:00Z' # Date with UTC time
startDate: '2025-01-15T14:30:00-05:00' # Date with timezone offset
Rails provides iso8601 method for ActiveSupport::TimeWithZone objects:
event.starts_at.iso8601 # => "2025-01-15T14:30:00Z"
Duration formats also use ISO 8601. PT30M represents 30 minutes, PT2H represents 2 hours, PT1H30M represents 1 hour 30 minutes.
HTML Escaping in JSON-LD
Rails automatically HTML-escapes output for security. JSON-LD requires unescaped output to remain valid JSON.
# Incorrect - escaped JSON breaks parsing
<script type="application/ld+json">
<%= @schema.to_json %>
</script>
# Correct - raw prevents escaping
<script type="application/ld+json">
<%= raw @schema.to_json %>
</script>
The raw helper prevents HTML escaping. The html_safe method achieves the same result. Never use raw with user-provided content - only with internally generated Schema.org markup.
Duplicate Properties
Schema.org markup should not include duplicate properties with different values. Search engines may ignore one value or reject the entire markup.
# Incorrect - duplicate name properties
{
'@type': 'Product',
name: 'Widget Pro',
name: 'Professional Widget' # Duplicate property
}
# Correct - single name property
{
'@type': 'Product',
name: 'Widget Pro'
}
Ruby hashes naturally prevent duplicate keys, but building markup from multiple sources can create duplicates. Using merge or compact methods helps avoid this issue.
Wrong Availability URLs
Product availability must use Schema.org enumeration URLs, not strings.
# Incorrect - string values
availability: 'in stock'
availability: 'InStock'
# Correct - Schema.org URLs
availability: 'https://schema.org/InStock'
availability: 'https://schema.org/OutOfStock'
availability: 'https://schema.org/PreOrder'
Many Schema.org properties accept only enumerated values from specific vocabularies. The specification documentation lists valid values for each enumeration type.
Inconsistent Currency Handling
Price properties require string values, not numbers. Currency codes must follow ISO 4217.
# Incorrect - numeric price
{
price: 29.99, # Number instead of string
priceCurrency: 'USD'
}
# Correct - string price
{
price: '29.99', # String representation
priceCurrency: 'USD' # ISO 4217 code
}
Rails number formatting can introduce commas or currency symbols. Convert prices to plain decimal strings for Schema.org markup:
price: product.price.to_s # => "29.99"
Missing Context or Type
Every Schema.org object requires @context and @type properties. Omitting these makes the markup invalid.
# Incorrect - missing context
{
'@type': 'Product',
name: 'Widget'
}
# Incorrect - missing type
{
'@context': 'https://schema.org',
name: 'Widget'
}
# Correct - includes both
{
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Widget'
}
The @context property identifies the vocabulary (almost always https://schema.org). The @type property specifies the schema type.
Reference
Common Schema Types
| Type | Use Case | Required Properties |
|---|---|---|
| Product | E-commerce items | name, image, offers |
| Article | Blog posts, news | headline, datePublished, author |
| Event | Concerts, conferences | name, startDate, location |
| Recipe | Cooking instructions | name, image, recipeIngredient |
| LocalBusiness | Physical stores | name, address, telephone |
| Person | Author profiles | name |
| Organization | Companies, nonprofits | name, url |
| WebPage | General pages | name, url |
| FAQPage | FAQ sections | mainEntity |
| JobPosting | Job listings | title, description, datePosted |
| Review | Product reviews | itemReviewed, reviewRating |
| Video | Video content | name, description, thumbnailUrl |
Property Value Types
| Property | Expected Type | Example |
|---|---|---|
| name | Text | Wireless Headphones |
| description | Text | High-quality wireless headphones |
| url | URL | https://example.com/product |
| image | URL or ImageObject | https://example.com/image.jpg |
| price | Text | 149.99 |
| priceCurrency | Text | USD |
| datePublished | Date or DateTime | 2025-01-15T14:30:00Z |
| author | Person or Organization | Person object |
| address | PostalAddress or Text | PostalAddress object |
| telephone | Text | +1-555-123-4567 |
| Text | contact@example.com |
Date and Time Formats
| Format | Pattern | Example |
|---|---|---|
| Date | YYYY-MM-DD | 2025-01-15 |
| DateTime UTC | YYYY-MM-DDTHH:MM:SSZ | 2025-01-15T14:30:00Z |
| DateTime Offset | YYYY-MM-DDTHH:MM:SS±HH:MM | 2025-01-15T14:30:00-05:00 |
| Duration | PTnHnM | PT2H30M |
| Time | HH:MM:SS | 14:30:00 |
Availability Enumerations
| Value | URL | Use Case |
|---|---|---|
| In Stock | https://schema.org/InStock | Available for purchase |
| Out of Stock | https://schema.org/OutOfStock | Temporarily unavailable |
| Discontinued | https://schema.org/Discontinued | No longer produced |
| Pre-Order | https://schema.org/PreOrder | Available for pre-order |
| Pre-Sale | https://schema.org/PreSale | Pre-release sales |
| Sold Out | https://schema.org/SoldOut | Limited quantity exhausted |
Event Status Enumerations
| Value | URL | Use Case |
|---|---|---|
| Scheduled | https://schema.org/EventScheduled | Event will occur |
| Cancelled | https://schema.org/EventCancelled | Event cancelled |
| Postponed | https://schema.org/EventPostponed | Event delayed |
| Rescheduled | https://schema.org/EventRescheduled | New date scheduled |
Ruby Helper Methods
| Method | Purpose | Example |
|---|---|---|
| to_json | Convert hash to JSON | hash.to_json |
| iso8601 | Format datetime | time.iso8601 |
| raw | Prevent HTML escaping | raw json_string |
| html_safe | Mark string as safe | string.html_safe |
| compact | Remove nil values | hash.compact |
| to_s | Convert to string | number.to_s |
Validation Checklist
| Check | Validation |
|---|---|
| Context present | @context key exists |
| Type specified | @type key exists |
| Required properties | All required fields present |
| Date format | ISO 8601 compliance |
| Price format | String representation |
| Currency code | ISO 4217 compliance |
| URLs absolute | Full URLs with protocol |
| Images accessible | URLs return 200 status |
| Enumerations | Schema.org URLs used |
| JSON validity | Valid JSON syntax |