Skip to content

Plugin authors and maintainers

plugin-load-order-probe

Find plugin code that depends on another plugin too early, including failures, skipped features and code that works only because of load order.

Kind
Open-source tool
Language
JavaScript
License
MIT
Status
First release
  • cli
  • load-order
  • php
  • static-analysis
  • woocommerce
  • wordpress
  • wordpress-plugin

Load order bugs

A WordPress add-on can work on one site and fail after a small change that appears unrelated. Consider a plugin whose main file declares class My_Gateway extends WC_Payment_Gateway. WordPress must already know WC_Payment_Gateway when that file is included. If the add-on loads before WooCommerce, PHP reaches the declaration before the parent class exists.

The same timing problem can be quieter. A plugin may wrap its setup in class_exists(). When that check happens before the provider loads, it returns false every time and the feature behind the check never starts. Moving a plugin folder can expose the issue because active plugins are kept sorted by path. Network activation can also change where a dependency appears in the loading sequence.

plugin-load-order-probe is for finding those dependencies before they become a site-specific failure. It identifies code that fails now, code that never runs, and code whose success depends on the current alphabetical order.

How it works

The probe reads PHP without executing it. It starts from code that runs while WordPress is including plugin files, then follows visible calls made from that path.

That includes the main file, files reached through supported include forms, and plugin functions, constructors or static methods called while loading. It can follow patterns such as a singleton instance() call and new self(). It does not continue into callbacks registered for later. Code attached with add_action( 'plugins_loaded', ... ), closures and methods hooked for later runs after the plugin loading phase that the probe is checking.

Each cross-plugin class or function use is compared with the order in which its plugin and provider sort. Must-use plugins are treated as loading before regular plugins. A must-use plugin that reaches regular plugin code during loading therefore fails. A regular plugin using code from a must-use plugin is not reported as a problem.

WooCommerce is recognized without its source. Names beginning with WC_ or Automattic\WooCommerce\, the WooCommerce class, WC() and wc_* functions are associated with woocommerce/woocommerce.php. Other providers must be present in the checked folder.

Decisions that matter

The report separates four cases because they require different attention. fails means the provider loads later and the required class or function is unavailable. skipped means a guard such as class_exists() runs too soon, so the guarded code never executes. Both produce exit code 1.

fragile means the provider currently sorts first, so the use works because of that order. Its normal exit status is 0, while --strict makes that case exit 1. guarded means a check protects the use and the provider already loads first. Those entries stay hidden unless --show-guarded is supplied.

This distinction matters for add-ons that appear healthy during development. A dependency can be available today because one directory name happens to sort before another. The probe reports that relationship instead of treating the current result as proof that the load-time dependency is safe.

The repository's example is synthetic. test/fixtures/wp-content contains a WooCommerce stub, six plugins and a must-use plugin built to demonstrate the supported patterns. It is not copied from a live site. Full sample output is stored in examples/report.txt and examples/report.json.

Local data only

The tool reads the files you point it at and makes no network call. Nothing leaves the machine. It uses no model and needs no API key.

That also means the result comes from static inspection rather than executing WordPress. The probe follows code paths it can identify from the source and compares those paths with the assumed plugin ordering. It does not need a running site or database to perform that analysis.

Where it stops

Dynamic PHP can hide relationships from static inspection. The probe does not follow new $class(), files whose paths are assembled at run time, or calls made through call_user_func().

It assumes the sorted order WordPress keeps for active plugins. If a plugin rewrites the active_plugins option so that it loads itself first, the probe does not discover that changed order.

Network activation is not read from a database. Every plugin is treated as site-activated, although the explanation for a fragile use describes what changes if one is network-activated.

Methods are followed only when visible load-time code reaches them through supported forms. These include $this->method(), self::, static::, parent::, the plugin's own classes by name and its own functions.

Provider discovery also has a boundary. WooCommerce is known without being present. Any other provider has to be inside the folder being checked. When you scan one plugin by itself, only WooCommerce can be compared as an external provider.

Install and run

plugin-load-order-probe requires Node 20 or later. Its PHP parser dependency is pinned. You can install it globally, scan a WordPress content directory, scan a plugins directory, or point it at one plugin. The single-plugin form can identify WooCommerce dependencies even when WooCommerce source is absent.

npm install -g github:hamzaahmadaslam/plugin-load-order-probe
plugin-load-order-probe wp-content/
plugin-load-order-probe wp-content/plugins/
plugin-load-order-probe wp-content/plugins/my-addon/

For CI, the README also shows running the repository directly with npx. The command exits with 0 when nothing fails, 1 when a use fails or never runs, and 2 for a usage error. With --strict, a fragile use also produces exit code 1. --json prints the findings as JSON, while --only <a,b> limits which plugin folders are reported without removing the other folders as possible providers.

Related