Skip to content
ThemesIonic — home
Troubleshooting

Creation of Dynamic Property Is Deprecated: The PHP 8.2 Notice in WordPress

PHP 8.2 started warning whenever code assigns a property a class never declared. The site keeps working. The notice tells you which plugin or theme has not been updated for modern PHP, and you see it only because debugging is switched on.

5 min read intermediate

PHP 8.2 now warns when code writes to a property its class never declared; the site still works, and the message names the class that needs fixing. Deprecated: Creation of dynamic property My_Plugin_Admin::$settings is deprecated in /wp-content/plugins/my-plugin/admin.php on line 31 tells you the class, the property and the line. Nothing has broken. The notice is a deprecation, an early warning that this pattern is on its way out of the language.

What PHP is objecting to

Before 8.2, assigning a property that did not exist simply created it:

class My_Plugin_Admin {
	public function __construct() {
		$this->settings = get_option( 'my_plugin' ); // never declared
	}
}

PHP 8.2 still creates $this->settings, then raises E_DEPRECATED. The aim is to catch typos ($this->setings) that used to silently create a second property, and the deprecation RFC proposes making it an error in PHP 9. Three things are exempt:

  • Classes marked #[\AllowDynamicProperties], and their child classes.
  • stdClass, which is what (object) array() and json_decode() return.
  • Classes that implement __set(), because the assignment goes through __set() instead of creating a property.

Why you only see it now

WordPress itself decides which PHP errors are reported. When WP_DEBUG is false, wp_debug_mode() sets an error_reporting() level that lists the fatal, parse, warning and user-error categories but not E_DEPRECATED. The same plugin produces no output at all on a normal production site.

Switch WP_DEBUG on — to chase another problem, or because a staging copy has it on by default — and every deprecation in every active plugin is reported. With WP_DEBUG_DISPLAY also on, they print at the top of the page, and in wp-admin they can appear before the headers are sent, which turns a harmless notice into a real headers already sent warning and broken redirects. Log to a file instead:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

That keeps the notices in wp-content/debug.log, where they belong. The rest of the setup is in debug mode.

Whose code is it

The path in the message settles it:

Path contains Owner What to do
wp-content/plugins/<name>/ That plugin Update it; if current, report to the author
wp-content/themes/<name>/ That theme Update it, or fix it in your own theme
wp-includes/ or wp-admin/ Core, or a plugin calling core Update WordPress; check the stack trace for the caller
wp-content/mu-plugins/ Your host or your team Fix it yourself

Core prepared for this during the PHP 8.2 cycle: the classes that still rely on dynamic properties — WP_Query, wpdb and more than a hundred others — carry #[AllowDynamicProperties], and the attribute is inherited by subclasses. A plugin can therefore write $query->my_flag = true on a WP_Query without a notice. When a message does point into wp-includes, the class named in it is the clue: if it is not a core class, core is setting a property on an object a plugin handed it, and the stack trace in the log names that plugin.

Fixing your own code

There are three correct fixes, and the right one depends on what the property is for.

Declare it. This is the right answer almost every time:

class My_Plugin_Admin {
	/** @var array */
	private $settings = array();

	public function __construct() {
		$this->settings = (array) get_option( 'my_plugin', array() );
	}
}

Declaring also makes the class's shape readable and lets an editor or static analyser catch the typo PHP was warning you about.

Use an array or a stdClass for genuinely arbitrary data. If the class stores keys it does not know in advance — values from a settings form, fields from an API — keep them in one declared array property:

private $data = array();

public function set( $key, $value ) {
	$this->data[ $key ] = $value;
}

Allow it explicitly. For legacy code you cannot refactor yet, the attribute restores the old behaviour for one class:

#[\AllowDynamicProperties]
class Legacy_Widget {
	// ...
}

The leading backslash matters inside a namespace; without it PHP looks for YourNamespace\AllowDynamicProperties, finds nothing, and the attribute does nothing. Attributes are parsed as comments on PHP 7, so the line is safe on old servers as long as it sits on its own line.

Third-party plugins

Do not edit a plugin to silence the notice: the next update overwrites the change, and you will not know it happened. Instead:

  1. Update the plugin. Maintained plugins have mostly fixed these since PHP 8.2 shipped.
  2. If it is current, send the author the full line from the log, including the path.
  3. If the plugin is abandoned, treat the notice as a sign of what PHP 9 will break and plan a replacement. A plugin that never declared its properties tends to have other problems on new PHP versions, covered in site not working after a PHP update.

Meanwhile, keep WP_DEBUG_DISPLAY off on any site visitors can reach. If you want the log readable while you look for other problems, the only safe way to drop deprecations is a must-use plugin that sets the error level after WordPress does, and that hides every deprecation, including ones you would want to see.

Common mistakes

  • Treating the notice as the cause of a broken page. It never is; look for the fatal error further down the log, or work through plugin conflicts.
  • Downgrading PHP to make it go away. It hides the warning and keeps the debt.
  • Adding #[AllowDynamicProperties] to a subclass when the notice names its parent. The attribute covers its own class and children, never a parent; put it on the class named in the message.
  • Fixing the typo the notice revealed in one place and not the others.

Verify

  • wp-content/debug.log has no new "Creation of dynamic property" lines after the fix and a page load that runs the code.
  • Run the code path that sets the property — a settings save, an admin screen — not just the home page.
  • On production, WP_DEBUG_DISPLAY is false and nothing prints above the page.

Frequently asked

No. A deprecation is a notice, not an error: the property is still created and the code still runs. It warns that a future PHP version is expected to turn this into an error, so the code needs updating before then.
Either the server moved to PHP 8.2 or later, or someone switched on WP_DEBUG. Without WP_DEBUG, WordPress sets an error reporting level that excludes E_DEPRECATED, so the same code ran silently before.
Only as a stopgap. Edits to a third-party plugin disappear on the next update. Report the notice to the author with the full message; for your own code, declare the property instead.

Related guides