A comprehensive, WordPress schema generation framework with a clean provider-based architecture.
WP Schema follows a clean, modular architecture:
- Core Framework: Provider registration, schema assembly, and output management
- Provider System: Hook-based registration for extensible schema generation
- Clean References: Schema graphs with @id references and automatic deduplication
- WordPress Integration: Deep integration with WordPress core data and features
- @graph Format: Modern JSON-LD output using Google's recommended @graph structure
- Simple Provider Interface: Easy to implement schema providers
- Comprehensive Coverage: Built-in providers for all major WordPress contexts
- Registration Priority System: Predictable schema ordering with priority-based registration
- Flexible Filtering: Multiple filter hooks for customization at every level
- Reference Resolution: Clean @id references for building complex schema graphs
- WordPress Core Integration: Automatic schema for posts, pages, archives, media, and more
- Type Registry: Comprehensive registry of 250+ schema.org types with UI support
# Via Composer
composer require builtnorth/wp-schema
Initialize the framework in your plugin or theme:
// Initialize wp-schema
if (class_exists('BuiltNorth\WPSchema\App')) {
add_action('init', function() {
BuiltNorth\WPSchema\App::initialize();
});
}
Once initialized, the framework automatically outputs schema via HTML <script type="application/ld+json">
tags in the document head.
Register your schema providers via hook:
add_action('wp_schema_framework_register_providers', function($provider_manager) {
$provider_manager->register(
'my_plugin_provider',
'MyPlugin\\Schema\\MySchemaProvider'
);
});
For basic schema additions:
add_filter('wp_schema_framework_pieces', function($pieces, $context) {
if ($context === 'singular' && get_post_type() === 'event') {
$pieces[] = [
'@type' => 'Event',
'name' => get_the_title(),
'startDate' => get_post_meta(get_the_ID(), 'event_date', true)
];
}
return $pieces;
}, 10, 2);
Override the schema type for specific posts:
add_filter('wp_schema_framework_post_type_override', function($type, $post_id, $post_type, $post) {
if (get_post_meta($post_id, 'page_type', true) === 'contact') {
return 'ContactPage';
}
return $type;
}, 10, 4);
The ProductProvider automatically detects WooCommerce, Easy Digital Downloads, and BigCommerce products.
Note: To avoid conflicts, ProductProvider automatically disables itself when WooCommerce's built-in schema is active. To force wp-schema to handle product schema instead:
// Disable WooCommerce's built-in structured data
add_filter('woocommerce_structured_data_disable', '__return_true');
// Or tell wp-schema that WooCommerce schema is not active
add_filter('wp_schema_framework_woocommerce_schema_active', '__return_false');
You can also integrate custom e-commerce solutions:
// Mark custom post type as product
add_filter('wp_schema_framework_is_product', function($is_product, $post_id, $context) {
return get_post_type($post_id) === 'my_product_type';
}, 10, 3);
// Provide product data for custom e-commerce
add_filter('wp_schema_framework_get_product_data', function($data, $post_id) {
if (get_post_type($post_id) !== 'my_product_type') {
return $data;
}
return [
'name' => get_the_title($post_id),
'price' => get_post_meta($post_id, 'price', true),
'currency' => 'USD',
'availability' => 'https://schema.org/InStock',
'sku' => get_post_meta($post_id, 'sku', true),
'brand' => get_post_meta($post_id, 'brand', true),
'aggregateRating' => [
'ratingValue' => get_post_meta($post_id, 'rating', true),
'reviewCount' => get_post_meta($post_id, 'review_count', true),
],
];
}, 10, 2);
The EventProvider automatically detects The Events Calendar, Events Manager, Modern Events Calendar, and Event Organiser. You can also integrate custom event solutions:
// Mark custom post type as event
add_filter('wp_schema_framework_is_event', function($is_event, $post_id, $context) {
return get_post_type($post_id) === 'my_event_type';
}, 10, 3);
// Provide event data for custom events
add_filter('wp_schema_framework_get_event_data', function($data, $post_id) {
if (get_post_type($post_id) !== 'my_event_type') {
return $data;
}
return [
'name' => get_the_title($post_id),
'description' => get_the_excerpt($post_id),
'startDate' => get_post_meta($post_id, 'event_start', true), // ISO 8601 format
'endDate' => get_post_meta($post_id, 'event_end', true),
'eventStatus' => 'https://schema.org/EventScheduled',
'eventAttendanceMode' => 'https://schema.org/OfflineEventAttendanceMode',
'location' => [
'name' => get_post_meta($post_id, 'venue_name', true),
'address' => [
'streetAddress' => get_post_meta($post_id, 'venue_address', true),
'addressLocality' => get_post_meta($post_id, 'venue_city', true),
'addressRegion' => get_post_meta($post_id, 'venue_state', true),
'postalCode' => get_post_meta($post_id, 'venue_zip', true),
],
'type' => 'Place'
],
'offers' => [
'price' => get_post_meta($post_id, 'ticket_price', true),
'currency' => 'USD',
'availability' => 'https://schema.org/InStock',
],
];
}, 10, 2);
Create schema providers by implementing SchemaProviderInterface
:
<?php
namespace MyPlugin\Schema;
use BuiltNorth\WPSchema\Contracts\SchemaProviderInterface;
class MySchemaProvider implements SchemaProviderInterface
{
public function can_provide(string $context): bool
{
// Return true if this provider can generate schema for the current context
return $context === 'singular' && get_post_type() === 'my_post_type';
}
public function get_pieces(string $context): array
{
// Return array of schema pieces
return [
[
'@type' => 'Thing',
'@id' => get_permalink() . '#my-thing',
'name' => get_the_title()
]
];
}
public function get_priority(): int
{
// Return priority for ordering (lower = higher priority)
return 20;
}
}
- OrganizationProvider: Organization/LocalBusiness schema with support for all organization types
- WebsiteProvider: WebSite schema with site-wide metadata and SearchAction for sitelinks
- ArticleProvider: Article, BlogPosting, and NewsArticle schema for posts
- ProductProvider: Product schema with auto-detection for WooCommerce, Easy Digital Downloads, and BigCommerce
- EventProvider: Event schema with auto-detection for The Events Calendar, Events Manager, Modern Events Calendar, and Event Organiser
- AuthorProvider: Person schema for post authors
- NavigationProvider: SiteNavigationElement schema from WordPress menus
- PageTypeProvider: Specialized page types (ContactPage, AboutPage, FAQPage, etc.)
- WebPageProvider: Standard WebPage schema for pages
- ArchiveProvider: CollectionPage and ItemList for category, tag, and date archives
- SearchResultsProvider: SearchResultsPage with search action and results
- MediaProvider: ImageObject, VideoObject, and AudioObject for attachments
- CommentProvider: Comment schema added to posts and pages
- LogoProvider: Organization logo from WordPress site logo/custom logo
- SiteIconProvider: Site icon/favicon added to WebSite schema
- GenericSchemaProvider: Handles custom schema types via post meta and filters
The package outputs clean schema with proper relationships using the @graph format:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "My Organization",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"name": "My Site",
"publisher": { "@id": "https://example.com/#organization" },
"image": {
"@type": "ImageObject",
"url": "https://example.com/icon.png"
}
},
{
"@type": "Article",
"@id": "https://example.com/post/#article",
"headline": "Article Title",
"author": { "@id": "https://example.com/#author-1" },
"publisher": { "@id": "https://example.com/#organization" },
"comment": [
{
"@type": "Comment",
"author": { "@type": "Person", "name": "Commenter" },
"text": "Great article!"
}
]
},
{
"@type": "Person",
"@id": "https://example.com/#author-1",
"name": "Author Name",
"url": "https://example.com/author/authorname/"
}
]
}
The system recognizes these contexts for schema generation:
home
- Front pagesingular
- Individual posts/pagesarchive
- Archive pages (categories, tags, dates, authors, custom taxonomies)search
- Search results pages404
- 404 error pagesattachment
- Media/attachment pages
The framework provides extensive hooks and filters for customization. Here are the most commonly used:
wp_schema_framework_register_providers
- Register custom providerswp_schema_framework_ready
- Framework initialization completewp_schema_framework_before_output
- Before schema is output to pagewp_schema_framework_after_output
- After schema has been output
wp_schema_framework_output_enabled
- Enable/disable schema output globallywp_schema_framework_context
- Override detected page contextwp_schema_framework_pieces
- Modify final schema pieces arraywp_schema_framework_graph
- Modify complete schema graph before outputwp_schema_framework_json_output
- Modify final JSON-LD string before outputwp_schema_framework_piece_{type}
- Modify specific schema piece (e.g.,article
,product
)wp_schema_framework_piece_id_{id}
- Modify schema piece by ID (e.g.,#organization
)
wp_schema_framework_organization_data
- Modify organization schemawp_schema_framework_organization_type
- Override organization typewp_schema_framework_website_data
- Modify website schemawp_schema_framework_website_can_provide
- Control website schema outputwp_schema_framework_article_data
- Modify article schemawp_schema_framework_webpage_data
- Modify webpage schemawp_schema_framework_author_data
- Modify author/person schemawp_schema_framework_product_data
- Modify product schemawp_schema_framework_event_data
- Modify event schemawp_schema_framework_archive_data
- Modify archive schemawp_schema_framework_search_results_data
- Modify search results schemawp_schema_framework_media_data
- Modify media schemawp_schema_framework_page_type_data
- Modify specialized page type schema
wp_schema_framework_post_type_override
- Override schema type for specific postswp_schema_framework_post_type_mapping
- Map post types to schema typeswp_schema_framework_post_description
- Provide custom post descriptionswp_schema_framework_homepage_type
- Override homepage schema typewp_schema_framework_homepage_data
- Modify homepage schema data
wp_schema_framework_is_product
- Custom product detectionwp_schema_framework_is_event
- Custom event detectionwp_schema_framework_get_product_data
- Provide custom product datawp_schema_framework_get_event_data
- Provide custom event data
wp_schema_framework_woocommerce_schema_active
- Override WooCommerce conflict detectionwp_schema_framework_tribe_events_schema_active
- Override The Events Calendar conflict detection
wp_schema_framework_faq_items
- Provide FAQ items for FAQPagewp_schema_framework_collection_items
- Provide collection itemswp_schema_framework_gallery_items
- Provide gallery imageswp_schema_framework_available_types
- Modify available schema types for UI
📚 View Complete Hooks Reference - Comprehensive documentation with examples and use cases
Access available schema types for UI elements like dropdowns in admin settings:
// Get available schema types
$types = apply_filters('wp_schema_framework_available_types', []);
// Returns array with label, value, category, subcategory, and parent fields
// Example: Creating a schema type dropdown in admin
echo '<select name="schema_type">';
foreach ($types as $type) {
echo sprintf(
'<option value="%s">%s</option>',
esc_attr($type['value']),
esc_html($type['label'])
);
}
echo '</select>';
The registry now includes category metadata for better organization:
// Get types organized by category
$type_registry = BuiltNorth\WPSchema\App::instance()->get_type_registry();
$categorized = $type_registry->get_categorized_types();
// Returns structure like:
// [
// 'Organization' => [
// 'LocalBusiness' => [...types],
// 'FoodEstablishment' => [...types],
// 'Store' => [...types]
// ],
// 'CreativeWork' => [
// 'Article' => [...types],
// 'WebPage' => [...types]
// ]
// ]
Get filtered sets of types for specific use cases:
$type_registry = BuiltNorth\WPSchema\App::instance()->get_type_registry();
// Get only organization/business types (for organization settings)
$org_types = $type_registry->get_organization_types();
// Get organization types categorized for dropdowns
$categorized_org = $type_registry->get_categorized_organization_types();
// Returns user-friendly categories like:
// - General Business
// - Food & Dining
// - Retail Stores
// - Home & Construction
// - Medical Services
// etc.
// Get content-focused types (for posts/pages)
$content_types = $type_registry->get_content_types();
// Returns Article, BlogPosting, HowTo, Product, Event, etc.
The registry provides 250+ comprehensive schema types including:
- Content Types: Article, BlogPosting, NewsArticle, HowTo, QAPage, TechArticle, Report
- Business & Services: LocalBusiness subtypes, home services (Plumber, Electrician, etc.), professional services (Attorney, Dentist, etc.)
- Commerce: Product, Service, Store types, automotive services
- Places & Venues: Restaurant, Hotel, Museum, Zoo, Park, recreational facilities
- Events: 20+ event subtypes including BusinessEvent, MusicEvent, SportsEvent
- Media & Creative Works: Various media types, artwork, publications
- Digital Products: SoftwareApplication, MobileApplication, WebApplication
- Geographic: Country, City, Mountain, Beach, tourist destinations
All types can be extended/consolidated via the wp_schema_framework_type_registry_types
filter.
Add custom schema types to the registry:
// Add custom schema types with categories
add_filter('wp_schema_framework_type_registry_types', function($types) {
// Add custom types with category metadata
$types[] = [
'label' => 'Podcast',
'value' => 'PodcastSeries',
'category' => 'CreativeWork',
'subcategory' => 'PodcastSeries'
];
$types[] = [
'label' => 'Coworking Space',
'value' => 'CoworkingSpace',
'category' => 'Organization',
'subcategory' => 'LocalBusiness',
'parent' => 'LocalBusiness'
];
$types[] = [
'label' => 'Webinar',
'value' => 'Webinar',
'category' => 'Event',
'subcategory' => 'Event'
];
return $types;
});
Replace with a custom curated list:
// Replace entire registry with your own curated list
add_filter('wp_schema_framework_type_registry_types', function($types) {
// Ignore default types and define only what you need
return [
['label' => 'Article', 'value' => 'Article'],
['label' => 'Product', 'value' => 'Product'],
['label' => 'LocalBusiness', 'value' => 'LocalBusiness'],
['label' => 'Event', 'value' => 'Event'],
['label' => 'Person', 'value' => 'Person'],
['label' => 'Organization', 'value' => 'Organization'],
];
});
Remove or modify existing types:
// Remove specific schema types from existing list
add_filter('wp_schema_framework_type_registry_types', function($types) {
// Remove all Action types (not typically used as main entity)
$types = array_filter($types, function($type) {
return !str_contains($type['value'], 'Action');
});
// Remove specific types
$remove_types = ['Cemetery', 'Canal', 'Mountain'];
$types = array_filter($types, function($type) use ($remove_types) {
return !in_array($type['value'], $remove_types);
});
return $types;
});
Organize types for better UX using built-in categories:
// Create optgroups using the built-in category metadata
$type_registry = BuiltNorth\WPSchema\App::instance()->get_type_registry();
$categorized = $type_registry->get_categorized_organization_types();
echo '<select name="organization_type">';
foreach ($categorized as $category => $types) {
echo '<optgroup label="' . esc_attr($category) . '">';
foreach ($types as $type) {
echo sprintf(
'<option value="%s">%s</option>',
esc_attr($type['value']),
esc_html($type['label'])
);
}
echo '</optgroup>';
}
echo '</select>';
- PHP 8.1+
- WordPress 6.0+
The main application class provides static methods for framework interaction:
// Initialize the framework
BuiltNorth\WPSchema\App::initialize();
// Register a provider programmatically
BuiltNorth\WPSchema\App::register_provider('my_provider', 'MyPlugin\MyProvider');
// Get the singleton instance
$app = BuiltNorth\WPSchema\App::instance();
// Check if initialized
if ($app->is_initialized()) {
// Access services
$registry = $app->get_registry();
$graph_builder = $app->get_graph_builder();
$type_registry = $app->get_type_registry();
}
Manages the schema graph with piece management:
use BuiltNorth\WPSchema\Graph\SchemaGraph;
use BuiltNorth\WPSchema\Graph\SchemaPiece;
$graph = new SchemaGraph();
// Add pieces
$piece = new SchemaPiece('my-id', 'Article', ['headline' => 'Title']);
$graph->add_piece($piece);
// Query pieces
$article = $graph->get_piece('my-id');
$all_articles = $graph->get_pieces_by_type('Article');
// Convert to output
$array = $graph->to_array();
$json = $graph->to_json();
Represents individual schema pieces:
use BuiltNorth\WPSchema\Graph\SchemaPiece;
// Create a piece
$piece = new SchemaPiece('article-1', 'Article');
// Set properties
$piece->set('headline', 'My Article')
->set('datePublished', '2024-01-01')
->add_reference('author', 'person-1');
// Access properties
$headline = $piece->get('headline');
$has_author = $piece->has('author');
// Convert to array
$data = $piece->to_array();
See CONTRIBUTING.md for details on how to contribute to this project.
This package is licensed under the GPL version 2 or later. See LICENSE.md for details.
For support and questions, please open an issue on GitHub.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The schema markup generated by this package is not guaranteed to result in rich snippets or enhanced search results. Search engines determine rich snippet eligibility based on many factors including content quality, site authority, and their own algorithms. Always validate your schema output using official testing tools and follow search engine guidelines.
This package has not been fully tested across all WordPress configurations and use cases. The generated schema may not be accurate or complete for all scenarios. Users are responsible for validating and testing the schema output for their specific implementations.