A Composer plugin that moves your dependencies into a namespace of your own, so that your WordPress plugin or theme cannot collide with anybody else's.
WordPress loads every active plugin into one PHP process, and PHP has one global namespace. Your
plugin requires guzzlehttp/guzzle 7. Another plugin on the same site bundles Guzzle 6. Whichever
autoloader registers first wins, the other plugin gets a class it does not recognise, and something
fatals — on a site you do not control and cannot test against.
PHP Scoper solves this by rewriting your dependencies under
a prefix that nobody else will use. But it prefixes everything it can see, WordPress's own
functions and classes included, so a scoped plugin ends up calling
MyPlugin\Deps\add_action() — which does not exist.
This plugin is PHP Scoper wired up correctly for WordPress. It ships a generated database of every symbol declared by WordPress, WooCommerce, Action Scheduler and WP-CLI, and keeps those unprefixed while everything else moves into your namespace.
PHP 8.2 or newer, and Composer 2.6 or newer.
Install the plugin globally, pinned to a major version:
composer global config --no-plugins allow-plugins.wpify/scoper true
composer global require wpify/scoper:^4.0In your plugin, declare the dependencies you want scoped in composer-deps.json — same format as
composer.json, but it holds only the dependencies that get prefixed:
{
"require": {
"guzzlehttp/guzzle": "^7.0"
},
"config": {
"platform": {
"php": "8.2.0"
}
}
}Then tell the plugin what namespace to use, in your ordinary composer.json:
{
"config": {
"allow-plugins": {
"wpify/scoper": true
}
},
"extra": {
"wpify-scoper": {
"prefix": "MyPlugin\\Deps"
}
}
}Run Composer:
composer installThe scoped tree is written to deps/. Load its autoloader alongside Composer's own:
<?php
require_once __DIR__ . '/deps/scoper-autoload.php';
require_once __DIR__ . '/vendor/autoload.php';
$client = new \MyPlugin\Deps\GuzzleHttp\Client();That class is yours alone. The other plugin's Guzzle no longer matters.
Full walkthrough with the verification steps: Getting started.
Write the scoped tree to vendor-prefixed/ instead. Since October 2024 every new plugin
submitted to the WordPress.org directory is run through
Plugin Check first, and an error blocks the
submission until it is fixed. Plugin Check skips vendor-prefixed/ — it carries the name in its
ignore list precisely so that prefixed dependencies do not raise false positives — but it does scan
deps/, so a scoped Guzzle gets read as your code and reported for every file_get_contents() and
curl_* call in it, none of which you can fix.
{
"extra": {
"wpify-scoper": {
"prefix": "MyPlugin\\Deps",
"folder": "vendor-prefixed"
}
}
}require_once __DIR__ . '/vendor-prefixed/scoper-autoload.php';
require_once __DIR__ . '/vendor/autoload.php';Add /vendor-prefixed/ to .gitignore, and make sure your release build does not strip it. The
full picture — what the exemption does and does not cover, and the checklist to run before
submitting — is in Publishing to WordPress.org.
| Getting started | Install it and scope your first dependency, start to finish. |
| Configuration | Every extra.wpify-scoper key, every command, every environment variable. |
| Troubleshooting | Symptom, cause, fix. Start here when something is wrong. |
| Publishing to WordPress.org | Why the scoped tree belongs in vendor-prefixed/, and the pre-submission checklist. |
| Deployment | What to commit, CI recipes, Bedrock, multi-plugin repositories. |
| Customizing php-scoper | scoper.custom.php and patchers, for dependencies that need help. |
| How it works | The symbol lists, the pipeline, and why the scoped tree is swapped rather than written in place. |
| Upgrading to 4.0 | Coming from 3.2 or earlier. |
Bug reports, symbol-list regressions and pull requests are welcome. See
CONTRIBUTING.md — in particular for what sources/ is, why require-dev
contains WordPress, and how to regenerate the symbol lists.
Security issues should not go in a public issue. See SECURITY.md.
GPL-2.0-or-later. Copyright © 2021–2026 WPify s.r.o. and contributors.