automattic/jetpack-autoloader
A custom Composer autoloader for WordPress plugins that prevents package version conflicts by building an in-memory classmap and always loading the latest available version of each class, no matter which plugin loads it.
This is not a Laravel package in the usual sense — it has no service provider, no config file, and nothing to register in config/app.php. It is a build-time autoloader generator plus a runtime shim for WordPress plugins that bundle Composer dependencies. If you are a Laravel developer, your day-to-day use of it is almost certainly one of two things:
Either way, treat it as packaging infrastructure, not application code.
Minimal setup — in the plugin's composer.json:
{
"require": {
"automattic/jetpack-autoloader": "^5"
},
"config": {
"vendor-dir": "vendor"
}
}
The vendor directory must be the default vendor — the generated entrypoint hardcodes its own relative location assumptions.
Then:
composer install --no-dev --optimize-autoloader
In your main plugin file, bootstrap through the generated entrypoint instead of vendor/autoload.php:
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload_packages.php';
Where to look first
vendor/autoload_packages.php — the shim your plugin file requires. Read it once; it shows the Autoloader class being invoked and the JETPACK_AUTOLOAD_* constants it probes.vendor/composer/jetpack_autoload_classmap.php — the generated classmap, including package version numbers per entry. This is the file to inspect when the "wrong" version of a class is loading.vendor/composer/jetpack_autoload_filemap.php — the equivalent for files autoload entries.First use case to validate: install two copies of the same plugin-ecosystem dependency at different versions, activate them together, and confirm the newer class wins. If you only ever activate one plugin, you will never observe the behaviour this package exists for — and you will also never see its failure modes.
Single owner, many consumers. The standard pattern is one plugin per site bundles the autoloader, but every plugin that requires the shared dependency also bundles it. The package only guarantees "latest version wins" when all plugins in the set use this autoloader. One plugin using stock Composer autoloading breaks the contract for the whole site.
Always ship an optimized build. Run composer install -o / composer dump-autoload -o in your release pipeline. With -o, PSR-4 and PSR-0 namespaces are converted to classmaps, which is what the version-resolution logic operates on. Unoptimized builds only convert PSR-0 to classmap and leave PSR-4 as a runtime prefix lookup, which weakens the version-resolution guarantees.
Use classmap autoloading for your own code too. The package's own guidance is that new code should use classmap autoloading so filenames can follow WordPress Coding Standards (class-my-thing.php) rather than PSR-4's MyThing.php. In practice:
{
"autoload": {
"classmap": ["src/"]
}
}
and let -o do the work. This also means your file layout is not part of your public API, which is useful when the same class is bundled by multiple plugins.
Defer everything to plugins_loaded. Do not instantiate package classes at file scope in your main plugin file. Hook your bootstrap:
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload_packages.php';
add_action( 'plugins_loaded', function () {
( new My_Plugin\Bootstrap() )->run();
} );
The autoloader needs every participating plugin to have been registered (via plugins_loaded-time plugin discovery / its transient plugin list) before a class is resolved, so that it knows about the highest available version. Loading early is the single most common source of "why did the old class run".
Development versions. During local development, when a dependency is checked out at dev-main or 9999999-dev, define the constant before the autoloader is required:
define( 'JETPACK_AUTOLOAD_DEV', true );
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload_packages.php';
Otherwise semantic versioning applies and your working-copy package can lose to a released 1.x bundled by another active plugin. Never define this in production code paths.
CI integration. Because vendor/autoload_packages.php and the two vendor/composer/jetpack_autoload_*.php files are generated artifacts that encode version numbers, they must be regenerated on every release. A minimal check for a release build:
composer install --no-dev --optimize-autoloader
php -l vendor/autoload_packages.php
grep -c "'version'" vendor/composer/jetpack_autoload_classmap.php
If that grep count is zero, you have a stale or hand-edited classmap and version resolution will be wrong.
Laravel-specific integration tip. Do not add this to a Laravel application's root composer.json alongside the normal autoloader. Two Composer autoloaders on one request is exactly the conflict the package is designed to detect (JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS). Keep it scoped to a WordPress plugin's own composer.json, or to a package that is only ever installed inside a WordPress plugin. If you need to share code between a Laravel app and a WP plugin, build it as a plain library and let each host bring its own autoloader.
Moved or renamed class files break on update. The in-memory classmap is built the moment the autoloader loads, and paths are not refreshed after a plugin update within the same request. If a plugin update relocates a class file and that file is autoloaded afterwards, you get a fatal. Practical rule: never move, rename, or re-namespace a class in the same release in which other plugins may still hold the old path. Ship the new location, wait a release cycle, then delete the old file (or keep a thin forwarding file).
Package version numbers decide hierarchy, not release dates. If a class moves into a newer package that happens to carry a lower version number (0.1.0), it will be shadowed by the older code in the 2.0.1 package. When extracting shared classes into new packages, start the new package at a version above every package that previously held those classes.
The plugin-list transient is a debugging trap. The autoloader caches the list of available plugins in a transient to avoid a filesystem scan on every request. That cache can hide early-loading bugs, making them appear intermittently — typically right after the cache is invalidated. When chasing a phantom, delete the relevant transients (or flush the object cache) and retry; a bug that only appears on a cold cache is almost always an early-load.
Two debug constants, use them deliberately. Both are booleans defined before requiring the autoloader:
define( 'JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS', true ); // flags classes loaded before plugins_loaded
define( 'JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS', true ); // flags stock Composer autoloaders in the stack
Leave both off in production: they add overhead and their output is not user-facing-safe.
Diagnosing "wrong version loaded". Read vendor/composer/jetpack_autoload_classmap.php in each active plugin and compare the version recorded for the same class key. If two plugins record different versions and the lower one wins, either the higher-version plugin loaded late, or it is not using this autoloader at all. Check with the conflicting-loaders constant before touching anything else.
version in composer.json matters. The autoloader reads version data to make its decision. If you build from a source checkout with no tags and no version field, Composer may record dev-* or nothing useful, and resolution becomes arbitrary. For release artifacts, always build from a tagged state.
The GitHub repo is a read-only mirror. Issue tracking and development happen in the Automattic/jetpack monorepo, and the mirror's "last release" metadata is not authoritative — the changelog runs far past it. Check the real installed version with composer show automattic/jetpack-autoloader and read the monorepo changelog before concluding a bug is unfixed. Version 5.x is current; ^2 in the README is historical.
Extension points are thin by design. There is no filter/event API. Your levers are: the two dev/debug constants, the requirement that vendor is the vendor dir, and what you put in the classmap. If you need finer control than that, the correct move is to stop using this package rather than to patch the generated files — patched classmaps will be silently overwritten on the next composer install.
How can I help you explore Laravel packages today?