What Might WP Next Look Like: Part 5: The Plugin Economy

Published by

on

This is part 5 of a six-part series. [Part 1] made the case for splitting WordPress into Classic and Next. [Part 2] talked about the kernel. [Part 3] talked about the admin and editor. [Part 4] covered performance and security. This post is about the plugin economy and the contracts that hold it together (or don’t).

After all the kernel talk and security talk and performance talk earlier in this series, plugins are actually where I think the most low-hanging fruit lives. The economy that grew up around add_action, the .org repo, “Tested up to” headers, and the unspoken-but-universal assumption that “your version number means whatever you want it to mean” was perfect for 2008. It is not serving anyone particularly well in 2026.

Dependency declaration, actually

WordPress 6.5’s Requires Plugins: header is a start, and hobbled. It slug-matches against .org only, no version constraints, no conflicts, no optional dependencies, no suggests. Composer has all of this. npm has all of this. Drupal’s info.yml has all of this.

Next: plugins have a composer.json (or a superset, wp-plugin.json, that compiles to it) with:

{
"name": "acme/my-plugin",
"version": "2.3.1",
"require": {
"php": "^8.2",
"wordpress/core": "^2.0",
"woocommerce/woocommerce": "^9.0"
},
"suggest": { "yoast/seo": "Rich schema integration" },
"conflict": { "other/conflicting-plugin": "*" },
"provide": { "wp-plugin/seo": "1.0" }
}

I want to dwell on this for a moment because I think it’s underappreciated how much pain this single change would prevent. I have personally been on calls where a customer’s site went down because Plugin A and Plugin B both tried to register the same thing, or because a plugin update bumped its minimum WooCommerce version without telling anyone, or because two plugins both wanted to be the “SEO” plugin and stepped on each other. None of this is hard to declare. Most plugin authors would happily declare it. There just isn’t a place to put it that core respects.

Semantic versioning as mandatory

Plugin versions today are folklore. Version: 3.4 might mean anything. “Tested up to: 6.7” is a social contract, and social contracts don’t scale and don’t survive turnover.

Next mandates semantic versioning for Next-targeted plugins. Major version means breaking. Minor version means new features. Patch version means bug fixes. So far this is just borrowing what every other ecosystem figured out a decade ago.

The interesting part is enforcement, because mandates without enforcement are just folklore in a nicer suit. The conformance test suite I’m proposing here only works because of something else I’m proposing later in this post: @api vs. @internal annotations on the public surface. Without a declared public surface, “did this minor version break compatibility” is unanswerable. With one, it’s a tractable diff. The suite runs the plugin’s declared @api surface against the previous version and flags removals or signature changes in anything below a major. The .org repo flags the release. The author either bumps the version or revises the change.

This is the same trick Drupal pulls with @api annotations plus drupal-rector. It only feels like magic. Underneath, it’s discipline applied consistently for long enough that the tooling can take it for granted.

Composer-first distribution, without abandoning .org

WPackagist has been doing the proxy work for over a decade. It should have been first-party, but now never can be because it was bought by WP Engine.

Anyways, the .org repo becomes the curated, default, signed, free-plugin source, and it exposes a proper Composer repository natively. wp plugin install and composer require are the same command with different user experiences.

It’s worth being explicit about the trust model here, because there are actually three different things going on and they’re easy to conflate:

.org is the default trust root. Free plugins are reviewed (to the extent the team can), signed, and served from a first-party Composer endpoint. This is what almost everyone uses, and the bar to publish stays roughly where it is today, with the addition of signing and provenance from part 4.

Hosts can publish curated overlays. If a managed host wants to say “here is the set of plugins we’ve audited for our platform,” they can ship that as a Composer repository that prefers their reviewed versions over the upstream .org versions. Today, hosts do this informally with allow-lists, warnings, and disabled-by-default settings. Making it a first-class Composer concept turns an informal practice into a product. Customers see the curation. Hosts get credit for the work they’re already doing. Plugin authors can engage with the host’s audit process instead of finding out about it from a support ticket.

