Why the final plugin filter is only half of the architecture
https://developer.woocommerce.com/2026/07/21/selective-plugin-loading/
WooCommerce Engineering recently published a useful account of how WooCommerce.com reduces WordPress bootstrap cost by loading fewer plugins on selected requests.
The article deserves attention for a simple reason: it names and measures a problem that WordPress performance discussions often overlook.
A WordPress installation has one global list of active plugins.
A single request rarely needs all of them.
WooCommerce.com found that unrelated plugins could consume 10 to 20 percent of the generation time of an uncached blog request. On selected internal endpoints, loading only the required plugins reduced memory use by more than 50 percent. Reported request times dropped from around 800 ms to 475 ms and from 880 ms to 550 ms. An early test on product pages produced roughly a 10 percent improvement in page-generation time.
That is valuable evidence.
It confirms that plugin bootstrap is not merely theoretical overhead. Every loaded plugin may register hooks, instantiate services, read options, load translations, attach REST routes or initialize integrations that the current request never uses. Full-page caching reduces how often this work occurs, but it cannot remove the cost from cache misses, logged-in sessions, APIs and other dynamic requests.
WooCommerce.com solves this with route-specific rules in an early MU-plugin. The MU-plugin filters WordPress’s active plugin list before regular plugin files are included.
The result is real: excluded plugins do not execute.
But the filter itself is only the final switch.
The more interesting question is what had to happen before that switch could be operated.
Selective plugin loading is not one architecture
Several public WordPress plugins already provide selective plugin loading.
Plugin Organizer can selectively disable plugins by managed URL or post type and installs an MU component for its early loading mechanism.
Freesoul Deactivate Plugins supports pages, posts, custom post types, archives, backend pages, devices and custom URLs. Its documentation explicitly states that disabled plugins do not merely lose their assets. Their PHP code does not run for the affected request.
Plugin Load Filter also installs an MU-plugin component and distinguishes between administrative requests, page types, post types, devices and other WordPress contexts.
These are genuine PHP execution-control tools.
They should not be confused with asset managers that merely suppress CSS or JavaScript after the responsible plugin has already loaded.
But the fact that several solutions eventually influence the same WordPress plugin list does not make their architectures equivalent.
An MU-plugin may be:
- the complete selection engine
- a runtime rule interpreter
- a URL and post-type matcher
- or merely the handoff through which WordPress receives a decision prepared elsewhere
Looking only at the final filter point is like comparing two buildings because both have a light switch.
The wiring still matters.
The hidden cost of deciding what not to load
A WordPress-internal selector can prevent expensive regular plugins from loading. That can easily produce a net performance gain.
WooCommerce.com has demonstrated exactly that.
But the selection itself is not free.
Before an MU-plugin can execute inside wp-settings.php, a relevant part of the WordPress bootstrap already exists. Configuration has been loaded. Database and object-cache infrastructure have been initialized. Core files and the MU-plugin environment are available. The selector may then read its settings, inspect the request, evaluate route rules and modify the active plugin list.
This does not necessarily mean there is one dedicated SQL query for active_plugins on every request. The option may be part of WordPress’s autoloaded options or served through an object-cache layer.
That distinction is technically important, but it does not remove the architectural cost.
The WordPress options, database and cache infrastructure must already exist before a WordPress-internal selector can use them. A persistent object cache also requires additional infrastructure. It is not a magical property of every ordinary WordPress installation.
The useful question is therefore not:
Does selective plugin loading save time?
It often does.
The more revealing question is:
How much application runtime must exist merely to decide how much application runtime should exist?
A selector can be worthwhile while still paying an avoidable WordPress-internal decision cost.
Those two statements do not contradict each other.
WooCommerce.com chose a pragmatic internal solution
The WooCommerce.com implementation is designed for a large, engineering-owned platform.
Its rules live in code, pass through review and deployment processes, and are monitored like other performance-sensitive changes. The team deliberately focuses on predictable, high-traffic routes where the expected plugin requirements can be understood and tested.
The difficult part is not filtering the array.
The difficult part is determining what a route actually needs.
WooCommerce Engineering describes dependency discovery as the hard part. Developers inspect route code, follow dependencies, manually exercise request flows, add tests, deploy incrementally and monitor errors and performance data. Stateful or broad requests such as checkout, cart, account, admin, cron, webhooks and payment callbacks require extra caution.
That makes the solution appropriate for WooCommerce.com.
It does not automatically make it a generally reusable context engine for arbitrary WordPress installations.
This is not criticism of the implementation. It is simply the boundary of the selected architecture.
WooCommerce.com uses a WordPress-controlled environment to decide how much of that environment should load.
That can work very well when routes are narrow, rules are owned by engineers and failures can be monitored and rolled back.
The enforcement point and the decision process are different things
Selective plugin loading contains at least two separate tasks:
Determine the execution scope required by the request.
Prevent WordPress from loading plugins outside that scope.
WordPress offers an early point at which the plugin list can be changed.
That is the enforcement mechanism.
It does not tell us how the decision was produced.
A simple implementation may inspect REQUEST_URI and compare it with a list of exact paths, prefixes or patterns. A more advanced WordPress plugin may use page IDs, post types, device classes, backend screens or user-related conditions.
These approaches can be useful. They can also become increasingly dependent on runtime expressions, duplicated routing knowledge and manually maintained exceptions.
There is another possible abstraction:
Request signals
↓
Execution context
↓
Runtime bucket
↓
Prepared plugin scope
↓
WordPress loads only that scope
The distinction is subtle but important.
A URL rule asks:
Which rule matches this URL?
A runtime bucket asks:
What kind of application runtime does this request require?
Several URLs, entry points or request variants may belong to the same runtime bucket. Conversely, requests that appear similar at URL level may require different execution scopes.
The quality of selective loading therefore depends not only on how early plugins are filtered, but also on the resolution and reliability of the context model behind the filter.
Post types are useful, but they are not the complete request universe
WordPress-oriented selectors often begin with understandable concepts:
- pages
- posts
- custom post types
- archives
- frontend versus backend
- desktop versus mobile.
These are useful dimensions. They are also only part of the request landscape.
A modern WordPress installation may also process:
- REST requests
- AJAX actions
- cron requests
- login and account flows
- administrative screens
- webhooks
- payment callbacks
- feeds
- internal tools
- crawler requests
- cache-related operations
- application-specific endpoints.
Treating the whole WordPress backend as one indivisible runtime is safe, but often unnecessarily broad.
Likewise, treating every custom URL as an expression that must be interpreted during the request can create its own maintenance and execution overhead.
A more differentiated execution model can map requests into prepared runtime contexts instead of constructing a growing condition language around individual URLs.
This does not make URL-based selection obsolete.
A URL can be an excellent signal.
The question is whether the URL is evaluated as an isolated runtime rule, or used as one input into an already prepared execution context.
The MU-plugin does not equalize the solutions
This point is easy to miss.
If two systems both contain an MU-plugin, it is tempting to classify them as variations of the same technique.
That conclusion is too quick.
In one system, the MU-plugin may perform nearly everything:
WordPress starts
↓
MU-plugin loads
↓
settings are retrieved
↓
URL and context rules are evaluated
↓
active plugin list is filtered
In another system, the MU component may have a much narrower role:
request context and execution scope are prepared
↓
WordPress reaches the early handoff
↓
MU component applies the prepared scope
↓
regular plugins load
The MU component in the second model is not the context engine.
It is the bridge into WordPress.
The shared enforcement point does not erase the difference between the decision paths that reached it.
Where WP-Rush fits
For transparency: I am the developer of WP-Rush.
WP-Rush was created because I did not want WordPress to perform most of the work required to decide how much WordPress should run.
It uses a staged, request-aware execution model. The required execution scope is completed before regular plugins are loaded. WordPress then continues normally inside that selected scope.
The public architectural contract is deliberately clearer than the internal implementation details:
- request processing begins before the regular plugin-loading phase
- the MU component is an early WordPress handoff, not the complete selection engine
- the execution scope is prepared before regular plugins initialize
- plugins outside that scope do not execute for the request
- uncertain or sensitive contexts can retain a broader or complete runtime.
WP-Rush organizes requests through runtime buckets rather than relying only on WordPress post types or individual page rules.
A bucket represents an execution context.
Different public requests may share one bucket. Administrative requests can be separated into more specific runtime classes instead of treating the entire backend as one block. Asynchronous and other non-document requests can receive their own scopes.
WP-Rush also provides a URL-oriented mode, but the URL mode does not require users to build a runtime expression language. URLs can be associated with prepared execution contexts rather than repeatedly interpreted through chains of conditions during every request.
The point is not that URL selection is bad.
The point is that URL selection does not have to mean runtime expression processing inside WordPress.
The internal preparation, classification and safety mechanisms remain product implementation details. Publishing every internal step would turn an architectural explanation into a reconstruction guide.
A reader can still evaluate the public system boundary:
When does context processing begin?
When is the scope complete?
What code is prevented from executing?
What happens when the context is uncertain?
Those are legitimate architectural questions even when the proprietary implementation is not published line by line.
WP-Rush describes itself as a request-aware execution layer that reduces the plugin runtime created for a request before normal plugin execution begins.
What about server-level classification?
Some infrastructure-owned systems move request classification even further toward the web server, reverse proxy or hosting layer.
That is a legitimate architecture class.
It may also require privileged access to Nginx, PHP pool settings or custom hosting infrastructure that ordinary WordPress installations do not provide. The discussion below the WooCommerce Engineering article itself notes that rules can exist outside the MU-plugin path, but that such approaches require deeper server access and infrastructure control.
Earlier is not automatically better.
A very early classifier with poor context may be less useful than a slightly later classifier with reliable information.
The relevant comparison remains:
- selection cost
- context quality
- scope granularity
- maintenance effort
- deployment requirements
- and safe fallback behaviour
Without enough public technical detail, naming individual proprietary infrastructure systems would turn this article into speculation. The architectural class is worth acknowledging even when particular products cannot yet be compared fairly.
Five questions worth asking any selective-loading solution
When two tools both claim to “load only the plugins that are needed,” ask more than whether they filter active_plugins.
Ask:
1. How much WordPress runs before the decision exists?
Does the selector require WordPress’s database, options, object cache and its own plugin runtime before it can classify the request?
2. Where does the context model come from?
Is the decision based on runtime URL expressions, WordPress post types, hard-coded route rules, prepared contexts or another classification layer?
3. How granular is the execution model?
Can it distinguish only pages and post types, or also administrative, asynchronous, API and other request classes?
4. What happens when the request is ambiguous?
Does the system guess aggressively, fail closed, or fall back to a broader safe runtime?
5. Is the MU component the engine or merely the handoff?
The answer can reveal more about the architecture than the presence of the MU-plugin itself.
Same goal, different selection cost
WooCommerce.com has done WordPress performance work a favour.
Its Engineering article confirms three important facts:
Global plugin loading can create substantial request overhead.
Preventing unnecessary plugin execution can produce measurable improvements.
Safely determining the required plugin scope is harder than filtering the plugin list.
WooCommerce.com solves that problem with route-owned rules inside a controlled engineering environment.
Plugin Organizer, Freesoul Deactivate Plugins and Plugin Load Filter provide generally available WordPress interfaces for related forms of selective plugin loading.
Infrastructure-level systems may move parts of classification toward the server.
WP-Rush uses a request-aware context and bucket model in which its MU component is primarily the early handoff into WordPress, not the whole decision engine.
These approaches share a goal.
They do not share the same selection cost, context model, granularity or operational architecture.
Selective plugin loading should therefore not be compared only by asking:
Can it remove a plugin from the final list?
The better question is:
How much had to run, and how much had to be known, before that list could be produced?
The final filter point is only half of the architecture.
Sources: https://developer.woocommerce.com/2026/07/21/selective-plugin-loading/