Platform · We do not build on it
The PHP content management system behind 40.7 per cent of all websites, which assembles every page at request time from a core, a theme and an unbounded number of third-party plugins.
WordPress core is well-maintained software: six vulnerabilities were disclosed against it in the whole of 2025, all rated low priority. The other 11,328 found that year were in the plugins and themes bolted onto it, and that ratio is not an accident of who happens to write extensions — it follows from an architecture in which independent parties compose through an untyped global hook registry and share the same two tables. What follows takes the mechanisms behind that ratio apart one at a time, and then states plainly where WordPress is the correct answer, because a refusal that cannot argue the other side is an advertisement.
01
Exposure tracks the extension list, not the version number. Patchstack recorded 11,334 new vulnerabilities across the WordPress ecosystem in 2025, a 42 per cent rise on 2024. Ninety-one per cent were in plugins and nine per cent in themes; core accounted for six, all low priority. That distribution is the whole argument compressed into one line — core is a serious project with a serious security process, and a WordPress site's attack surface is almost entirely code the core team did not write and does not review. The consequence is that "is the site up to date?" is not answerable as asked. The answer that matters is the union of the release states of every installed extension, each maintained by a different party on a different cadence, with a different definition of an emergency. Patchstack also found that premium components carried three times as many known exploited vulnerabilities as free ones, and that 76 per cent of the vulnerabilities reported in premium components were exploitable in real attacks, which disposes of the comfortable assumption that a licence fee buys maintenance., which disposes of the comfortable assumption that a licence fee buys maintenance.
02
The window between disclosure and exploitation is shorter than any maintenance cycle, and the update mechanism is off by default. In 2025 the weighted median time from public disclosure to first observed exploitation was five hours, and roughly half of high-impact vulnerabilities were exploited within twenty-four hours. Against that, 46 per cent of disclosed vulnerabilities had no patch available from the developer at the moment of disclosure — a routine state in which the exploit exists, the vulnerability is public, and no fix has been written by anyone. Since WordPress 5.5 the platform can auto-update plugins and themes, but the setting is per item and disabled by default, so surviving that window requires that somebody explicitly opted every extension in, one at a time. The next point explains why owners refuse to.
03
Updating is the risky operation, which inverts the security model. WordPress plugins do not compose through typed interfaces. They compose by registering callbacks against a shared, priority-ordered global hook registry and by writing to shared global state and shared tables. Nothing checks at install time that two plugins hooked to the same filter agree about what that filter returns, and nothing checks at update time that a plugin's new version still returns what its neighbour assumed. Breakage is discovered at runtime, in production, on the page a customer is looking at. The rational response of an operator burned once is to stop updating, and the wordpress.org telemetry shows exactly that: as of September 2026, 24.2 per cent of installations run a branch older than 6.9 and 7.9 per cent are still below 6.0. The same avoidance appears one layer down. WordPress 7.1 requires PHP 7.4 as a minimum and recommends 8.3, yet 17.2 per cent of installations report PHP 7.4 — which lost security support in November 2022 — and 38.8 per cent report PHP 8.1 or older, every one of those branches now unsupported upstream. The security model assumes patching is cheap. Coupling through untyped hooks makes it expensive, and the field data shows which assumption wins.
04
A withdrawn plugin is indistinguishable, from inside the admin, from a current one. When wordpress.org closes a plugin — typically because a vulnerability was reported and not fixed — it disappears from the directory, but the copies already installed do nothing at all. They report no update, because there is no update, and core's plugin list renders them exactly as it renders a plugin that is genuinely current. Directory policy forbids plugins from updating themselves from an external server, so there is no path by which the author of a withdrawn plugin can push a fix to existing installations even if one gets written. Closures have reached plugins with millions of active installations. The failure mode here is not an administrator ignoring a warning; it is that no warning is produced, and the only detection is a human noticing the plugin's page has become a 404.
05
The options table is an unbounded query that runs before any application code. WordPress keeps site configuration in wp_options, whose 7.1 schema is option_name varchar(191) unique, option_value longtext, and autoload varchar(20) with an index on autoload. On every request, before a theme or plugin has executed a line, core selects every row flagged for autoload and holds the result in memory. Nothing in the design bounds that set. Plugins write to it freely, and — this is the part that compounds — deactivating a plugin does not remove its rows, so a site's autoloaded payload is the accumulated configuration of every plugin ever installed rather than the ones currently running. Core has been retrofitting limits against this: since 6.6, an option larger than 150,000 bytes is no longer autoloaded unless the caller explicitly demands it, and Site Health raises a warning once the total exceeds 800,000 bytes. Both figures are filterable, which is to say both are advisory rather than enforced. The cost is paid per request on the admin as well as the front end, and it is precisely the cost a page cache does not remove.
06
wp_postmeta is entity-attribute-value storage with no index on the value. The 7.1 schema is meta_id, post_id, meta_key varchar(255) and meta_value longtext, with a primary key on meta_id and secondary keys on post_id and on the leading 191 bytes of meta_key. meta_value carries no index and cannot usefully carry one, because it is a longtext holding every attribute of every content type in a single column. Any query filtering on an attribute value therefore either scans or is rewritten by WP_Query into a self-join per attribute: two filters and a sort produce three joins against a table that grows as posts multiplied by attributes. This is not a prediction about what might go wrong at volume. WooCommerce, the largest consumer of that schema in existence, moved order storage out of wp_posts and wp_postmeta into four purpose-built normalised tables — wc_orders, wc_order_addresses, wc_order_operational_data and wc_orders_meta — and made the new arrangement the default for fresh installations in version 8.2, released October 2023. The vendor with the most to lose from admitting the shape was wrong for transactions admitted it and rewrote.
07
Per-request assembly through the hook chain, and why the standard remedy is a cache rather than a faster query. WordPress 7.1 contains WordPress 7.1 contains 3,477 hook invocation sites spread across 412 of the 1,512 PHP files in core, carrying 2,712 distinct hook names, and a rendered page is the result of walking that chain with whatever callbacks the installed extensions registered, in whatever priority order they asked for. and WordPress 7.1 contains 3,477 hook invocation sites spread across 412 of the 1,512 PHP files in core, carrying 2,712 distinct hook names, and a rendered page is the result of walking that chain with whatever callbacks the installed extensions registered, in whatever priority order they asked for., and a rendered page is the result of walking that chain with whatever callbacks the installed extensions registered, in whatever priority order they asked for. There is no compiled artefact and no build step; the page is recomputed from scratch on every uncached request. Because the cost is spread across hundreds of small callbacks rather than concentrated in one slow query, profiling rarely yields a single thing to fix, and the remedy the ecosystem actually reaches for is a full-page cache that skips PHP entirely. That works, and it is why so many WordPress sites benchmark respectably. What it does not do is help the pages that cannot be cached. WooCommerce's own performance documentation instructs operators to exclude cart, checkout and my-account from page caching, because those pages render per-customer state. So the pages that carry the money run at full assembly cost, uncached, at exactly the moment traffic peaks — and the benchmark that sold the architecture measured the pages that do not decide anything.
08
Every plugin is a dependency with an independent release cadence and no contract behind it. The directory currently holds over 67,000 plugins. Installing one is not equivalent to adding a package to a lockfile: core enforces no semantic-version contract, the plugin declares no interface it promises to keep, and there is no mechanism that pins a site to a revision anybody reviewed. The update a site receives is whatever the author last pushed. The maintenance obligation is therefore not "keep WordPress updated" but "track the release behaviour, the responsiveness and the continued existence of N independent parties", where N was fixed by whoever built the site, often years earlier, often to save a fortnight of work. The GPL guarantees the right to fork an abandoned plugin and that right is real, but exercising it converts a line item into a codebase somebody must own, staff and test. Most organisations decline, which is why abandonment resolves as accumulated unpatched code rather than as forks.
09
Structured data emitted by a plugin is structured data nobody on the team owns. WordPress core emits no schema.org markup whatsoever. In 7.1 the only two occurrences of application/ld+json in the entire codebase configure the syntax-highlighting mode of the built-in code editor, and Structured data emitted by a plugin is structured data nobody on the team owns. WordPress core emits no schema.org markup whatsoever. In 7.1 the only two occurrences of application/ld+json in the entire codebase configure the syntax-highlighting mode of the built-in code editor, and every one of the thirty-nine occurrences of the string schema.org is part of a json-schema.org URL rather than the vocabulary — thirty-eight of them the $schema key of a REST controller, the thirty-ninth a docblock reference in the Abilities API.. Everything a machine reads about a WordPress page as structured data therefore arrives from an extension, at a hook priority, in a shape the extension chose. Install two plugins that each consider structured data their responsibility and the page ships two disconnected graphs whose @id references do not resolve into one another — which no human notices, because the page renders identically. This matters more than it did five years ago, because the consumer is increasingly a retrieval system deciding whether the page answers a question, not a rendering engine deciding how to draw a snippet. A markup layer nobody can point at a line of and defend is not a machine-readable surface; it is a plugin's output that happens to be on the site.
10
Where WordPress is genuinely the right answer, stated without hedging. A publication with several authors, an editorial calendar and a workflow that is the actual product: WordPress ships post revisions, a roles and capabilities system, scheduled publishing and a localisation infrastructure carrying 232 right-to-left stylesheets in core, each shipped minified as well, which for Arabic-language publishing in this region is a solved problem nobody should pay to re-solve., which for Arabic-language publishing in this region is a solved problem nobody should pay to re-solve. A catalogue site whose requirement is honestly a catalogue. Any site whose owner must be able to hand it to a maintainer they have not met yet — WordPress runs 58.9 per cent of all sites with a recognised CMS, which makes the labour pool an asset rather than a risk, and the exit is a MySQL dump plus a wp-content directory that will run on any host that serves PHP. These are not consolation prizes. A well-kept WordPress on a supported PHP with a default theme and a short plugin list will outlive a bespoke application only its author understands, and a practice that pretends otherwise is selling its own indispensability rather than an engineering judgement. The refusal above is not about the platform's ceiling. It is about what happens when the requirement becomes transactional, personalised or machine-readable, and the answer to every gap is another plugin.
An entry that cannot point at something you can open, run or read does not compile. That is a property of the type, not a promise in a paragraph.
Invariant
This argument is about what to build, not about what to demolish, and it does not predict a breach at any particular site. The 11,334 figure is an ecosystem aggregate across more than 67,000 plugins; a WordPress running a supported PHP, a default theme, a dozen maintained extensions and auto-updates enabled sits far off that distribution, and its real risk profile is closer to any other maintained PHP application than to the headline number. Nor does the refusal license a migration. Moving a content site off WordPress costs a redirect map, a rebuilt editorial workflow and the retraining of everyone who publishes, and none of that is repaid if the replacement carries a comparable extension surface, or if the metric that was actually failing was never the platform's fault. Where a client's own team already operates WordPress competently, a bespoke stack maintained by a single supplier is the higher-variance choice and we will say so rather than quote for it. And where the requirement is a brochure site and a blog that a marketing hire must be able to edit in two years without calling anyone, the honest recommendation is a short, well-kept WordPress — not an application that becomes unmaintainable the day the relationship ends.