WP Manifestindependent plugin directory
manifest / seo / schema-patcher-for-yoast-seo

Schema Patcher for Yoast SEO

A WordPress plugin that patches Yoast SEO's schema graph by queuing schema "patches" from PHP or ACF-authored JSON and applying them deterministically.

★ 1stars

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/kni-labs/schema-patcher-for-yoast-seo/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.

A WordPress plugin that patches Yoast SEO's schema graph by queuing schema "patches" from PHP or ACF-authored JSON and applying them deterministically.

Requirements

  • WordPress 5.9+
  • PHP 7.4+
  • Yoast SEO (required)
  • Advanced Custom Fields Pro (required)

Compatibility

Supported:

  • WordPress 5.9+
  • PHP 7.4 - 8.4
  • Yoast SEO 19.0+
  • ACF Pro 5.9+

Tested on:

  • WordPress 5.9, 6.0, 6.4
  • PHP 7.4, 8.0, 8.1, 8.4
  • Yoast SEO 19.7, 21.0
  • ACF Pro 6.0

Installation

  1. Upload the plugin folder to /wp-content/plugins/schema-patcher-for-yoast-seo/
  2. Activate the plugin through the 'Plugins' menu in WordPress
  3. Ensure both Yoast SEO and ACF Pro are installed and activated

Verification

After installation, verify the plugin works:

  1. Check dependencies: Visit Plugins page.

    • Expected: No error notices and plugin is active.
  2. Test PHP API: Add to theme's functions.php.

    add_action('wp_head', function() {
        patch_yoast_schema(['@type' => 'Organization', 'name' => 'Test']);
    });
    • Expected: No PHP errors in debug.log.
  3. Test ACF UI: Edit any Page.

    • Expected: "Schema Patcher for Yoast SEO" field group appears.
  4. Check schema output: View page source and search for application/ld+json.

    • Expected: JSON-LD block present (may need Yoast configured first).

Usage

CRITICAL: Timing Requirements

The patch_yoast_schema() function MUST be called BEFORE the wp_head() hook fires.

Schema output happens during wp_head() (which runs inside get_header() in template files). Any patches queued after this point will be silently ignored.

✅ CORRECT Usage Patterns

In functions.php or plugin files:

// Option 1: template_redirect hook (recommended for conditional logic)
add_action('template_redirect', 'my_schema_patches');
function my_schema_patches() {
    if (is_singular('casestudy')) {
        patch_yoast_schema([/* ... */]);
    }
}

// Option 2: wp hook (earlier, still has post data)
add_action('wp', 'my_schema_patches');
function my_schema_patches() {
    patch_yoast_schema([/* ... */]);
}

// Option 3: init/wp_loaded (for global patches)
add_action('wp_loaded', 'my_global_schema');
function my_global_schema() {
    patch_yoast_schema([/* ... */]);
}

In template files:

<?php
// MUST come BEFORE get_header()
patch_yoast_schema([/* ... */]);

get_header(); // wp_head() fires here
?>

❌ INCORRECT Usage (Will Fail)

<?php
get_header(); // wp_head() already fired

// TOO LATE - patch will be ignored and error logged
patch_yoast_schema([/* ... */]);

get_footer();
?>
// TOO LATE - these hooks fire after wp_head
add_action('wp_footer', function() {
    patch_yoast_schema([/* ... */]); // ❌ Will fail
});

add_action('loop_start', function() {
    patch_yoast_schema([/* ... */]); // ❌ Will fail
});

Debugging Timing Issues

If patches aren't appearing in your schema output:

  1. Check browser console or WordPress debug log for timing warnings.
  2. Enable WP_DEBUG to see detailed error messages.
  3. Verify hook timing: init → wp_loaded → template_redirect → wp → wp_head (patches must be queued before this).
  4. In templates, ensure patch_yoast_schema() appears before get_header().

Via PHP (Programmatic)

Use the patch_yoast_schema() function anywhere in your theme or plugin (before wp_head):

// Simple patch with JSON string
patch_yoast_schema('{"@type": "Organization", "name": "My Company"}');

// Patch with PHP array (simple format)
patch_yoast_schema([
    '@type' => 'WebPage',
    'description' => 'Custom description'
]);

