WordPress 7.1 landed on 19 August. I went through it the way I go through any release that matters to my work: I downloaded the 7.0.4 and 7.1 zips from wordpress.org and diffed them. Not the changelog, the actual files.
Three things came out of that worth writing down. One of them I haven't seen anyone mention.
Core has an API for AI agents, and 7.1 added a way to answer for it
The Abilities API is not new in 7.1. It's @since 6.9.0, and all four of its classes ship in 7.0.4 already. If you read somewhere that 7.1 introduced an AI API, that's wrong.
What 7.1 added is the layer around it. Four things, none of which appear anywhere in 7.0.4:
wp_ability_invoked- an action that fires on every ability call, before input normalization, so it sees the raw inputwp_pre_execute_ability- a filter that can short-circuit the whole callWP_Filter_Sentinel- a new final class, more on it below- a
publicflag on abilities, defaulting to false
Here's the docblock for that public flag, straight out of class-wp-ability.php:
Whether the ability is meant to be available to clients such as the REST API, MCP, or AI agents.
MCP is the Model Context Protocol - the thing Claude and other agents use to talk to tools. It's now named in WordPress core source. That alone is worth knowing about.
The part that got my attention is wp_pre_execute_ability. Core documents what it does, and it's blunt about it. Returning anything other than the default value bypasses "input normalization, input validation, permission checks, the registered execute callback, output validation, and the surrounding actions".
Permission checks are in that list.
I tested it
I set up WordPress 7.1 locally and ran this against core/get-site-info, which is one of the three abilities core registers itself. It requires manage_options.
// Run as nobody.
wp_set_current_user( 0 );
$a = wp_get_ability( 'core/get-site-info' );
$r1 = $a->execute();
add_filter( 'wp_pre_execute_ability', function ( $pre, $name ) {
return 'core/get-site-info' === $name ? 'HIJACKED BY FILTER' : $pre;
}, 10, 2 );
$r2 = $a->execute();
The output:
WITHOUT filter : DENIED -> ability_invalid_permissions
WITH filter : RETURNED -> "HIJACKED BY FILTER"
Same call, same anonymous user. Without the filter the permission callback denies it. With the filter hooked, the caller gets whatever the filter decided to return, and the permission callback never gets consulted.
Now, the honest part. This is not a vulnerability and I'm not going to dress it up as one. The filter runs server-side, so anything that can hook it is already PHP running as your site. The REST endpoints have their own permission_callback, and listing abilities requires current_user_can( 'read' ). I checked that specifically, because a wrong security claim about core would be worse than saying nothing.
Core also documents the intended uses, and they're reasonable: cached responses, rate limiting, maintenance mode, test mocking. Every one of those needs to run before the expensive work.
What I'd want people to understand is the shape of it. Any plugin on the site can answer on an agent's behalf, silently, and the ability's own permission logic won't run. If you're building abilities and relying on permission_callback as your security boundary, that boundary holds against REST callers and not against other PHP on the same install. That distinction matters more as more sites start exposing abilities to agents.
It's the same trust model as the plugin installer filter I wrote about earlier this month. Different screen, same idea: a filter with no gate of its own, sitting on a path you assumed was guarded.
The sentinel is a nice bit of engineering
Small thing, but I liked it. WP_Filter_Sentinel is a final class with an empty body. That's the whole implementation.
It solves a problem WordPress has had forever. apply_filters( 'something', null ) can't tell you whether a callback returned null deliberately or whether nothing was hooked at all. Both look identical on the way out. Every plugin author has hit this and worked around it with a magic string or a global.
Core's fix is to pass a unique object as the default and compare with === afterwards. If you get the same instance back, nobody touched it. Any other value, including null or false, is a real answer from a real callback.
I'd like to see this used more widely than one filter.
A performance decision from 5.8 got reversed
This one I found by diffing wp-includes/media.php.
In 7.0.4:
$infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false );
In 7.1 the default moved into a variable, and the docblock above it says: @since 7.1.0 Changed default to true and introduced per-user opt-out of infinite scrolling.
Infinite scrolling in the Media Library is on again. It was switched off in 5.8, and performance was one of the stated reasons alongside accessibility and usability.
I checked what a real install computes rather than trusting the diff. On a clean 7.1 site, with no filter of my own, the value that reaches the media JavaScript is infiniteScrolling = 1.
If you run sites with tens of thousands of attachments, this is the 7.1 change most likely to make wp-admin feel worse. Infinite scrolling replaces the pager, so the grid keeps requesting the next page of attachments as you scroll instead of stopping at one screenful. I verified the default is on, but I haven't measured this against a library with 100,000 attachments, so treat the size of the effect as unmeasured. There's a per-user opt-out and the media_library_infinite_scrolling filter still wins, so it's fixable, but it's on by default now and most people won't know it changed.
I don't have a strong opinion on whether the 5.8 decision or the 7.1 decision is right. I'd just want to see the numbers that justified the reversal, and I couldn't find any.
The one performance item on the roadmap didn't ship
The 7.1 roadmap said that when core detects both an object cache and a page cache, speculative loading eagerness would move from conservative to moderate. That was the performance item for this release.
I checked wp-includes/speculative-loading.php in the shipped zip. The defaults are still prefetch and conservative. The auto-detection isn't there.
Then I loaded the front page of a 7.1 site and read the rules it actually prints:
<script type="speculationrules">
{"prefetch":[{ ... ,"eagerness":"conservative"}]}
</script>
Conservative, as shipped.
What did ship is a pair of overrides, WP_SPECULATIVE_LOADING_DEFAULT_MODE and WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS, settable as constants or environment variables.
Those work. I put WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS into wp-config.php, set it to moderate, reloaded, and the printed rule changed to "eagerness":"moderate". Removing the constant put it back to conservative. So a host can flip a whole fleet without shipping an mu-plugin, and the default for everyone else didn't move.
Worth thinking about if you're on managed hosting. A host that sets eager prerender on an origin without a page cache multiplies the work that origin does, and you won't see it in your own config.
What the release actually is
Some numbers from the two zips, because they explain the shape of this release better than any feature list.
| What | 7.0.4 to 7.1 |
|---|---|
| Core, unpacked | 91 MB to 114 MB |
wp-includes/js/dist | 28 MB to 50 MB |
Files in dist | 133 to 133 |
editor.min.js | 1021 KB to 1536 KB |
option.php | 3 lines changed |
cache.php | unchanged |
class-wp-object-cache.php | unchanged |
Core grew by a quarter and every byte of it is JavaScript. The file count in dist didn't change at all, so nothing new was added there - the existing bundles got bigger. The new script modules that came with it are vips and video-conversion, which is image and video processing moving into the browser.
On the server side, the things I care about most didn't move. option.php changed by three lines and one of them is a PHPStan annotation. The object cache files are untouched. There's no autoload story in this release, no new indexes, nothing in the query layer. class-wpdb.php has 157 changed lines and they're almost all PHP 8.5 deprecation fixes.
Core also published no 7.1-versus-7.0 numbers for TTFB, LCP or query count. There's no performance field guide for this release, which there was for 6.2 through 6.9. So if you read that 7.1 is faster, ask where that came from.
Where the extra 23 MB goes, and who pays for it
The size jump is the thing I most wanted to pin down, so I measured instead of guessing. All of this is from a real 7.1 install, not from the zip.
The two new script modules are where the weight sits:
| File | Size |
|---|---|
vips/worker.min.js | 13,417,612 B (12.8 MB) |
video-conversion/worker.min.js | 409,786 B |
vips/loader.min.js | 75 B |
video-conversion/loader.min.js | 87 B |
Core's dev note said the worker is around 13 MB. It is, and now that's measured rather than repeated.
The loaders being 75 and 87 bytes is the answer to the obvious worry. Those workers are fetched when an upload needs them, not when a screen loads.
The front end doesn't see any of it
I loaded the front page of a default 7.1 site and totalled every asset it pulls. One file, 3 KB. No vips, no video conversion, nothing from the editor bundles. The 22 MB that js/dist gained is admin-side code, and visitors don't download it.
So if you were worried that upgrading makes your visitors download a bigger site, they don't.
Admin is a different story, and there's one thing that shouldn't be there
I logged into wp-admin and recorded every network response on three screens:
| Screen | Requests | Downloaded |
|---|---|---|
| Users list | 59 | 372 KB |
| Media library | 86 | 488 KB |
| New post | 163 | 1.1 MB |
The 12.8 MB worker never appeared on any of them, which is what you want.
What did appear on all three is upload-media.min.js, 16 KB, including on the users list where there is nothing to upload.
I pushed on that one. I made a subscriber account, confirmed with user_can() that it has no upload_files capability, logged in as them, and loaded their own profile page. The 16 KB media upload script downloads there too.
It isn't a deliberate enqueue. wp-upload-media is registered as a dependency of wp-block-editor, wp-block-library and wp-editor, and wp-block-library gets pulled into admin screens broadly. So the dependency graph drags it along, and nobody wrote a capability check because nobody wrote an enqueue.
16 KB is not going to hurt anyone. I'm noting it because a script for uploading media has no business being downloaded by an account that cannot upload media, and that pattern tends to grow.
Class loading: there's no autoloader to regress
I went looking for a PSR-4 mapping or an autoloader change, because that would be the interesting failure mode. WordPress core has neither. There's no spl_autoload_register anywhere in wp-settings.php or load.php, and no autoloader class. Core loads by 311 unconditional require statements, the same way it always has.
7.1 adds to that list. The four Abilities API classes, abilities-api.php, icons.php and the filter sentinel come to about 100 KB of PHP that every request reads, front end included, whether or not anything on the page uses an ability or an icon.
So I measured what that costs. Same site, same content, same machine, upgraded and downgraded between the two versions:
| Measurement | 7.0.4 | 7.1 |
|---|---|---|
| Included files | 697 | 712 |
| PHP bytes included | 19,587,285 | 20,091,659 |
| Peak memory | 53,805,056 B | 53,805,056 B |
Fifteen more files and 2.5% more PHP read per request, and peak memory came out identical to the byte.
TTFB I couldn't separate from noise. 7.1 ran a median of 41 ms over 20 requests with a 7 ms standard deviation, against 39 ms on 7.0.4. Small sample runs on this are worth nothing - at 8 requests the gap looked like a 10 ms regression, and it isn't one. This is one local Docker install, so treat it as "I found no measurable slowdown here", not as a benchmark of your hosting.
On the security side, nothing
I looked, because a fresh release is a reasonable place to look. 7.1 is not a security release. No CVE is assigned against it, WPScan lists nothing, and every fix it carries came from 7.0.2, 7.0.3 and 7.0.4 before it went final.
The 7.0 line is where the interesting stuff happened. CVE-2026-65640, fixed in 7.0.4, was an Author-level remote code execution through a PostScript upload on servers running Imagick and Ghostscript. Earlier there was a worse pair: an SQL injection in author__not_in chained with a REST batch route confusion into unauthenticated RCE. Both of those are in CISA's Known Exploited Vulnerabilities catalogue, which means they were being used against real sites.
If you're on 7.1, none of it is open. If you're sitting on 7.0.3 or earlier, the Imagick one is.
Upgrading
I'd take 7.1. The security position is better than any earlier 7.0, and nothing I found in the diff makes a front end slower in a way I could measure.
Two things to do after: turn Media Library infinite scrolling back off if you have a big library, and check whether your host has opinions about those new speculative loading constants.
The abilities work is what I'll be watching. Core is building a surface for autonomous agents, and the first piece of plumbing it added lets any plugin stand in front of that surface. I'd like to see what plugin authors do with it before deciding whether that's a problem.
One personal note. My wordpress.org username shows up in the 7.1 credits, in the props list, and it's the first release it appears in. Small thing, and it's a props credit rather than anything grand, but I've been reading this codebase for fifteen years and it's nice to be a line in it.