WP Manifestindependent plugin directory
manifest / seo / wp-schemata

Schemata

Clean, opinionated JSON-LD schema.org emitter for WordPress. Organization + WebSite + BreadcrumbList everywhere; pluggable Event / NewsArticle schemas. MIT.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/rennerdo30/wp-schemata/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Clean, opinionated JSON-LD schema.org emitter for WordPress.

Yoast bundles schema with 30+ unrelated features. Schemata is just schema, done right.

Schemata auto-emits the schemas every site needs (Organization, WebSite, BreadcrumbList) and ships pluggable per-post-type schemas (Event, NewsArticle, Article) with sensible defaults — no SEO plugin, no ACF dependency, no upsell prompts.

Why Schemata

Yoast RankMath Schemata
Plugin scope SEO + schema + redirects + breadcrumbs + readability + … SEO + schema + analytics + AI + redirects + … Schema only
Free Event schema No Yes (often invalid) Yes (validated)
Themeable via filters Limited Limited Every payload
Required-field validator No No Yes (logs in WP_DEBUG)
Dependencies Yoast\WP\Free, etc. RankMath core, etc. None
LOC 100k+ 60k+ < 900

Quick start

  1. Drop the plugin into wp-content/plugins/schemata/ (or upload the zip).
  2. Activate.
  3. Settings → Schemata → fill in Organization name, logo, and Same-As URLs.
  4. Done — JSON-LD ships on every front-end page.

What's auto-emitted

Schema When
Organization every front-end page
WebSite every front-end page (with searchbox)
BreadcrumbList archives + singles (toggle in settings)
Event is_singular($event_post_types)
NewsArticle is_singular($news_post_types)
Article is_singular('post') fallback

The post-type → schema-class map is configurable in Settings → Schemata, or at runtime via the schemata/post_type_schema filter (see below).

Customizing the payloads

Every schema runs through two filters before emit. Filters carry the (array $data, ?int $post_id) signature; $post_id is null on site-wide schemas (Organization, WebSite).

// Append a custom field to the Event schema for a singular event:
add_filter('schemata/event/data', function (array $data, ?int $post_id): array {
    $data['offers'] = [
        '@type'         => 'Offer',
        'url'           => get_field('buy_link', $post_id),
        'price'         => (string) get_field('ticket_price', $post_id),
        'priceCurrency' => 'JPY',
        'availability'  => 'https://schema.org/InStock',
    ];
    return $data;
}, 10, 2);

// Inject sameAs URLs from theme Customizer mods on Organization:
add_filter('schemata/organization/data', function (array $data): array {
    $data['sameAs'] = array_filter([
        get_theme_mod('social_twitter'),
        get_theme_mod('social_facebook'),
        get_theme_mod('social_instagram'),
    ]);
    return $data;
});

// Suppress the WebSite searchbox on a specific theme:
add_filter('schemata/website/should_emit', '__return_false');

Filter slugs

Per-schema:

  • schemata/organization/{data,should_emit}
  • schemata/website/{data,should_emit}
  • schemata/breadcrumb_list/{data,should_emit}
  • schemata/event/{data,should_emit}
  • schemata/news_article/{data,should_emit}
  • schemata/article/{data,should_emit}

Cross-cutting:

  • schemata/post_type_schema — override the post-type → schema-class map at runtime. Receives the schema slug ('event', 'news_article', 'article', '' for none) and the post type; return the slug to use.
  • schemata/emit_custom — fires for each emitted schema with the slug + post ID. Useful for logging or appending side-channel JSON-LD.

Validator

Schemata\Schema\Validator::missing($payload) returns the list of missing required fields per @type. The emitter calls Validator::validate() on every payload before output. In WP_DEBUG mode any missing field is logged to error_log so theme authors can spot bad data without the page breaking.

// Build a payload, then verify before sending it somewhere else:
$missing = \Schemata\Schema\Validator::missing($data);
if ($missing !== []) {
    error_log('Bad event data: missing ' . implode(', ', $missing));
}

Required-field rules live in src/Schema/Validator.php.

Defaults

Schemata reads sensible fallbacks if Settings is empty:

  • Organization name → get_bloginfo('name')
  • Organization URL → home_url()
  • Organization logo → get_site_icon_url() → custom-logo theme mod
  • Same-As URLs → empty
  • Post-type map → post → NewsArticle, event → Event, news → NewsArticle, page → none

Known limitations

  • No Product / Recipe / LocalBusiness schemas yet. Add a custom emitter via schemata/emit_custom if you need them, or open an issue.
  • One schema per post type. A post can only resolve to one of Event / NewsArticle / Article. To emit multiple types per page, add the extra payload via the data filter on whatever schema is already active.
  • No archive-level schema beyond BreadcrumbList. CollectionPage for category archives is a possible future addition.
  • Validator is required-fields only. It catches missing fields, not invalid types or malformed URLs. Use Google's Rich Results Test for full structural validation.

Tests

composer install
composer test
# or, no composer:
php tests/ValidatorTest.php

License

MIT — Copyright 2026 Renner.

See LICENSE.