WordPress and PHP 8.4: what changes for plugins and themes
Short answer
WordPress 6.7 and later work with PHP 8.4; your plugins and theme decide whether your site does. Of the PHP 8.4 changes you’re most likely to meet in plugins and themes, most only log deprecation notices, such as “Implicitly marking parameter … as nullable is deprecated”, and the code keeps working. Two can stop code: extensions that are no longer part of PHP (IMAP, OCI8, PDO_OCI, PSpell) and five removed MySQLi constants. php.net lists the full set of changes. Check before you switch.
Is WordPress ready for PHP 8.4?
PHP 8.4.0 was released on 21 November 2024. It gets bug fixes until 31 December 2026 and security fixes until 31 December 2028 (php.net).
The WordPress core team’s compatibility page lists WordPress 6.7 and later as compatible with PHP 8.4. That covers WordPress itself; the PHP 8.4 changes below concern the code of your plugins and theme.
The short version
The PHP 8.4 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.4 | What happens | Kind |
|---|---|---|
| IMAP, OCI8, PDO_OCI and PSpell no longer part of PHP | Code using them stops, unless your host installs them separately | Breaks |
| Five MySQLi constants removed | Fatal error | Breaks |
Parameter with a null default but no ? in its type | Deprecation notice, code keeps working | Notice |
trigger_error() with E_USER_ERROR, CSV functions without the escape argument, E_STRICT, lcg_value() and some mysqli_* functions, session_set_save_handler() with separate functions, a class named _, changed return types of built-in methods | Deprecation notice, code keeps working | Notices |
What can stop the code
A fatal error stops PHP at that point: the page isn’t built, and WordPress shows its critical-error message. Of the PHP 8.4 changes you’re most likely to meet in plugins and themes, two can cause one.
Extensions that are no longer part of PHP
IMAP, OCI8, PDO_OCI and PSpell “have been moved to PECL and are no longer part of the PHP distribution” (php.net). A plugin that uses PHP’s IMAP functions to read email, for example, works on PHP 8.4 only if your host installs that extension separately. That is a server setting, not something a check of your code can see, so ask your host before you switch.
Five removed MySQLi constants
PHP 8.4 removed five MySQLi constants that were never usable or only an alias, for example: “The unused MYSQLI_TYPE_INTERVAL constant, which is currently a stub and an alias for MYSQLI_TYPE_ENUM, has been removed.” (php.net) Code that still names one of them stops on PHP 8.4:
wp-content/plugins/table-viewer/includes/columns.php wp-content/plugins/table-viewer/includes/columns.phpCode: before and after
<?php
$field_type = 247; // the type number MySQL reports for a column
if ($field_type === MYSQLI_TYPE_INTERVAL) {
echo "List column\n";
}
echo "Done\n";
List column
DoneFatal error: Uncaught Error: Undefined constant "MYSQLI_TYPE_INTERVAL" in /var/www/html/wp-content/plugins/table-viewer/includes/columns.php:3
Stack trace:
#0 {main}
thrown in /var/www/html/wp-content/plugins/table-viewer/includes/columns.php on line 3<?php
$field_type = 247; // the type number MySQL reports for a column
if ($field_type === MYSQLI_TYPE_ENUM) {
echo "List column\n";
}
echo "Done\n";
List column
Done
This is how a check reports it in a plugin:
What only shows notices: the deprecations you’re most likely to meet
A deprecation notice means: this still works, but a future PHP version is expected to change it. The page is built as usual, and the notice goes to the error log, or onto the page if displaying errors is switched on, which should never be the case on a live site (how to keep them off your pages).
PHP 8.4 deprecates more than these eight; php.net lists the full set in its deprecated features in PHP 8.4.
Parameters with a null default but no ?
The notice reads “Implicitly marking parameter … as nullable is deprecated”. php.net explains: “A parameter’s type is implicitly widened to accept null if the default value for it is null.” (php.net) The fix is to say it in the type with ?, which works since PHP 7.1. The notice appears as soon as the file is compiled, even if the function is never called.
wp-content/plugins/price-format/includes/format.php wp-content/plugins/price-format/includes/format.phpCode: before and after
<?php
function format_price(float $amount, string $currency = null): string {
return number_format($amount, 2) . ' ' . ($currency ?? 'EUR');
}
echo format_price(19.9), "\n";
19.90 EURDeprecated: format_price(): Implicitly marking parameter $currency as nullable is deprecated, the explicit nullable type must be used instead in /var/www/html/wp-content/plugins/price-format/includes/format.php on line 2
19.90 EUR<?php
function format_price(float $amount, ?string $currency = null): string {
return number_format($amount, 2) . ' ' . ($currency ?? 'EUR');
}
echo format_price(19.9), "\n";
19.90 EUR
trigger_error() with E_USER_ERROR
“Calling trigger_error() with error_level being equal to E_USER_ERROR is now deprecated. Such usages should be replaced by either throwing an exception, or calling exit(), whichever is more appropriate.” (php.net)
wp-content/plugins/licence-check/licence-check.php wp-content/plugins/licence-check/licence-check.phpCode: before and after
<?php
function check_licence($key) {
if ($key === '') {
trigger_error('A licence key is required.', E_USER_ERROR);
}
return true;
}
check_licence('');
Fatal error: A licence key is required. in /var/www/html/wp-content/plugins/licence-check/licence-check.php on line 4Deprecated: Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead in /var/www/html/wp-content/plugins/licence-check/licence-check.php on line 4
Fatal error: A licence key is required. in /var/www/html/wp-content/plugins/licence-check/licence-check.php on line 4<?php
function check_licence($key) {
if ($key === '') {
throw new InvalidArgumentException('A licence key is required.');
}
return true;
}
try {
check_licence('');
} catch (InvalidArgumentException $e) {
echo 'Licence problem: ', $e->getMessage(), "\n";
}
Licence problem: A licence key is required.
CSV functions without the escape argument
“Using the default value for the escape parameter for the fputcsv(), fgetcsv(), and str_getcsv() is now deprecated. It must be passed explicitly either positionally or via named arguments.” (php.net) Passing today’s default, a backslash, keeps the output exactly as it was.
wp-content/plugins/order-export/order-export.php wp-content/plugins/order-export/order-export.phpCode: before and after
<?php
$file = fopen('php://memory', 'r+');
fputcsv($file, ['Order', 'Total']);
rewind($file);
echo stream_get_contents($file);
Order,TotalDeprecated: fputcsv(): the $escape parameter must be provided as its default value will change in /var/www/html/wp-content/plugins/order-export/order-export.php on line 3
Order,Total<?php
$file = fopen('php://memory', 'r+');
fputcsv($file, ['Order', 'Total'], ',', '"', '\\');
rewind($file);
echo stream_get_contents($file);
Order,Total
The E_STRICT constant
“Because the E_STRICT error level was removed, this constant is now deprecated.” (php.net) Old code often leaves it out of the error level; there is nothing left to leave out.
wp-content/plugins/debug-helper/debug-helper.php wp-content/plugins/debug-helper/debug-helper.phpCode: before and after
<?php
error_reporting(E_ALL & ~E_STRICT);
echo "Error level set\n";
Error level setDeprecated: Constant E_STRICT is deprecated in /var/www/html/wp-content/plugins/debug-helper/debug-helper.php on line 2
Error level set<?php
error_reporting(E_ALL);
echo "Error level set\n";
Error level set
lcg_value() and some mysqli_* functions
“lcg_value() is now deprecated, as the function is broken in multiple ways.” (php.net) The same page deprecates mysqli_ping(), mysqli_kill() and mysqli_refresh(), and xml_set_object().
wp-content/plugins/random-quote/random-quote.php wp-content/plugins/random-quote/random-quote.phpCode: before and after
<?php
$chance = lcg_value();
echo $chance < 1 ? "Random number OK\n" : "";
Random number OKDeprecated: Function lcg_value() is deprecated since 8.4, use \Random\Randomizer::getFloat() instead in /var/www/html/wp-content/plugins/random-quote/random-quote.php on line 2
Random number OK<?php
$chance = mt_rand() / mt_getrandmax();
echo $chance <= 1 ? "Random number OK\n" : "";
Random number OK
session_set_save_handler() with separate functions
“Calling session_set_save_handler() with more than two arguments is deprecated.” (php.net) The two-argument form takes an object that implements SessionHandlerInterface.
wp-content/plugins/member-area/includes/sessions.php wp-content/plugins/member-area/includes/sessions.phpCode: before and after
<?php
session_set_save_handler(
function ($path, $name) { return true; },
function () { return true; },
function ($id) { return ''; },
function ($id, $data) { return true; },
function ($id) { return true; },
function ($max_lifetime) { return 0; }
);
echo "Session storage set\n";
Session storage setDeprecated: session_set_save_handler(): Providing individual callbacks instead of an object implementing SessionHandlerInterface is deprecated in /var/www/html/wp-content/plugins/member-area/includes/sessions.php on line 2
Warning: session_set_save_handler(): Session save handler cannot be changed after headers have already been sent in /var/www/html/wp-content/plugins/member-area/includes/sessions.php on line 2
Session storage set<?php
class Member_Session_Handler implements SessionHandlerInterface {
public function open(string $path, string $name): bool { return true; }
public function close(): bool { return true; }
public function read(string $id): string|false { return ''; }
public function write(string $id, string $data): bool { return true; }
public function destroy(string $id): bool { return true; }
public function gc(int $max_lifetime): int|false { return 0; }
}
session_set_save_handler(new Member_Session_Handler(), true);
echo "Session storage set\n";
Session storage set
A class named _
“Naming a class _ is now deprecated” (php.net); names that only start with an underscore are fine.
wp-content/themes/tiny-theme/inc/translate.php wp-content/themes/tiny-theme/inc/translate.phpCode: before and after
<?php
class _ {
public static function text($text) {
return $text;
}
}
echo _::text('Read more'), "\n";
Read moreDeprecated: Using "_" as a class name is deprecated since 8.4 in /var/www/html/wp-content/themes/tiny-theme/inc/translate.php on line 2
Read more<?php
class Tiny_Theme_Text {
public static function text($text) {
return $text;
}
}
echo Tiny_Theme_Text::text('Read more'), "\n";
Read more
Changed return types of built-in methods
A plugin class that extends a PHP class and overrides one of its methods must promise a compatible return type. PHP 8.4 narrowed some of these types, for example DateTime::modify(): “Now has a tentative return type of DateTime. Previously it was DateTime|false.” (php.net) An override written for PHP 8.3 then gets a notice.
wp-content/plugins/event-calendar/includes/class-event-date.php wp-content/plugins/event-calendar/includes/class-event-date.phpCode: before and after
<?php
class Event_Date extends DateTime {
public function modify(string $modifier): DateTime|false {
return parent::modify($modifier);
}
}
$date = new Event_Date('2026-05-01');
echo $date->modify('+1 day')->format('j F Y'), "\n";
2 May 2026Deprecated: Return type of Event_Date::modify(string $modifier): DateTime|false should either be compatible with DateTime::modify(string $modifier): DateTime, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/html/wp-content/plugins/event-calendar/includes/class-event-date.php on line 3
2 May 2026DateTime. It is narrower than the old one, which PHP allows, so the same code runs without a notice from PHP 8.0 to 8.5. (The #[\ReturnTypeWillChange] attribute the message names only silences the notice.)<?php
class Event_Date extends DateTime {
public function modify(string $modifier): DateTime {
return parent::modify($modifier);
}
}
$date = new Event_Date('2026-05-01');
echo $date->modify('+1 day')->format('j F Y'), "\n";
2 May 20262 May 20262 May 20262 May 2026
What no code reader can see
Some PHP 8.4 changes depend on the values a plugin works with while it runs. For example, raising zero to a negative power “corresponds to dividing by 0 … Thus, this behavior has been deprecated.”, and “Passing invalid options to hash functions is now deprecated.” (php.net) Whether that happens depends on the data, so 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.
- Ask your host whether the extensions your plugins use (for example IMAP) stay available on PHP 8.4.
- Check your plugins and theme for PHP 8.4. With CompatNav, choose PHP 8.4 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 plugins’ next updates.
- Switch, then check your important pages and your error log.
PHP 8.5 changes more; if your host offers it, compare with WordPress and PHP 8.5: what breaks, what only shows notices. Which version to choose and until when each gets security fixes: PHP 8.2 reaches end of life on 31 December 2026.
Key takeaways
- PHP 8.4 was released on 21 November 2024 and gets security fixes until 31 December 2028 (php.net). WordPress 6.7 and later are compatible with it.
- One of the notices reads “Implicitly marking parameter … as nullable is deprecated”: a parameter with a
nulldefault and no?in its type. The code keeps working. - Two changes can stop code: extensions moved out of PHP (ask your host whether it still installs them) and five removed MySQLi constants.
- Of the changes you’re most likely to meet in plugins and themes, seven others only log deprecation notices. php.net lists the full set.
- Some changes depend on values while the code runs, which no code check can see: look at your error log after switching.
Frequently asked questions
Is PHP 8.4 stable for WordPress?
PHP 8.4 is a regular release: 8.4.0 came out on 21 November 2024; it gets bug fixes until 31 December 2026 and security fixes until 31 December 2028 (php.net). The WordPress core team lists WordPress 6.7 and later as compatible with it. Whether your site is ready depends on your plugins and theme.
PHP 8.3 or PHP 8.4?
If your plugins and theme are ready for both, 8.4: its security fixes run a year longer (until 31 December 2028, against 31 December 2027 for 8.3). PHP 8.5 runs longer still, until 31 December 2029.
What does “Implicitly marking parameter … as nullable is deprecated” mean?
A function in a plugin accepts null for a parameter only because its default value is null, without saying so in the type. PHP 8.4 still accepts it and adds this notice. The fix is one character in the code: a ? before the type. The plugin’s developer makes it; you update the plugin.
Do I have to fix the notices before switching?
No. Deprecation notices don’t stop your site. Fix what will break before you switch; notices can wait for the plugins’ next updates.
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.4.x
- php.net: Deprecated features in PHP 8.4.x
- php.net: Removed extensions in PHP 8.4
- php.net: DateTime::modify() (changelog)
- 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.