Is my WordPress plugin compatible with PHP 8.3, 8.4 or 8.5?
You cannot tell from the plugin’s label, and nobody can promise it for you. A plugin that works today can log errors, or stop, on a newer PHP version. Below: what the labels say, three ways to find out, and ten PHP 8.3 to 8.5 changes worth looking for, each with a before and after example.
What the labels say, and what they do not
A plugin’s listing carries two fields that look like answers and are not.
| Field | What it tells you | What it does not tell you |
|---|---|---|
| Tested up to | The WordPress version the author last tested the plugin with. | Anything about PHP. |
| Requires PHP | The oldest PHP version the plugin supports. | That it runs on newer versions. It is a minimum, not a maximum. |
A changelog line such as “PHP 8.4 compatibility” is the author’s statement about the release it appears in. It helps, and it cannot cover PHP versions released after it was written. Treat every label as a hint, and test.
Three ways to find out
Test on a staging copy
Switch a copy of your site to the PHP version you want, add these lines to wp-config.php, click through the site and read wp-content/debug.log.
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
Good at: it runs the real code with your real settings and data. Limits: only the code you trigger runs. A task that runs once a month, an export or a rarely used admin screen can be missed.
Read the changelog and the support forum
Good at: it is quick, and it shows whether the author has dealt with newer PHP versions and whether other users hit problems on yours. Limits: it depends on the author writing things down, an empty forum is not evidence that all is well, and a note describes the release it was written for, not the one you run.
Run a static scan
A static scan reads the code without running it and matches it against lists of constructs that PHP removed or deprecated. It can be a plugin, like the one in the box further down, or a command-line tool such as PHP_CodeSniffer with the PHPCompatibility rules.
Good at: it covers every file, including code that rarely runs, scanning does not execute the plugin’s code, and it names a file and a line. Limits: it only finds constructs it has rules for, it cannot see problems that depend on your data or server setup, and a flagged line may sit in code that never runs.
Use two of the three: a scan finds known problem constructs everywhere, and a staging test shows what happens on your site.
Ten changes to look for
All ten are deprecations. The code still runs and PHP logs a notice, but a deprecated feature may be removed in a later PHP version. Each notice ends with the file and line, for example in /path/to/plugin.php on line 12, which is how a log or a scan points you to the spot. The notices below are the text PHP prints. We ran every before and after pair on PHP 8.3.36, 8.4.27 and 8.5.12.
| Change | PHP | Replace with |
|---|---|---|
| get_class() without an argument | 8.3 | self::class |
| Implicit nullable parameter types | 8.4 | ?string $title = null |
| The E_STRICT constant | 8.4 | Remove it from the expression |
| trigger_error() with E_USER_ERROR | 8.4 | Throw an exception |
| CSV functions without $escape | 8.4 | Pass $escape explicitly |
| CURLOPT_BINARYTRANSFER | 8.4 | Delete the line |
| curl_close() and similar | 8.5 | Delete the call |
| The backtick operator | 8.5 | shell_exec() |
| Non-canonical casts | 8.5 | (int), (bool), (float), (string) |
| PDO::MYSQL_ATTR_* constants | 8.5 | Pdo\Mysql::ATTR_* |
get_class() without an argument
Inside a class method, get_class() with no argument returns the class where the method is written. self::class gives the same name. get_parent_class() without an argument is deprecated too; use parent::class. To get the class of the running object, use static::class.
$name = get_class();
PHP 8.3 logsDeprecated: Calling get_class() without arguments is deprecated
$name = self::class;
Implicit nullable parameter types
A typed parameter with a null default, such as string $title = null, used to count as nullable without saying so. PHP 8.4 wants it written out. The notice appears when PHP loads the file, even if the function is never called, so a library with many such functions logs many notices. ?string works on PHP 7.1 and later; string|null needs PHP 8.0.
function render( string $title = null ) {
return $title;
}
PHP 8.4 logsDeprecated: render(): Implicitly marking parameter $title as nullable is deprecated, the explicit nullable type must be used instead
function render( ?string $title = null ) {
return $title;
}
The E_STRICT constant
The engine no longer uses the E_STRICT error level, and the constant is deprecated. It turns up in code that sets the error level. Remove it from the expression: there is nothing left to hide.
error_reporting( E_ALL & ~E_STRICT );
PHP 8.4 logsDeprecated: Constant E_STRICT is deprecated
error_reporting( E_ALL );
trigger_error() with E_USER_ERROR
Passing E_USER_ERROR to trigger_error() is deprecated. The PHP manual says to throw an exception or call exit() instead. The script still stops with a fatal error, as before; the notice is what is new. An exception also lets the calling code catch the problem.
trigger_error( 'Cannot connect', E_USER_ERROR );
PHP 8.4 logsDeprecated: Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead
throw new RuntimeException( 'Cannot connect' );
CSV functions without $escape
Calling fputcsv(), fgetcsv() or str_getcsv() without the $escape argument is deprecated, because its default value will change. Pass it. A single backslash is today’s default, so the output stays the same. An empty string turns the escape character off, which changes how fields that contain a backslash are written and read, so test those.
fputcsv( $handle, $row );
PHP 8.4 logsDeprecated: fputcsv(): the $escape parameter must be provided as its default value will change
fputcsv( $handle, $row, ',', '"', '\\' );
CURLOPT_BINARYTRANSFER
The constant is deprecated. PHP 8.5’s own notice says the option has had no effect since PHP 5.1.2, so you can delete the line.
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_BINARYTRANSFER, true );
PHP 8.4 logsDeprecated: Constant CURLOPT_BINARYTRANSFER is deprecated
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_close() and other close and free functions
PHP 8.5 deprecates curl_close(), curl_share_close(), finfo_close(), imagedestroy() and xml_parser_free(), because the objects they released are freed automatically. Delete the calls; nothing replaces them.
$response = curl_exec( $ch );
curl_close( $ch );
PHP 8.5 logsDeprecated: Function curl_close() is deprecated since 8.5, as it has no effect since PHP 8.0
$response = curl_exec( $ch );
The backtick operator
Backticks around a command run it through the shell, like shell_exec(). PHP 8.5 deprecates the backtick form. Replace the backticks with a shell_exec() call.
$user = `whoami`;
PHP 8.5 logsDeprecated: The backtick (`) operator is deprecated, use shell_exec() instead
$user = shell_exec( 'whoami' );
Non-canonical casts
The long cast names (integer), (boolean), (double) and (binary) are deprecated. Use (int), (bool), (float) and (string), which behave the same.
$count = (integer) $value;
PHP 8.5 logsDeprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead
$count = (int) $value;
PDO driver constants such as PDO::MYSQL_ATTR_*
Driver-specific constants on the PDO class are deprecated in favor of constants on a class for each driver. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY becomes Pdo\Mysql::ATTR_USE_BUFFERED_QUERY, and the PHP manual lists the other drivers’ constants the same way. The new class exists from PHP 8.4, so on 8.3 and older the new name is a fatal error. Code that must run on both can test with defined() first.
$options = [ PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true ];
PHP 8.5 logsDeprecated: Constant PDO::MYSQL_ATTR_USE_BUFFERED_QUERY is deprecated since 8.5, use Pdo\Mysql::ATTR_USE_BUFFERED_QUERY instead
$buffered = defined( 'Pdo\Mysql::ATTR_USE_BUFFERED_QUERY' )
? Pdo\Mysql::ATTR_USE_BUFFERED_QUERY
: PDO::MYSQL_ATTR_USE_BUFFERED_QUERY;
$options = [ $buffered => true ];
Beyond these ten
Removals are harsher: a removed function ends in a fatal error, not a notice. One example is PHP 8.4 moving the IMAP, OCI8, PDO_OCI and PSpell extensions out of the PHP distribution (PHP manual). The manual lists every release’s backward-incompatible changes and deprecations; the links are under Sources.
Sources
- PHP manual, deprecated features: 8.3, 8.4, 8.5.
- PHP manual, backward-incompatible changes: 8.3, 8.4, 8.5.
- PHP manual, removed extensions in 8.4.
- The notices and the before and after code were run on PHP 8.3.36, 8.4.27 and 8.5.12 on 1 November 2026.
General information, not a guarantee for any particular plugin or site. Test on a copy before you change your live site. This page was last checked on 1 November 2026.