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.

by KNI · github.com/kni-labs/schema-patcher-for-yoast-seo · website

1stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/kni-labs/schema-patcher-for-yoast-seo/archive/refs/heads/main.zip

Readme

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.

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: initwp_loadedtemplate_redirectwpwp_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., locallocal_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.

Read the full README on GitHub →