WordPress and PHP 8.5: what breaks, what only shows notices

By CompatNav · Published · Last reviewed · 11 min read

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.5What happensKind
setlocale() with 0, in files with strict typesFatal errorBreaks
#[Attribute] on an abstract class, interface, trait or enumThe file doesn’t loadBreaks
class_alias() to array or callableFatal errorBreaks
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 argumentDeprecation notice, code keeps workingNotices

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";

Output on PHP 8.4.26

Current numeric locale: C

Output on PHP 8.5.11the script stopped

Fatal 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 4

The 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";

Output on PHP 8.5.11

Current numeric locale: C

The 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";

Output on PHP 8.5.11

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";

Output on PHP 8.4.26

Routes loaded

Output on PHP 8.5.11the script stopped

Fatal 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";

Output on PHP 8.5.11

Routes loaded

PHP 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";

Output on PHP 8.4.26

Helpers loaded

Output on PHP 8.5.11the script stopped

Fatal 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";

Output on PHP 8.5.11

Helpers loaded

This 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().

Code: before and after

example.php

<?php
$greeting = `echo hello`;
echo trim($greeting), "\n";

Output on PHP 8.4.26

hello

Output on PHP 8.5.11

Deprecated: The backtick (`) operator is deprecated, use shell_exec() instead in /var/www/html/example.php on line 2
hello

example.php

<?php
$greeting = shell_exec('echo hello');
echo trim($greeting), "\n";

Output on PHP 8.5.11

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;.

Code: before and after

example.php

<?php
$mode = 'compact';
switch ($mode) {
    case 'compact';
        echo "Compact layout\n";
        break;
    default;
        echo "Default layout\n";
}

Output on PHP 8.4.26

Compact layout

Output on PHP 8.5.11

Deprecated: 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

example.php

<?php
$mode = 'compact';
switch ($mode) {
    case 'compact':
        echo "Compact layout\n";
        break;
    default:
        echo "Default layout\n";
}

Output on PHP 8.5.11

Compact layout

Old cast names

(boolean), (integer), (double) and (binary) become (bool), (int), (float) and (string).

Code: before and after

example.php

<?php
$value = '42 items';
$count = (integer) $value;
echo "Count: $count\n";

Output on PHP 8.4.26

Count: 42

Output on PHP 8.5.11

Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in /var/www/html/example.php on line 3
Count: 42

example.php

<?php
$value = '42 items';
$count = (int) $value;
echo "Count: $count\n";

Output on PHP 8.5.11

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.

Code: before and after

example.php

<?php
class Cart {
    public function __debugInfo() {
        return null;
    }
}

var_dump(new Cart());

Output on PHP 8.4.26

object(Cart)#1 (0) {
}

Output on PHP 8.5.11

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) {
}

example.php

<?php
class Cart {
    public function __debugInfo() {
        return [];
    }
}

var_dump(new Cart());

Output on PHP 8.5.11

object(Cart)#1 (0) {
}

null as an array key

PHP has always stored null keys as an empty string; now it says so. Write ''.

Code: before and after

example.php

<?php
$labels = [null => 'None', 'a' => 'Option A'];
echo $labels[''], "\n";

Output on PHP 8.4.26

None

Output on PHP 8.5.11

Deprecated: Using null as an array offset is deprecated, use an empty string instead in /var/www/html/example.php on line 2
None

example.php

<?php
$labels = ['' => 'None', 'a' => 'Option A'];
echo $labels[''], "\n";

Output on PHP 8.5.11

None

setAccessible() on Reflection objects

It has done nothing since PHP 8.1. Call it only on older versions, or remove it.

Code: before and after

example.php

<?php
class Order {
    private $total = 99;
}

$property = new ReflectionProperty(Order::class, 'total');
$property->setAccessible(true);
echo $property->getValue(new Order()), "\n";

Output on PHP 8.4.26

99

Output on PHP 8.5.11

Deprecated: 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

example.php

<?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";

Output on PHP 8.5.11

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.

Code: before and after

example.php

<?php
function fetch_status(string $url): string {
    file_get_contents($url);
    return $http_response_header[0];
}

echo "Loaded\n";

Output on PHP 8.4.26

Loaded

Output on PHP 8.5.11

Deprecated: 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

example.php

<?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";

Output on PHP 8.5.11

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.

Code: before and after

example.php

<?php
$handle = curl_init();
curl_close($handle);
echo "Done\n";

Output on PHP 8.4.26

Done

Output on PHP 8.5.11

Deprecated: 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

example.php

<?php
$handle = curl_init();
if (PHP_VERSION_ID < 80000) {
    curl_close($handle);
}
echo "Done\n";

Output on PHP 8.5.11

Done

DATE_RFC7231

php.net deprecates this constant “because the associated timezone is ignored and always uses GMT.” Write the format out, in GMT.

Code: before and after

example.php

<?php
echo date(DATE_RFC7231, 0), "\n";

Output on PHP 8.4.26

Thu, 01 Jan 1970 00:00:00 GMT

Output on PHP 8.5.11

Deprecated: 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

example.php

<?php
echo gmdate('D, d M Y H:i:s \G\M\T', 0), "\n";

Output on PHP 8.5.11

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.

Code: before and after

example.php

<?php
$dir = opendir(__DIR__);
$count = 0;
while (false !== ($entry = readdir())) {
    $count++;
}
closedir();
echo "Entries: $count\n";

Output on PHP 8.4.26

Entries: 3

Output on PHP 8.5.11

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: 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

example.php

<?php
$dir = opendir(__DIR__);
$count = 0;
while (false !== ($entry = readdir($dir))) {
    $count++;
}
closedir($dir);
echo "Entries: $count\n";

Output on PHP 8.5.11

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.”
  • null as 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

  1. Find your current PHP version: Tools → Site Health → Info → Server.
  2. 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.
  3. 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.
  4. 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() with 0 under strict types, #[Attribute] on abstract classes, interfaces, traits or enums, and class_alias() to array or callable.
  • 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.

Get CompatNav on wordpress.org How it works

Sources

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.