License verification is orthogonal and commercial. A LicenseVerifier contract, a signed license token format (Paseto or similar), a standard update-auth flow, and EDD/Freemius/custom verifiers plug in. This isn’t about curation or trust; it’s about commercial plugins needing a way to verify the customer paid, deliver updates only to paying customers, and revoke access cleanly when subscriptions lapse. Right now every premium plugin reinvents this, and it shows. License servers go down, plugins go silent, customers get stuck. A standard contract lets the rest of the ecosystem build the tooling around it.

Three layers, three different jobs. The default trust root, the optional curated overlay, the commercial licensing primitive. They stack rather than compete.

mu-plugins/: formalize

Must-use plugins are a beautiful hack. Auto-loaded, no UI, no subdirectories by default, no activation hook. Hosts use them for object cache, forced config, platform code. I’ve written a few mu-plugins myself, so I have some affection for them. They are exactly the right primitive in exactly the wrong shape.

Next formalizes them as platform plugins: declared loading order, access to a pre-boot hook, visible (but not user-toggleable) in the admin with a platform/host badge, with proper metadata. The host gets to identify itself. The user gets to know what platform code is running and why. The plugin author gets a real lifecycle to hook into instead of a require statement that runs at an undefined point in the boot sequence.

Theme/plugin distinction in a block-theme world

Classic themes were PHP templates. functions.php was a mini-plugin. Block themes move templates to HTML plus theme.json plus PHP for dynamic blocks. The boundary between “theme” and “plugin” is a lot blurrier than it was.

The proposal is to keep the mental distinction (themes own presentation, plugins own functionality) but unify the extension lifecycle underneath. One manifest format. One install flow. One update flow. One dependency system. One permissions model. “Theme” and “plugin” become tags on the manifest, not different code paths through core.

I bet the actual core team has thought about this more than I have, and there are reasons it hasn’t happened that I’m not seeing from the outside. But the duplicated effort between the two code paths is real, and the user-facing weirdness (a “theme” you have to install before a “plugin” before another “theme” because dependencies cross the boundary) is real too. Worth pulling on the thread.

A real backwards compatibility policy

WordPress’s backwards compatibility story is folklore. Don’t break stuff, mark things deprecated, sometimes remove. No RFC process for breaking changes. No explicit deprecation windows. No @internal vs. @api distinction.

Drupal 8+’s backwards compatibility policy (deprecations land in minor versions, removals at major versions, with drupal-rector automating upgrades) is the model. PHP’s own RFC process on wiki.php.net is another. Python’s PEP process. Rust’s RFC process. There’s no shortage of working examples to copy.

Next adopts:

  • An RFC repo for breaking changes. If you want to change something, you write it down, you justify it, and people get to weigh in before it ships.
  • @api vs. @internal annotations enforced by tooling. If you’re using something marked @internal, you don’t get to complain when it changes. This is also the thing that makes the conformance suite from earlier in this post actually work.
  • Deprecation lands at least one minor version before removal. An N-2 deprecation window minimum.
  • A wp-rector tool that autofixes common upgrades. Drupal proved this works.
  • A public API surface map generated from the codebase that tooling can diff between versions. So you can actually see, automatically, what changed.

Where this leaves us

Contracts replace folklore. That’s the whole post in a sentence. Once authors declare dependencies, versions mean what they say, distribution has trust layers, the platform tier is named instead of bolted on, and the project commits to a real backwards compatibility process, the rest of the security and performance work from earlier in this series has somewhere to land. Right now the ecosystem doesn’t have guarantees. It has hopes.

None of this is technically hard. Most of it is borrowed from other ecosystems that have already proven the patterns work. The hard part, as with everything else in this series, is the will to actually ship it.

Next post is the last one. The migration plan. How Classic and Next coexist without becoming a Perl 6 situation. How existing sites move. The timeline. The constraints that have to be preserved. And the economic model for who pays to keep Classic alive, which I owe an answer on from part 1.

More soon.

AI Disclosure: Featured image generated by Gemini, and Claude assisted with the refinement of my choices, arguments, and editing of this post and series.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.