WordPress and PHP 8.5: what breaks, what only shows notices
Short answer
WordPress 7.1 itself works with PHP 8.5; your plugins and theme decide whether your site does. Of the PHP 8.5 changes you’re most likely to meet in plugins and themes, three stop the code with a fatal error: setlocale() with 0 in files that use strict types, #[Attribute] on a class that can’t be an attribute, and class_alias() to the name array or callable. Ten only log deprecation notices, and the code keeps working. php.net lists the full set of changes. Check your plugins and theme before you switch.
Is WordPress ready for PHP 8.5?
PHP 8.5.0 was released on 20 November 2025. It gets bug fixes until 31 December 2027 and security fixes until 31 December 2029 (php.net).
WordPress 6.9 added support for PHP 8.5, labelled “beta support” until the core team retired that label in May 2026. The WordPress core team’s compatibility page records both changes and lists WordPress 6.9, 7.0 and 7.1 as compatible with PHP 8.5. That covers WordPress itself. Your site also runs plugins and a theme, and their code is what the PHP 8.5 changes affect.
The short version
The PHP 8.5 changes you’re most likely to meet in plugins and themes. php.net lists the full set of backward incompatible changes and deprecated features.
| What changes in PHP 8.5 | What happens | Kind |
|---|---|---|
setlocale() with 0, in files with strict types | Fatal error | Breaks |
#[Attribute] on an abstract class, interface, trait or enum | The file doesn’t load | Breaks |
class_alias() to array or callable | Fatal error | Breaks |
Backtick operator, case …;, old cast names, __debugInfo() returning null, null as array key, setAccessible(), $http_response_header, clean-up functions like curl_close(), DATE_RFC7231, readdir() without argument | Deprecation notice, code keeps working | Notices |
What breaks: three changes that stop the code
A fatal error stops PHP at that point: the page isn’t built, and WordPress shows “There has been a critical error on this website.” Of the PHP 8.5 changes you’re most likely to meet in plugins and themes, these three cause one. php.net lists the full set of backward incompatible changes, many of them in specific extensions.
setlocale() with 0 in files that use strict types
Some plugins ask PHP for the current language settings (the locale) by passing 0. php.net: “Passing an integer 0 as the locales argument to setlocale() is no longer supported and now throws a TypeError.” (php.net)
In our tests this happens in files that start with declare(strict_types=1). In files without it, PHP 8.5 turns 0 into '0' and the call keeps working, so only strict files are affected.
Code: before, after, and the same call without strict types
wp-content/plugins/price-format/price-format.php
<?php
declare(strict_types=1);
$previous = setlocale(LC_NUMERIC, 0);
echo "Current numeric locale: $previous\n";
Current numeric locale: CFatal error: Uncaught TypeError: setlocale(): Argument #2 ($locales) must be of type array|string|null, int given in /var/www/html/wp-content/plugins/price-format/price-format.php:4
Stack trace:
#0 /var/www/html/wp-content/plugins/price-format/price-format.php(4): setlocale(4, 0)
#1 {main}
thrown in /var/www/html/wp-content/plugins/price-format/price-format.php on line 4The fix is to pass the string '0', which asks for the same information:
wp-content/plugins/price-format/price-format.php
<?php
declare(strict_types=1);
$previous = setlocale(LC_NUMERIC, '0');
echo "Current numeric locale: $previous\n";
Current numeric locale: CThe original call in a file without declare(strict_types=1), on PHP 8.5:
wp-content/plugins/price-format/price-format.php
<?php
$previous = setlocale(LC_NUMERIC, 0);
echo "Current numeric locale: $previous\n";
Current numeric locale: C#[Attribute] on an abstract class, interface, trait or enum
php.net: “Applying #[\Attribute] to an abstract class, enum, interface, or trait triggers an error during compilation. Previously, the attribute could be added, but when ReflectionAttribute::newInstance() was called an error would be thrown.” (php.net)
In plain words: code that was only wrong in a corner case now stops the whole file from loading, even if that corner case never happens on your site.
Code: before and after
wp-content/plugins/shop-api/src/Route.php
<?php
#[Attribute]
abstract class Route {
}
echo "Routes loaded\n";
Routes loadedFatal error: Cannot apply #[\Attribute] to abstract class Route in /var/www/html/wp-content/plugins/shop-api/src/Route.php on line 3
Stack trace:
#0 {main}Put the attribute on a class that can actually be used as one, for example a final subclass:
wp-content/plugins/shop-api/src/Route.php
<?php
abstract class Route {
}
#[Attribute]
final class Get extends Route {
}
echo "Routes loaded\n";
Routes loadedPHP 8.5 also adds #[\DelayedTargetValidation], which moves the error back to the moment the attribute is used (php.net); fixing the target is the cleaner way.
class_alias() to array or callable
php.net: “It is no longer possible to use “array” and “callable” as class alias names in class_alias().” (php.net) PHP ignores upper and lower case here, so Array fails too.
Code: before and after
wp-content/plugins/shop-api/helpers.php
<?php
class ArrayHelper {
}
class_alias('ArrayHelper', 'Array');
echo "Helpers loaded\n";
Helpers loadedFatal error: Cannot use "array" as a class alias as it is reserved in /var/www/html/wp-content/plugins/shop-api/helpers.php on line 5
Stack trace:
#0 /var/www/html/wp-content/plugins/shop-api/helpers.php(5): class_alias('ArrayHelper', 'Array')
#1 {main}wp-content/plugins/shop-api/helpers.php
<?php
class ArrayHelper {
}
class_alias('ArrayHelper', 'Arr');
echo "Helpers loaded\n";
Helpers loadedThis is how a check reports the first case in a plugin:
What only shows notices: ten deprecations
A deprecation notice means “this still works, but a future PHP version is expected to change it.” The page is built as usual. PHP writes the notice to the error log, or onto the page if displaying errors is switched on, which should never be the case on a live site (WordPress debugging documentation).
PHP 8.5 deprecates more than ten things. These are the ten you’re most likely to meet in plugins and themes; php.net lists the full set in its deprecated features in PHP 8.5.
Notices can add up: in the readdir() example below, PHP writes the same notice once for every file in the folder.
The backtick operator
`command` runs a server command, as a shortcut for shell_exec(). Replace it with shell_exec().
example.php example.phpCode: before and after
<?php
$greeting = `echo hello`;
echo trim($greeting), "\n";
helloDeprecated: The backtick (`) operator is deprecated, use shell_exec() instead in /var/www/html/example.php on line 2
hello<?php
$greeting = shell_exec('echo hello');
echo trim($greeting), "\n";
hello
case followed by a semicolon
PHP has always accepted a semicolon after case (case 'x'; instead of case 'x':); PHP 8.5 deprecates it. Replace the semicolon with a colon; the same goes for default;.
example.php example.phpCode: before and after
<?php
$mode = 'compact';
switch ($mode) {
case 'compact';
echo "Compact layout\n";
break;
default;
echo "Default layout\n";
}
Compact layoutDeprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead in /var/www/html/example.php on line 4
Deprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead in /var/www/html/example.php on line 7
Compact layout<?php
$mode = 'compact';
switch ($mode) {
case 'compact':
echo "Compact layout\n";
break;
default:
echo "Default layout\n";
}
Compact layout
Old cast names
(boolean), (integer), (double) and (binary) become (bool), (int), (float) and (string).
example.php example.phpCode: before and after
<?php
$value = '42 items';
$count = (integer) $value;
echo "Count: $count\n";
Count: 42Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in /var/www/html/example.php on line 3
Count: 42<?php
$value = '42 items';
$count = (int) $value;
echo "Count: $count\n";
Count: 42
__debugInfo() returning null
This method tells PHP what to show when an object is inspected for debugging. Return an empty array instead of null.
example.php example.phpCode: before and after
<?php
class Cart {
public function __debugInfo() {
return null;
}
}
var_dump(new Cart());
object(Cart)#1 (0) {
}Deprecated: Returning null from Cart::__debugInfo() is deprecated, return an empty array instead in /var/www/html/example.php on line 8
object(Cart)#1 (0) {
}<?php
class Cart {
public function __debugInfo() {
return [];
}
}
var_dump(new Cart());
object(Cart)#1 (0) {
}
null as an array key
PHP has always stored null keys as an empty string; now it says so. Write ''.
example.php example.phpCode: before and after
<?php
$labels = [null => 'None', 'a' => 'Option A'];
echo $labels[''], "\n";
NoneDeprecated: Using null as an array offset is deprecated, use an empty string instead in /var/www/html/example.php on line 2
None<?php
$labels = ['' => 'None', 'a' => 'Option A'];
echo $labels[''], "\n";
None
setAccessible() on Reflection objects
It has done nothing since PHP 8.1. Call it only on older versions, or remove it.
example.php example.phpCode: before and after
<?php
class Order {
private $total = 99;
}
$property = new ReflectionProperty(Order::class, 'total');
$property->setAccessible(true);
echo $property->getValue(new Order()), "\n";
99Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in /var/www/html/example.php on line 7
99<?php
class Order {
private $total = 99;
}
$property = new ReflectionProperty(Order::class, 'total');
if (PHP_VERSION_ID < 80100) {
$property->setAccessible(true);
}
echo $property->getValue(new Order()), "\n";
99
$http_response_header
After fetching a web address, PHP used to fill this variable with the response headers. Use http_get_last_response_headers() (PHP 8.4 and later). The notice appears as soon as the file loads, even if the code never runs.
example.php example.phpCode: before and after
<?php
function fetch_status(string $url): string {
file_get_contents($url);
return $http_response_header[0];
}
echo "Loaded\n";
LoadedDeprecated: The predefined locally scoped $http_response_header variable is deprecated, call http_get_last_response_headers() instead in /var/www/html/example.php on line 4
Loaded<?php
function fetch_status(string $url): string {
file_get_contents($url);
$headers = function_exists('http_get_last_response_headers')
? http_get_last_response_headers()
: ($http_response_header ?? []);
return $headers[0] ?? '';
}
echo "Loaded\n";
Loaded
Clean-up functions that do nothing: curl_close() and others
curl_close(), curl_share_close(), imagedestroy() and xml_parser_free() have had no effect since PHP 8.0, and finfo_close() isn’t needed because PHP frees these objects automatically. Remove the call, or keep it only for older PHP versions. The aliases mysqli_execute() and socket_set_timeout() are deprecated too.
example.php example.phpCode: before and after
<?php
$handle = curl_init();
curl_close($handle);
echo "Done\n";
DoneDeprecated: Function curl_close() is deprecated since 8.5, as it has no effect since PHP 8.0 in /var/www/html/example.php on line 3
Done<?php
$handle = curl_init();
if (PHP_VERSION_ID < 80000) {
curl_close($handle);
}
echo "Done\n";
Done
DATE_RFC7231
php.net deprecates this constant “because the associated timezone is ignored and always uses GMT.” Write the format out, in GMT.
example.php example.phpCode: before and after
<?php
echo date(DATE_RFC7231, 0), "\n";
Thu, 01 Jan 1970 00:00:00 GMTDeprecated: Constant DATE_RFC7231 is deprecated since 8.5, as this format ignores the associated timezone and always uses GMT in /var/www/html/example.php on line 2
Thu, 01 Jan 1970 00:00:00 GMT<?php
echo gmdate('D, d M Y H:i:s \G\M\T', 0), "\n";
Thu, 01 Jan 1970 00:00:00 GMT
readdir() and friends without an argument
Without an argument, these functions used the last opened directory. Pass the directory handle explicitly.
example.php example.phpCode: before and after
<?php
$dir = opendir(__DIR__);
$count = 0;
while (false !== ($entry = readdir())) {
$count++;
}
closedir();
echo "Entries: $count\n";
Entries: 3Deprecated: readdir(): Passing null is deprecated, instead the last opened directory stream should be provided in /var/www/html/example.php on line 4
Deprecated: readdir(): Passing null is deprecated, instead the last opened directory stream should be provided in /var/www/html/example.php on line 4
Deprecated: readdir(): Passing null is deprecated, instead the last opened directory stream should be provided in /var/www/html/example.php on line 4
Deprecated: readdir(): Passing null is deprecated, instead the last opened directory stream should be provided in /var/www/html/example.php on line 4
Deprecated: closedir(): Passing null is deprecated, instead the last opened directory stream should be provided in /var/www/html/example.php on line 7
Entries: 3<?php
$dir = opendir(__DIR__);
$count = 0;
while (false !== ($entry = readdir($dir))) {
$count++;
}
closedir($dir);
echo "Entries: $count\n";
Entries: 3
What no code reader can see
Some PHP 8.5 changes depend on the values a plugin works with while it runs, so no tool that reads code can predict them. From php.net’s lists of incompatible changes and deprecations:
- “Incrementing non-numeric strings is now deprecated.”
- “Passing integers outside the interval [0, 255] to chr() is now deprecated.”
nullas an array key when the key comes from a variable.- “Destructuring non-array values (other than null) using [] or list() now emits a warning.”
- “A warning is now emitted when casting floats (or strings that look like floats) to int if they cannot be represented as one.”
These produce deprecation notices or warnings, not fatal errors, but they are a good reason to look at your error log for a few days after switching.
How to check your site before switching
- Find your current PHP version: Tools → Site Health → Info → Server.
- Check your plugins and theme for PHP 8.5. With CompatNav, choose PHP 8.5 next to Check readiness for and start a scan: each plugin and theme gets its own list, with what will break and what only shows notices.
- Fix what will break before you switch: update, ask the developer, or replace (what to do). Notices can wait for the plugin’s next update.
- Switch, then check your important pages and your error log.
If something does break after the switch, “There has been a critical error” after a PHP update shows how to get your site back and find the cause. If your site is still on PHP 8.2, note its end-of-life date: PHP 8.2 reaches end of life on 31 December 2026.
Key takeaways
- PHP 8.5 was released on 20 November 2025 and gets security fixes until 31 December 2029 (php.net). WordPress 7.1 is compatible with it.
- Of the changes you’re most likely to meet in plugins and themes, three stop code with a fatal error:
setlocale()with0under strict types,#[Attribute]on abstract classes, interfaces, traits or enums, andclass_alias()toarrayorcallable. - Ten others only log deprecation notices; the code keeps working, but the notices can fill your error log. php.net lists the full set of changes.
- Some changes depend on values at run time, which no code reader can see: check your site after switching too.
- Don’t edit a plugin yourself: update it, ask its developer, or replace it.
Frequently asked questions
Does WordPress support PHP 8.5?
Yes. WordPress 6.9 added support for PHP 8.5 (called “beta support” until the core team retired that label in May 2026), and the WordPress core team’s compatibility page lists WordPress 6.9, 7.0 and 7.1 as compatible with it. Your plugins and theme are a separate question.
Is PHP 8.5 stable for WordPress?
PHP 8.5 is a regular release: 8.5.0 came out on 20 November 2025 and the branch gets bug fixes until 31 December 2027 and security fixes until 31 December 2029 (php.net). For WordPress core it is listed as compatible. Whether it is stable for your site depends on your plugins and theme.
Should I switch to PHP 8.5 now or wait?
Switch when your plugins and theme are ready for it and your host offers it. Waiting has no benefit if the check is clean; if it isn’t, fix or replace what it reports first.
One of my plugins isn’t ready. What now?
Update it if an update is available. If you already have the latest version, contact its developer, or replace it with a maintained alternative. Not ready yet: what to do explains each option.
Do deprecation notices slow my site down?
Each notice is written to the error log, or onto the page if displaying errors is switched on. A few are harmless; thousands per page view fill the log and add work to every request. The main reason to fix them is that they announce changes in a future PHP version.
Check your own site before you upgrade
CompatNav is a free WordPress plugin. It reads the code of your plugins and themes on your own server and tells you, in plain words, what will break and what will only show notices on the PHP version you choose. It never changes your code. It can’t see problems that only appear while code runs with real data, so a quick check of your site after the upgrade still matters.
Sources
- php.net: Supported Versions
- php.net: Backward Incompatible Changes in PHP 8.5.x
- php.net: Deprecated features in PHP 8.5.x
- make.wordpress.org: PHP Compatibility and WordPress Versions
About the code examples: each output is the real output of the code shown, run with the official PHP builds, without a php.ini, with all errors reported and displayed. Only the file path was replaced by a neutral server path.