Skip to content
ThemesIonic — home
Troubleshooting

Warning: Undefined Array Key — Old Code Meets PHP 8

The same code used to raise a notice that WordPress hid. PHP 8 raises a warning instead, and WordPress reports warnings even with debug mode off. Here is how to find the key, fix the code with defaults, and avoid the empty-string trap that turns it into a fatal error.

5 min read intermediate

PHP read an array element that does not exist, and since PHP 8 that is a warning WordPress reports even with debug mode off. The line reads Warning: Undefined array key "size" in /var/www/html/wp-content/plugins/some-plugin/includes/render.php on line 57 — or Undefined array key 0 for a numeric index. The page keeps working; PHP carries on with null in place of the missing value.

On PHP 7.4 the same code produced Notice: Undefined index: size or Notice: Undefined offset: 0. If your search for the old wording found nothing, that is why.

Why it appeared overnight

PHP 8.0 promoted this message from E_NOTICE to E_WARNING. WordPress, when WP_DEBUG is false, sets PHP's error reporting to a list that includes E_WARNING but not E_NOTICE. So:

PHP WP_DEBUG false WP_DEBUG true
7.4 Notice not reported Reported
8.x Warning reported Reported

Whether a reported warning shows on the page depends on the server's display_errors setting, which WordPress only changes when WP_DEBUG is on, and for AJAX and JSON requests, where it turns display off. Hosts that ship with display on show these warnings to visitors, at the top of the page or inside the layout. Set display_errors off on production in any case, and keep reading the log. Other breakage from the same upgrade is in site not working after a PHP update.

Find the key and the code

The message gives you the key name and the exact file and line. With debug logging on, collect them:

grep -o 'Undefined array key .* on line [0-9]*' wp-content/debug.log | sort | uniq -c | sort -rn | head

The counts matter: one line repeated thousands of times is usually a loop or a template on every page; fix that first. If the path is in a plugin or theme you did not write, update it first, and report it with the key and line if the latest version still has it.

The usual causes in WordPress

Code reads Why the key is missing
$_GET['tab'], $_POST['field'] The request did not include that parameter
$_SERVER['HTTP_REFERER'], $_SERVER['HTTP_USER_AGENT'] Cron, WP-CLI and many bots send no such header
get_option( 'plugin_settings' )['new_key'] Settings saved by an older version, before the key existed
$atts['color'] in a shortcode The attribute was not used in this shortcode
$posts[0] after get_posts() The query returned nothing

Fix it with defaults, not silence

Pick the pattern that fits the source:

// Request data: default when absent.
$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';

// Any array: null-coalescing operator.
$ref = $_SERVER['HTTP_REFERER'] ?? '';

// Settings: merge stored values over a full set of defaults.
$settings = wp_parse_args(
	(array) get_option( 'plugin_settings', array() ),
	array( 'size' => 'medium', 'new_key' => false )
);

// Shortcodes: declare every attribute.
$atts = shortcode_atts( array( 'color' => 'blue' ), $atts, 'my_box' );

wp_parse_args() is the right fix for the settings case: every option array saved before an update gets the keys that were added later, without a migration. shortcode_atts() does the same for shortcode attributes and also drops unknown ones.

For $posts[0], check before reading: if ( $posts ) { $first = $posts[0]; }.

Sanitizing does not replace the existence check: sanitize_text_field( $_POST['field'] ) still reads the missing key first and raises the warning before the sanitizer runs. Check, then sanitize, in that order.

The empty-string trap

get_post_meta( $id, 'key', true ) returns an empty string '' when the meta does not exist, not an array and not null. Code that expects an array and does $meta['field'] does not get the gentle warning on PHP 8: reading a string key from a string throws a TypeError (current versions word it Cannot access offset of type string on string), a fatal error that shows the critical error screen.

$meta = get_post_meta( $post_id, 'layout', true );
$cols = is_array( $meta ) ? ( $meta['cols'] ?? 3 ) : 3;

Its cousins are warnings again: Trying to access array offset on null and Trying to access array offset on false, the second typically from get_option() returning false for an option that was never saved.

isset() versus array_key_exists()

isset( $a['k'] ) and $a['k'] ?? … treat a key that holds null as missing. array_key_exists( 'k', $a ) says it exists. For defaults that difference rarely matters, but when null is a meaningful stored value — "explicitly cleared" as opposed to "never set" — use array_key_exists(), or the default silently overwrites the user's choice.

Common mistakes

  • Prefixing the expression with @ to suppress it. The warning still costs time on every call, and the code still runs with the wrong value.
  • Lowering error_reporting() to hide warnings. In wp-config.php it does nothing, because WordPress resets error reporting right after loading that file; in a must-use plugin it also hides warnings you need, like failed file writes.
  • Fixing only the line in the log when the same pattern appears in ten other places in the file.
  • Leaving warnings displayed on production, where they end up inside page output and cause headers already sent errors.

Verify

  • Reload the page or run the request that triggered it, and the grep above shows no new lines for that file and line.
  • Repeat with the parameter absent: no query string, logged out, and via wp cron event run --due-now for code that also runs in cron.
  • wp option get plugin_settings --format=json shows which keys old installs are actually missing.

Frequently asked

PHP 7.4 reported a missing key as a notice (Undefined index or Undefined offset). PHP 8.0 raised it to a warning with the new wording. With WP_DEBUG off, WordPress hides notices but still reports warnings, so code that was silently wrong for years becomes visible the day the server moves to PHP 8.
The warning itself does not stop the page: PHP continues with null as the value. The risks are around it: the warning printed into HTML or AJAX output, logs filling up, and code that treats the null as real data. The related "Cannot access offset of type string on string" is not a warning but a fatal error.
Use isset() or the ?? operator when a null value should count as missing, which is the usual case. Use array_key_exists() when a key that exists with the value null means something different from a key that is absent. isset() returns false for both.

Related guides