WordPress Broke After a PHP 8 Upgrade? How to Fix the Fatal Errors
Your host emailed that PHP 7.4 is end-of-life, you switched to PHP 8, and the site broke. Maybe it's a critical error, maybe a blank page, maybe just one feature that stopped working. WordPress core itself runs fine on PHP 8. What breaks is old theme and plugin code. Here's how to find that code and fix it, using the actual error messages you'll see.
Get the site back first. In your hosting panel (cPanel "Select PHP Version", Plesk "PHP Settings", or your host's dashboard), switch back to the previous PHP version. That restores the site in seconds. Then fix the code on a staging copy and switch PHP there first.
Step 1: Find every incompatible line
Don't fix errors one at a time as they show up in production. Scan the code upfront.
Scan with PHPCompatibility
PHPCompatibilityWP is a ruleset for PHP_CodeSniffer that flags code that won't run on a target PHP version. On your local machine or staging server:
mkdir php-scan && cd php-scan
composer require --dev dealerdirect/phpcodesniffer-composer-installer phpcompatibility/phpcompatibility-wp:"*"
# Scan a theme for PHP 8.0 and newer
vendor/bin/phpcs -p /path/to/wp-content/themes/my-theme \
--standard=PHPCompatibilityWP \
--extensions=php \
--runtime-set testVersion 8.0-
Composer will ask whether to allow the dealerdirect/phpcodesniffer-composer-installer plugin. Answer yes. Run the scan against your theme, child theme and every plugin that isn't from a major, actively maintained vendor. Errors (marked ERROR) will break on PHP 8. Warnings are deprecations that still run but should be fixed.
Then confirm on staging with the debug log
Static scans miss some runtime problems, like type errors that depend on data. Switch staging to PHP 8, enable WP_DEBUG_LOG (see the critical error guide for the exact lines), click through every important page and form, and read wp-content/debug.log.
Step 2: Fix the common fatal errors
These are the PHP 8.0 changes behind most broken WordPress sites I see.
create_function() removed
PHP Fatal error: Uncaught Error: Call to undefined function create_function()
Very common in old widgets and theme option panels. Replace it with an anonymous function:
// Before (PHP 7 and older)
add_action( 'widgets_init', create_function( '', 'return register_widget("My_Widget");' ) );
// After
add_action( 'widgets_init', function () {
register_widget( 'My_Widget' );
} );
each() removed
PHP Fatal error: Uncaught Error: Call to undefined function each()
// Before
while ( list( $key, $value ) = each( $array ) ) { /* ... */ }
// After
foreach ( $array as $key => $value ) { /* ... */ }
count() on null or a string
PHP Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given
PHP 7.2 gave a warning here. PHP 8 throws an error. It usually happens when get_option() or get_post_meta() returns an empty value instead of an array:
// Before
if ( count( $items ) > 0 ) { /* ... */ }
// After
if ( is_array( $items ) && count( $items ) > 0 ) { /* ... */ }
// or simply
if ( ! empty( $items ) ) { /* ... */ }
Unsupported operand types
PHP Fatal error: Uncaught TypeError: Unsupported operand types: string + int
PHP 8 throws on arithmetic with arrays or with strings that aren't numbers. Typical WooCommerce example: a price meta field that is empty ('') being added to a number. Cast and validate:
// Before
$total = $price + $shipping;
// After
$total = (float) $price + (float) $shipping;
Casting hides bad data, so if the value should never be empty, also find out why it is.
Curly-brace string offsets
PHP Fatal error: Array and string offset access syntax with curly braces is no longer supported
// Before
$first = $string{0};
// After
$first = $string[0];
Other removed functions
get_magic_quotes_gpc(): remove the check entirely. Magic quotes haven't existed since PHP 5.4.money_format(): usenumber_format()or theNumberFormatterclass from the intl extension.ereg(),split(),mysql_*(): removed long before PHP 8. If you still find these, the code is very old and needs a careful review, not just a patch.
Step 3: Fix deprecations before they become errors
Deprecations still run, but they fill your logs and turn into fatal errors in later versions. The ones I see most in WordPress code:
Optional parameter before a required one (PHP 8.0)
Deprecated: Optional parameter $args declared before required parameter $id is implicitly treated as a required parameter
// Before
function my_render( $args = array(), $id ) { }
// After: reorder, or make both optional
function my_render( $id, $args = array() ) { }
If other code calls this function, update those calls too.
Creation of dynamic properties (PHP 8.2)
Deprecated: Creation of dynamic property My_Plugin::$settings is deprecated
Declare the property in the class:
class My_Plugin {
public $settings = array(); // declare it
public function __construct() {
$this->settings = get_option( 'my_plugin_settings', array() );
}
}
For classes that really need arbitrary properties, add the #[\AllowDynamicProperties] attribute above the class as a stop-gap.
${var} string interpolation (PHP 8.2)
// Deprecated
echo "Hello ${name}";
// Fine
echo "Hello {$name}";
Implicitly nullable parameters (PHP 8.4)
// Deprecated in 8.4
function my_func( WP_Post $post = null ) { }
// Fine
function my_func( ?WP_Post $post = null ) { }
Step 4: Third-party plugins you can't edit
If the incompatible code is in a plugin you didn't write:
- Update it. Most maintained plugins fixed PHP 8 issues years ago.
- Check if it's abandoned. On WordPress.org, a plugin page that says "This plugin hasn't been tested with the latest 3 major releases of WordPress" is a red flag. Look for a maintained alternative.
- Patch it as a last resort, and keep it from being overwritten by an update. I cover the update-safe way to do this in how to customize a plugin without losing changes.
Step 5: Switch production safely
- Fix everything on staging and run it on the target PHP version for a few days with the debug log on.
- Deploy the code changes to production while it's still on the old PHP version. All the fixes above also run on PHP 7.4, as long as you don't add PHP 8-only functions like
str_contains()along the way. - Take a full backup, switch production to PHP 8, and watch the error log for an hour.
- If something breaks, switch PHP back. That's your instant rollback.
Done in that order, the upgrade becomes a non-event instead of an outage.
Written by
Sameer Hussain, Senior Shopify & WordPress Engineer
Top Rated on Upwork and the Shopify & WordPress engineer at Inceptus Digital. Short on time, or not comfortable editing code? Send me your URL and the error on Upwork and I'll tell you how I'd fix it before you commit.
Need Custom Engineering or Growth Systems?
Our team helps ambitious brands build bespoke software, AI integrations, and high-conversion platforms.