// Patch with PHP array (JSON-LD format with @graph wrapper - also supported)
patch_yoast_schema([
    '@context' => 'https://schema.org',
    '@graph' => [
        [
            '@type' => 'Article',
            'headline' => 'My Article',
            'description' => 'Article description'
        ],
        [
            '@type' => 'Dataset',
            'name' => 'My Data'
        ]
    ]
]);

// Targeted patch (merge into existing node)
patch_yoast_schema([
    '@target' => 'Organization',
    'address' => [
        '@type' => 'PostalAddress',
        'streetAddress' => '123 Main St'
    ]
]);

// Graph upsert (by @id)
patch_yoast_schema([
    '@id' => 'https://example.com/#custom-software',
    '@type' => 'SoftwareApplication',
    'name' => 'My App'
]);

// With options
patch_yoast_schema($json, [
    'order' => 100,  // Run early
    'merge' => [
        'offers.price' => 'fill',  // Only set if null
        '@type' => 'union'  // Merge as unique array
    ],
    'remove' => [
        'internalProperty',  // Remove this key
        'offers[].invalidKey'  // Remove from all offer items
    ],
    'debug' => true  // Log to error_log
]);

Via ACF (Admin UI)

  1. Edit any Page in WordPress.
  2. Scroll to the "Schema Patcher for Yoast SEO" field group.
  3. Click "Add Patch".
  4. Configure the patch:
    • Enabled: Toggle to enable/disable.
    • Attach To: Target (WebPage, Organization#primary or full URL).
    • Order: Queue priority (lower = earlier).
    • JSON Patch: Your schema JSON.
    • Merge Overrides: Path-specific merge strategies.
    • Remove Paths: Paths to remove before merging.

Features

Input Formats

  • JSON string: Auto-parsed, supports objects, arrays and JSON-LD @graph wrappers.
  • PHP array: Native associative or indexed arrays with optional @context and @graph wrappers.
  • UTF-8 BOM: Automatically stripped from JSON strings.
  • @context: Automatically removed (not needed in Yoast graph).
  • @graph wrapper: Automatically extracted from both JSON and PHP array formats.

Targeting

  • By Type: WebPage or Organization.
  • By Type + Fragment: Organization#primary (creates or updates @id fragment).
  • By URL: https://example.com/page/#softwareapp (exact @id match).
  • Graph Upsert: Nodes with @id but no @target are upserted to @graph.

Merge Strategies

Default behaviors:

  • @type: Union (unique values).
  • Scalars: Overwrite.
  • Objects: Deep merge.
  • Arrays: Append unique (dedupe by @id or JSON).

Override strategies:

  • overwrite: Replace value.
  • fill: Set only if target is null/missing.
  • union: Merge as unique array.
  • append_unique: Append with deduplication.
  • deep_merge: Recursive object merge.

Path Syntax

  • Dot notation: address.streetAddress.
  • Array wildcard: offers[].price or hasPart[].offers[].priceCurrency.
  • Remove paths: Delete keys at paths.

Development Setup

Prerequisites:

  • MySQL must be running - Tests require database connection
  • For Local by Flywheel: start your Local site before running tests
  • For MAMP/XAMPP/native MySQL: ensure MySQL service is running
  • Verify: mysql -u root -p -e "SHOW DATABASES;"

For plugin development, run the setup script to install dependencies and configure tests:

cd wp-content/plugins/schema-patcher-for-yoast-seo
bash local-setup.sh

This script will:

  1. Install Composer dependencies (PHPUnit, PHPStan, testing tools)
  2. Install WordPress test suite to /tmp/wordpress-tests-lib/
  3. Auto-detect your database configuration from wp-config.php
  4. Verify database connection (fails early with helpful error if MySQL not running)
  5. Create test database (e.g., local_test if your DB is local)
  6. Run all tests to verify setup

Database Auto-Detection:

  • Setup script reads DB_NAME, DB_USER, DB_PASSWORD, DB_HOST from your WordPress wp-config.php
  • For Local by Flywheel: automatically finds MySQL socket path
  • For standard MySQL: uses localhost or detected socket
  • Test database name: {DB_NAME}_test (e.g., local → local_test)
  • If tests fail with database errors: ensure MySQL is running and accessible

Test Commands:

composer test        # Run static analysis + all tests
composer test:unit   # Run unit tests only
composer test:wp     # Run integration tests only
composer analyze     # Run PHPStan static analysis

Fresh Install Test: To verify setup works from scratch:

bash test-fresh-install.sh

This removes vendor/ and composer.lock, then runs local-setup.sh to simulate a new developer cloning the repo.

Disabling the Plugin

Set environment variable or constant:

define('PATCH_YOAST_SCHEMA_DISABLED', true);

Or in .env:

PATCH_YOAST_SCHEMA_DISABLED=true

Development

Quick Start for New Developers

One command to set up everything:

cd wp-content/plugins/schema-patcher-for-yoast-seo
./local-setup.sh

That's it! The script will:

  1. ✅ Install Composer dependencies
  2. ✅ Set up WordPress test suite
  3. ✅ Run all tests to verify setup
  4. ✅ Show you're ready to develop

Output: ✅ Setup Complete! You're ready to develop.

Available Test Commands

composer test          # All tests
composer test:unit     # Unit tests only
composer test:wp       # Integration tests only
composer analyze       # PHPStan static analysis

Note: The install script may show warnings about database connection with Local by Flywheel. This is expected - the database is already created, and tests will work. If tests fail with database errors, see the troubleshooting section below.

Test Structure

  • tests/unit/ - Pure PHP unit tests (ingest, merge, path resolution)
  • tests/integration/ - WordPress integration tests (plugin, ACF, Yoast)
  • tests/bootstrap.php - Test bootstrap
  • tests/wp-test-stubs.php - Type stubs for IDE (Intelephense) - provides type definitions for WordPress test suite classes/functions that exist in /tmp/wordpress-tests-lib

Static Analysis

PHPStan is configured to analyze the plugin code (level 5) with WordPress stubs. It ignores test files since they depend on the dynamically-loaded WordPress test suite. The stub file provides IDE type hints without affecting runtime behavior.

Troubleshooting

Test Failures

Error: "Error establishing a database connection" or "Cannot connect to MySQL server"

  • Cause: MySQL is not running.
  • Solution:
    • For Local by Flywheel: Open Local app and start your site
    • For MAMP: Start MySQL service in MAMP control panel
    • For native MySQL: brew services start mysql or mysql.server start
  • Verify: mysql -u root -p -e "SHOW DATABASES;"

Error: "Unknown database 'local_test'" or similar test database missing

  • Cause: Test database wasn't created automatically.

  • Solution: Create it manually:

    mysql -u root -p -e "CREATE DATABASE local_test;"

    Replace local_test with {your_db_name}_test from wp-config.php

Error: "No such file or directory" for MySQL socket

  • Cause: Socket path changed or Local site not running.
  • Solution:
    • Ensure Local site is running
    • Delete test config and re-run setup: rm /tmp/wordpress-tests-lib/wp-tests-config.php && bash local-setup.sh

Plugin Not Loading

Error: "Function patch_yoast_schema() already exists"

  • Another plugin or theme defines this function.
  • Plugin automatically disables itself.

Error: "Yoast SEO plugin is required"

  • Install and activate Yoast SEO.

Error: "Advanced Custom Fields Pro is required"

  • Install and activate ACF Pro.

Patches Not Applying

  1. Check if patch is enabled in ACF.

  2. Verify JSON is valid - save triggers validation.

  3. Check target resolution - enable debug mode:

    patch_yoast_schema($json, ['debug' => true]);
  4. View error_log for debug messages.

ACF Fields Not Showing

  • Ensure you're editing a Page (not Post).
  • Plugin extends to Pages by default.
  • To add to other post types, use ACF location rules filter.

Architecture

Classes

  • Schema_Patcher_Yoast_Plugin - Dependency checks and initialization.
  • Schema_Patcher_Yoast_Ingest - Input normalization (JSON/array).
  • Schema_Patcher_Yoast_Queue - Patch queue management.
  • Schema_Patcher_Yoast_Merge - Merge engine (strategies and paths).
  • Schema_Patcher_Yoast_Target_Resolver - Target resolution.
  • Schema_Patcher_Yoast_Integration - Yoast filter hook.
  • Schema_Patcher_Yoast_ACF - ACF fields, validation and ingestion.

Hooks

  • plugins_loaded priority 1 - Plugin initialization.
  • acf/init - Field group registration.
  • wp priority 1 - Runtime ACF ingestion.
  • wpseo_schema_graph priority 9999 - Apply patches to Yoast graph.

License

GPL v2 or later

Support

For issues, questions, or contributions, visit the project repository.