PHP Deprecated Warnings Cluttering Your Site? Here's the Fix
You bumped your PHP version in cPanel - maybe your host nudged you, maybe you did it for the speed bump - and now there's a wall of orange text sitting above your site header: Deprecated: Creation of dynamic property... or Warning: foreach() argument must be of type array|object, null given. The site still works. It just looks broken to every visitor who lands on it. Here's what's actually happening and how to clean it up without just hiding the problem.
Symptom
The page loads and functions normally, but above the doctype or scattered mid-layout you see one or more lines like:
Deprecated: Creation of dynamic property Widget::$instance is deprecated in /home/user/public_html/wp-content/plugins/old-plugin/widget.php on line 42
Warning: Attempt to read property "ID" on null in /home/user/public_html/wp-content/themes/theme/functions.php on line 118
Sometimes it's one line. Sometimes it's forty, and it pushes your logo down the page or breaks a JSON response into invalid JSON (very common with AJAX calls and REST API endpoints - a stray warning at the top of the response body turns valid JSON into garbage the browser console can't parse).
This almost always starts right after a PHP version change, a plugin/theme update, or a migration to a server running a newer PHP build.
Cause
Nothing is actually broken. PHP 8.0 and later got much stricter about things that PHP 7.x quietly allowed:
- Dynamic properties - assigning a value to a class property that was never declared (very common in older plugins and themes) is now deprecated as of PHP 8.2.
- Null coercion - passing
nullto a function that expects a string, array, or int (e.g.strlen(null)) throws a deprecation notice as of PHP 8.1, where it used to fail silently. - Implicitly nullable parameters and a handful of other type-strictness changes tightened in each 8.x point release.
None of these stop execution - that's the whole point of a deprecated notice versus a fatal error. But your PHP install is configured to display these notices directly in the page output instead of only logging them, which is exactly backwards for a live site.
Fix
Step 1 - Stop the bleeding: hide output, keep logging
Never just switch off error reporting entirely - you'll go blind to real fatal errors too. The fix is to log everything but display nothing on a production site. In cPanel:
- Open MultiPHP INI Editor under the Software section.
- Select your domain, switch to Editor Mode.
- Set:
display_errors = Off
log_errors = On
error_log = /home/username/logs/php_errors.log
error_reporting = E_ALL
Keep error_reporting at E_ALL - you still want deprecations and warnings written to the log file so you (or your developer) can fix the real issues. You're only turning off the part that prints them to visitors.
If you're on WordPress specifically, also check wp-config.php for a leftover debug block from a previous troubleshooting session:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', true );
That second line is the culprit nine times out of ten on WordPress sites - someone turned on debug display months ago to chase a different bug and never turned it back off. Change it to:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', true );
This logs everything to wp-content/debug.log without printing a single character on the front end.
Step 2 - Find out what's actually deprecated
Now that nothing shows on the page, pull up the log and see what's generating the noise:
tail -n 100 /home/username/logs/php_errors.log
# or, for WordPress debug log:
tail -n 100 public_html/wp-content/debug.log
Group by file path. Usually it's one or two plugins, or an old theme, doing most of the damage. Check each one against the WordPress.org plugin page (or GitHub repo) for a "Tested up to PHP" note or recent update history. A plugin last updated in 2021 that hasn't touched PHP 8.x compatibility is your prime suspect.
Step 3 - Actually fix or replace the source
Suppressing display is a stopgap, not a solution. Work through the log in order of frequency:
- Update the plugin/theme. Most actively maintained ones fixed dynamic-property and null-coercion warnings a while back - a version bump alone often clears 80% of the noise.
- No update available? Check if there's a maintained fork or alternative plugin doing the same job.
- Custom code in
functions.phpor a must-use plugin? These are usually quick edits - wrap the offending call with a null check, or explicitly declare the class property instead of assigning to it dynamically:
// Before - triggers deprecated notice in PHP 8.2+
class Widget {
public function __construct() {
$this->instance = 1;
}
}
// After
class Widget {
public $instance;
public function __construct() {
$this->instance = 1;
}
}
If it's third-party code you can't edit safely, a small compatibility shim in a must-use plugin that filters error_reporting for that specific file is a last resort - but flag it for replacement, not a permanent fix.
Step 4 - Verify with display back on, briefly
Once you've cleared the log's biggest offenders, flip WP_DEBUG_DISPLAY (or display_errors) back on for five minutes on a staging copy or with your own IP allowlisted, reload every major page type - home, single post, archive, checkout if it's WooCommerce - and confirm the log has gone quiet. Then turn display back off before you forget.
Prevention
- Set
display_errors = Offas a default in MultiPHP INI Editor for every production domain the moment you provision it, not after the first embarrassing screenshot from a client. - Test PHP version bumps on a staging subdomain first. cPanel lets you set PHP per-domain in MultiPHP Manager - point a staging clone at the new version, click through the site, then move the live domain once it's clean.
- Audit plugins before a major PHP jump (7.4 to 8.x, or 8.1 to 8.3) rather than after. Anything not updated in over two years is a risk regardless of what the changelog says.
- Rotate the error log. A verbose plugin can grow
debug.loginto gigabytes over a few weeks and eat your disk quota. Add a monthly cron to trim it, or use JetBackup/logrotate if you're on a VPS.
The short version: deprecated notices are PHP telling you code will break in a future version, not that it's broken now. Log them, don't display them, and work through the list a plugin or two at a time instead of leaving the warnings printed on a live page indefinitely.
Frequently asked questions
Will turning off display_errors hide real fatal errors from me too?
No - as long as log_errors stays On and error_reporting is set to E_ALL, fatal errors still get written to your PHP error log or debug.log. You're only stopping them from printing on the page itself. If the site goes fully white or returns a 500, check the log file first.
Can I just downgrade PHP instead of fixing the warnings?
You can, but it's a temporary fix at best - most hosts eventually deprecate and remove old PHP versions entirely, and you'll hit the same warnings later with less runway to fix them. It's usually faster to update the one or two plugins causing most of the noise than to stay on an unsupported PHP branch.
Why do the warnings break my REST API or AJAX calls but not the rest of the site?
A deprecated notice printed before your JSON response corrupts the response body - the browser gets 'Deprecated: ... {"success":true}' instead of clean JSON, which fails to parse. This is usually the first place people notice the problem, even before they see it on a normal page.
How do I know which plugin is causing a specific warning?
The log line includes the full file path, e.g. /public_html/wp-content/plugins/plugin-name/file.php on line 42. That plugin folder name is your answer - no guessing needed.
Is it safe to just wrap everything in @ to suppress errors?
No. The @ error-suppression operator hides the warning from the log too, not just the display, which means you lose visibility into real problems along with the cosmetic ones. Use display_errors = Off instead - it keeps the log intact while cleaning up the page.