builtnorth / wp-schema
Comprehensive schema generation for WordPress with SEO optimization and logo support
Requires
- php: >=8.1
Requires (Dev)
- 10up/wp_mock: ^1.0
- builtnorth/coding-standards: ^1.0
- mockery/mockery: ^1.5
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2025-08-26 17:05:20 UTC
README
A comprehensive, WordPress schema generation framework with a clean provider-based architecture.
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
Features
- 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
Installation
# Via Composer
composer require builtnorth/wp-schema
Quick Start
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.
For Plugin Developers
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' ); });
Simple Filter Approach
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);
Schema Type Override
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);
Product Schema Integration
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);
Event Schema Integration
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);
Provider Interface
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; } }
Built-in Providers
Core Content Providers
- 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
Page Type Providers
- 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
Enhancement Providers
- 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
Schema Output
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/" } ] }
Contexts
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
Available Hooks
Hooks & Filters
The framework provides extensive hooks and filters for customization. Here are the most commonly used:
Actions
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
Core Filters
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
)
Provider Data Filters
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
Post Type Filters
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
Detection Filters
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
Plugin Conflict Filters
wp_schema_framework_woocommerce_schema_active
- Override WooCommerce conflict detectionwp_schema_framework_tribe_events_schema_active
- Override The Events Calendar conflict detection
Specialized Filters
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
Schema Type Registry
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>';
Categorized Schema Types
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] // ] // ]
Specialized Type Getters
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.
Extending the Type Registry
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>';
Requirements
- PHP 8.1+
- WordPress 6.0+
API Reference
App Class
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(); }
SchemaGraph Class
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();
SchemaPiece Class
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();
Contributing
See CONTRIBUTING.md for details on how to contribute to this project.
License
This package is licensed under the GPL version 2 or later. See LICENSE.md for details.
Support
For support and questions, please open an issue on GitHub.
Disclaimer
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.