PHP warning on every front-end request: Attempt to read property "ID" on null in document.php:356, from WooCommerce Product / Product_Archive enqueue_scripts()
Description
Summary
Elementor Pro's WooCommerce module instantiates its Product and Product_Archive document types as post-less objects. Those instances still register enqueue_scripts on wp_enqueue_scripts, and both implementations call $this->get_main_id(), which dereferences $this->post->ID while $this->post is NULL. This emits two PHP warnings on every front-end request.
Environment
WordPress 7.1.2 Elementor 4.2.4 Elementor Pro 4.3.0 WooCommerce 11.1.1 Theme: Hello Biz PHP 8.3.33 (fpm-fcgi), cPanel/EasyApache display_errors = On
The warning
Warning: Attempt to read property "ID" on null in
/wp-content/plugins/elementor/core/base/document.php on line 356Emitted twice per request.
Stack traces (captured via a custom error handler)
#0 {closure}() elementor/core/base/document.php:356
#1 Elementor\Core\Base\Document->get_main_id()
elementor-pro/modules/woocommerce/documents/product.php:63
#2 ElementorPro\Modules\Woocommerce\Documents\Product->enqueue_scripts()
wp-includes/class-wp-hook.php:353
...
#5 do_action() wp-includes/script-loader.php:2340 [wp_enqueue_scripts]
#0 {closure}() elementor/core/base/document.php:356
#1 Elementor\Core\Base\Document->get_main_id()
elementor-pro/modules/woocommerce/documents/product-archive.php:59
#2 ElementorPro\Modules\Woocommerce\Documents\Product_Archive->enqueue_scripts()
wp-includes/class-wp-hook.php:353
...
#5 do_action() wp-includes/script-loader.php:2340 [wp_enqueue_scripts]
Root cause
Both enqueue_scripts() implementations open with a preview-mode check that calls get_main_id():
product.php:63 — if ( Plugin::elementor()->preview->is_preview_mode( $this->get_main_id() ) ) { product-archive.php:59 — same pattern
Document::get_main_id() (elementor/core/base/document.php:354-368) begins:
php
public function get_main_id() {
if ( ! $this->main_id ) {
$post_id = $this->post->ID; // line 356
Dumping the two objects registered on wp_enqueue_scripts confirms they carry no post at all:
ElementorPro\Modules\Woocommerce\Documents\Product -> enqueue_scripts
Controls_Stack::id = NULL
Controls_Stack::data = NULL
Document::main_id = NULL
Document::post = NULLIdentical for Product_Archive. These are not orphaned templates — they are document-type instances created without a post that nonetheless hook wp_enqueue_scripts. Only these two Woo document classes call get_main_id() inside enqueue_scripts(), which is why exactly two warnings appear and no other document type is affected.
Ruled out (site data is clean)
All 35 theme-builder condition entries (elementor_pro_theme_builder_conditions) reference existing, published posts Zero orphaned _elementor_template_type postmeta rows All WooCommerce page ID options resolve to existing published pages Reproduces with no deleted or trashed templates anywhere on the site
Steps to reproduce
Install Elementor 4.2.4 + Elementor Pro 4.3.0 + WooCommerce on PHP 8.x Set display_errors = On Load any front-end URL
Expected: no PHP warnings. Actual: two Attempt to read property "ID" on null warnings per request.
Impact beyond the log noise
The warnings are emitted during wp_enqueue_scripts, i.e. before the document is fully output. Because the warning markup includes
tags emitted ahead of the doctype, the browser implicitly closes and starts — pushing , the meta description, canonical and Open Graph tags into <body>. On a live site with display_errors on, that is a real SEO regression, not just log noise. It also reproduces on RSS feeds and 404 responses.</p>
<p>Suggested fix</p>
<p>Null-guard Document::get_main_id(), or skip the wp_enqueue_scripts registration entirely for post-less document instances:</p>
<div class="md-code"><div class="md-code-bar"><span class="md-code-lang"></span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs">php
public function get_main_id() {
if ( ! $this->main_id ) {
if ( ! $this->post ) {
return 0;
}
$post_id = $this->post->ID;
...</code></pre></div><p>Workaround in use</p>
<div class="md-code"><div class="md-code-bar"><span class="md-code-lang"></span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs">php
add_action( 'wp_enqueue_scripts', function () {
global $wp_filter;
if ( empty( $wp_filter['wp_enqueue_scripts'] ) ) { return; }
$remove = array();
foreach ( $wp_filter['wp_enqueue_scripts']->callbacks as $prio => $cbs ) {
foreach ( $cbs as $cb ) {
$f = $cb['function'];
if ( ! is_array( $f ) || ! isset( $f[1] ) || 'enqueue_scripts' !== $f[1] || ! is_object( $f[0] ) ) { continue; }
if ( ! ( $f[0] instanceof \Elementor\Core\Base\Document ) ) { continue; }
if ( method_exists( $f[0], 'get_post' ) && null === $f[0]->get_post() ) {
$remove[] = array( $f, $prio );
}
}
}
foreach ( $remove as $r ) { remove_action( 'wp_enqueue_scripts', $r[0], $r[1] ); }
}, 0 );</code></pre></div><h3>Steps to reproduce</h3>
<p>``Here's a tighter, more verifiable "Steps to reproduce" section to drop into the report.</p>
<p>Steps to reproduce</p>
<p>Prerequisites</p>
<p>WordPress 7.1.2
Elementor 4.2.4 + Elementor Pro 4.3.0 (both active)
WooCommerce 11.1.1 active
PHP 8.x (reproduced on 8.3.33, fpm-fcgi)
At least one WooCommerce Single Product and one Products Archive theme-builder template exists (both published, both with valid conditions)</p>
<p>Steps</p>
<p>Set display_errors = On in php.ini (or .user.ini under PHP-FPM). WP_DEBUG can remain false — WordPress's non-debug error_reporting() mask still includes E_WARNING, so the warning surfaces either way.
If a page cache is active (LiteSpeed, WP Rocket, etc.), purge it, or test while logged out of a cache-bypassing session — a cached HTML copy will mask the change in either direction.
Load any front-end URL: the homepage, a page, a single product, /?p=1, a 404 URL, or /feed/.
View source.</p>
<p>Expected</p>
<p>Clean output; no PHP warnings.</p>
<p>Actual</p>
<p>Two warnings emitted before the doctype:</p>
<div class="md-code"><div class="md-code-bar"><span class="md-code-lang"></span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs">Warning: Attempt to read property "ID" on null in
/wp-content/plugins/elementor/core/base/document.php on line 356</code></pre></div><p>Scope notes (from narrowing on the affected site)</p>
<p>Fires on every front-end request type tested: pages, single products, product archives, 404s, and RSS feeds
Does not fire on REST API requests (/wp-json/...) or in wp-admin — REST short-circuits at parse_request, before the wp action
Count is always exactly two per request, one per Woo document type
Independent of which page is loaded — no WooCommerce page or product need be involved</p>
<p>Deterministic verification (works regardless of display_errors)</p>
<p>Add this snippet and load any front-end page. It lists the document objects registered on wp_enqueue_scripts and dumps their properties:</p>
<div class="md-code"><div class="md-code-bar"><span class="md-code-lang"></span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs">php
add_action( 'wp_enqueue_scripts', function () {
global $wp_filter;
$out = '';
foreach ( $wp_filter['wp_enqueue_scripts']->callbacks as $prio => $cbs ) {
foreach ( $cbs as $cb ) {
$f = $cb['function'];
if ( ! is_array( $f ) || ! is_object( $f[0] ) ) { continue; }
if ( ! ( $f[0] instanceof \Elementor\Core\Base\Document ) ) { continue; }
$out .= $prio . ' ' . get_class( $f[0] ) . ' -> ' . $f[1] . "\n";
foreach ( (array) $f[0] as $k => $v ) {
$k = str_replace( chr( 0 ), '|', $k );
if ( is_scalar( $v ) || null === $v ) {
$out .= ' ' . $k . ' = ' . var_export( $v, true ) . "\n";
}
}
}
}
error_log( $out );
}, 99999 );</code></pre></div><p>Expected in the log on any front-end request:</p>
<div class="md-code"><div class="md-code-bar"><span class="md-code-lang"></span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs">11 ElementorPro\Modules\Woocommerce\Documents\Product -> enqueue_scripts
|Elementor\Controls_Stack|id = NULL
|Elementor\Controls_Stack|data = NULL
|Elementor\Core\Base\Document|main_id = NULL
|*|post = NULL
11 ElementorPro\Modules\Woocommerce\Documents\Product_Archive -> enqueue_scripts
|Elementor\Controls_Stack|id = NULL
|Elementor\Controls_Stack|data = NULL
|Elementor\Core\Base\Document|main_id = NULL
|*|post = NULL</code></pre></div><p>Two Document subclasses hooked to wp_enqueue_scripts with post = NULL is the bug — enqueue_scripts() on each then calls get_main_id(), which dereferences that null.</p>
<p>Ruled out — not caused by site data</p>
<p>Verified on the affected install before filing:</p>
<p>All 35 entries in elementor_pro_theme_builder_conditions point to existing, published posts
SELECT for <em>elementor_template_type postmeta rows with no matching post row returns 0
All woocommerce</em>*_page_id options resolve to existing published pages
No trashed or deleted templates present</p>
<h3>Expected behavior</h3>
<p>Elementor Pro's WooCommerce document types should not emit PHP warnings on normal page loads. Document::get_main_id() should handle a null $this->post gracefully, or post-less document instances should not register enqueue_scripts on wp_enqueue_scripts in the first place. Either way, front-end requests should produce no PHP output before the doctype.</p>
<h3>Elementor System Info</h3>
<div class="md-code" data-lang="txt"><div class="md-code-bar"><span class="md-code-lang">txt</span><button type="button" class="md-code-copy" aria-label="Copy">复制</button></div><pre><code class="hljs language-txt">== Server Environment ==
Operating System: Linux
Software: Apache
MySQL version: MySQL Community Server - GPL v8.0.46
PHP Version: 8.3.33
PHP Memory Limit: 1024M
PHP Max Input Vars: 1000
PHP Max Post Size: 128M
GD Installed: Yes
ZIP Installed: Yes
Write Permissions: All right
Elementor Library: Connected
== WordPress Environment ==
Version: 7.1.2
WP Multisite: No
Max Upload Size: 128 MB
Memory limit: 256M
Max Memory limit: 1024M
Permalink Structure: /%year%/%monthnum%/%day%/%postname%/
Language: en_US
Timezone: -5
Admin Email: [email protected]
Debug Mode: Inactive
== Theme ==
Name: Hello Biz
Version: 1.2.2
Author: Elementor Team
Child Theme: No
== User ==
Role: administrator
WP Profile lang: en-US
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36
== Active Plugins ==
Akismet Anti-spam: Spam Protection
Version: 5.7.2
Author: Automattic - Anti-spam Team
Code Snippets
Version: 3.10.2
Author: Code Snippets Pro
Download Plugin
Version: 2.4.6
Author: Download Plugin
Elementor
Version: 4.2.4
Author: Elementor.com
Elementor Pro
Version: 4.3.0
Author: Elementor.com
ElementsKit Lite
Version: 4.0.6
Author: Wpmet
ElementsKit Pro
Version: 4.10.5
Author: Wpmet
Enable Media Replace
Version: 4.2.2
Author: ShortPixel
Google for WooCommerce
Version: 3.9.4
Author: WooCommerce
Gravity Forms
Version: 3.1.2
Author: Gravity Forms
Gravity Forms Widget for Elementor
Version: 1.1.1
Author: GravityKit
Gravity SMTP
Version: 2.3.3
Author: Gravity Forms
Hello Plus
Version: 1.7.8
Author: Elementor.com
IOS DSPS System Configurator
Version: 1.0.0
Author: Innovative Oncology Solutions
LiteSpeed Cache
Version: 7.9.1
Author: LiteSpeed Technologies
Premium Addons for Elementor
Version: 4.11.106
Author: Leap13
Premium Addons PRO
Version: 2.9.66
Author: Leap13
Product Options and Fields
Version: 1.4.1
Author: Addify
Rank Math SEO
Version: 1.0.279
Author: Rank Math SEO
Redirection
Version: 5.10.1
Author: John Godley
Site Kit by Google
Version: 1.188.0
Author: Google
Stripe Tax - Sales tax automation for WooCommerce
Version: 2.1.0
Author:
WebToffee WooCommerce Request A Quote
Version: 1.4.1
Author: WebToffee
WooCommerce
Version: 11.1.1
Author: Automattic
WooCommerce Tax
Version: 3.6.16
Author: WooCommerce
WP File Manager
Version: 8.0.4
Author: mndpsingh287
WP phpMyAdmin
Version: 5.2.2.01
Author: Puvox.software
== Elements Usage ==
product : 4
button : 4
container : 29
heading : 12
shortcode : 7
woocommerce-breadcrumb : 4
woocommerce-product-add-to-cart : 2
woocommerce-product-content : 3
woocommerce-product-data-tabs : 1
woocommerce-product-images : 4
woocommerce-product-related : 1
woocommerce-product-upsell : 3
section : 12
heading : 12
image : 6
premium-addon-button : 6
spacer : 12
text-editor : 6
wp-post : 0
container : 19
ekit-nav-menu : 1
elementskit-heading : 4
elementskit-mail-chimp : 1
elementskit-page-list : 6
elementskit-social-media : 1
gk_elementor_gravity_form : 1
heading : 3
image : 3
premium-addon-button : 3
premium-addon-dual-header : 1
text-editor : 3
woocommerce-product-content : 1
wp-page : 14
animated-headline : 3
container : 62
elementskit-blog-posts : 1
elementskit-button : 3
elementskit-flip-box : 6
elementskit-heading : 7
elementskit-icon-box : 4
elementskit-image-box : 7
elementskit-image-hover-effect : 6
elementskit-image-morphing : 1
elementskit-team : 6
elementskit-testimonial : 1
elementskit-timeline : 2
gk_elementor_gravity_form : 1
heading : 6
html : 6
premium-addon-button : 3
premium-addon-dual-header : 6
premium-image-accordion : 6
shortcode : 2
text-editor : 10
single-post : 1
container : 4
heading : 3
post-info : 1
post-navigation : 1
posts : 1
share-buttons : 1
theme-post-content : 1
product-archive : 31
container : 34
elementskit-advanced-search : 31
elementskit-flip-box : 6
elementskit-woo-category-list : 4
heading : 33
loop-grid : 1
premium-woo-products : 30
text-editor : 3
woocommerce-breadcrumb : 30
== Settings ==
cpt_support: post, page
allow_tracking: yes
editor_break_lines: 1
unfiltered_files_upload: 1
font_display: swap
disabled_elements: elementskit-contact-form7, elementskit-drop-caps, elementskit-social-share, elementskit-caldera-forms, elementskit-we-forms, elementskit-wp-forms, elementskit-ninja-forms, elementskit-fluent-forms
== Features ==
Custom Fonts: 0
Custom Icons: 0
== Integrations ==
woocommerce: Active
== Elementor Experiments ==
Inline Font Icons: Active by default
Additional Custom Breakpoints: Active by default
Container: Active
Optimized Markup: Active by default
Panel Promotions: Active by default
Default to New Theme Builder: Active by default
Elementor MCP WP Abilities API: Inactive by default
Pro Free Trial Popup: Active by default
Nested Elements: Active
Pages Panel: Inactive by default
Atomic Widgets: Inactive by default
Global Classes: Inactive by default
Enforce global classes capabilities: Active by default
Variables: Active by default
Editor v4 (Opt In Page): Active by default
Editor V4: Inactive by default
Components: Active by default
Interactions: Active by default
Widget Creation: Active by default
Generate website markdown: Inactive by default
Dynamic Assets Management: Inactive by default
Import/Export Customization: Active by default
Site Builder: Inactive by default
Elementor Editor Events: Active by default
Atomic Form: Active by default
Loop: Active by default
Menu: Active
In-Editor Feedback: Inactive by default
== Log ==
Log: showing 20 of 392026-07-30 13:03:09 [info] Elementor data updater process has been completed. [array (
'plugin' => 'Elementor',
'from' => '4.2.0',
'to' => '4.2.1',
)]
2026-08-31 14:31:56 [info] Elementor data updater process has been queued. [array (
'plugin' => 'Elementor',
'from' => '4.2.1',
'to' => '4.2.3',
)]
2026-08-31 14:32:48 [info] elementor::elementor_updater Started
2026-08-31 14:32:48 [info] Elementor/Upgrades - _on_each_version Start
2026-08-31 14:32:51 [info] Elementor/Upgrades - _on_each_version Finished
2026-08-31 14:32:51 [info] Elementor data updater process has been completed. [array (
'plugin' => 'Elementor',
'from' => '4.2.1',
'to' => '4.2.3',
)]
2026-08-31 14:32:57 [info] Elementor data updater process has been queued. [array (
'plugin' => 'Elementor',
'from' => '4.2.1',
'to' => '4.2.3',
)]
2026-08-31 14:47:38 [info] elementor-pro::elementor_pro_updater Started
2026-08-31 14:47:38 [info] Elementor Pro/Upgrades - _on_each_version Start
2026-08-31 14:47:38 [info] Elementor Pro/Upgrades - _on_each_version Finished
2026-08-31 14:47:39 [info] Elementor data updater process has been completed. [array (
'plugin' => 'Elementor Pro',
'from' => '4.2.1',
'to' => '4.2.3',
)]
2026-09-07 12:04:50 [info] elementor::elementor_updater Started
2026-09-07 12:04:50 [info] Elementor/Upgrades - _on_each_version Start
2026-09-07 12:04:52 [info] Elementor/Upgrades - _on_each_version Finished
2026-09-07 12:04:52 [info] Elementor data updater process has been completed. [array (
'plugin' => 'Elementor',
'from' => '4.2.3',
'to' => '4.2.4',
)]
2026-09-22 10:58:41 [info] Elementor data updater process has been queued. [array (
'plugin' => 'Elementor Pro',
'from' => '4.2.3',
'to' => '4.3.0',
)]
2026-09-22 11:00:25 [info] elementor-pro::elementor_pro_updater Started
2026-09-22 11:00:25 [info] Elementor Pro/Upgrades - _on_each_version Start
2026-09-22 11:00:25 [info] Elementor Pro/Upgrades - _on_each_version Finished
2026-09-22 11:00:25 [info] Elementor data updater process has been completed. [array (
'plugin' => 'Elementor Pro',
'from' => '4.2.3',
'to' => '4.3.0',
)]
JS: showing 3 of 3JS: 2026-07-28 16:40:18 [error X 1][https://domain.com/wp-content/plugins/elementor/assets/lib/backbone/backbone.marionette.min.js?ver=2.4.5.e1:24:19952] View (cid: "view32194") has already been destroyed and cannot be used.
JS: 2026-07-28 16:43:53 [error X 1][https://domain.com/wp-content/plugins/elementor/assets/lib/backbone/backbone.marionette.min.js?ver=2.4.5.e1:24:19952] View (cid: "view40315") has already been destroyed and cannot be used.
JS: 2026-09-22 18:17:03 [error X 1][https://domain.com/wp-content/plugins/elementor/assets/js/admin.min.js?ver=4.2.4:2:42363] r.startsWith is not a function
PHP: showing 1 of 1PHP: 2026-09-22 10:58:58 [warning X 79][/home/ios/public_html/wp-content/plugins/elementor/core/base/document.php::356] Attempt to read property "ID" on null [array (
'trace' => '
#0: Elementor\Core\Logger\Manager -> shutdown()
',
)]
== Elementor - Compatibility Tag ==
Elementor Pro: Compatibility not specified
Gravity Forms Widget for Elementor: Compatibility not specified
Premium Addons for Elementor: Compatibility not specified
== Elementor Pro - Compatibility Tag ==</code></pre></div><h3>Agreement</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> I confirm I have read and followed all the guidelines and instructions outlined in the Elementor Bug Report form.</li>
<li><input checked="" disabled="" type="checkbox"> I agree that my issue may be closed without further action if it doesn't meet all the requirements outlined in the Elementor Bug Report form.</li>
</ul>
Source: elementor/elementor