ACF get_field with 'option' fails to read on front end requests
Description
Subject: Elementor Pro's ACF Dynamic Tags integration breaks every get_field($field, 'option') call on front-end pages
Summary
Elementor Pro's ACF Dynamic Tags module hooks the ACF filter acf/pre_load_post_id and returns its $post_id argument unconditionally, on every request - not only when an Elementor dynamic tag is actually being resolved, and not only in genuine preview mode. Because ACF's own resolver treats any non-null return from this filter as authoritative and returns immediately, Elementor's callback short-circuits ACF's normal 'option' → 'options' aliasing before it ever runs. The practical effect: get_field($any_field, 'option') (singular) silently returns null/false on any front-end page where Elementor Pro is active - even though the identical call from wp-admin, or on a page with 'options' (plural), works correctly.
Environment
WordPress version: latest Elementor version: latest Elementor Pro version: latest Advanced Custom Fields Pro version: latest PHP version: 8.4 Confirmed with a plain custom plugin (no page builder markup involved) reading an ACF Options Page field via get_field($field_name, 'option') on a normal front-end shortcode/template render- not inside any Elementor-edited content. Steps to Reproduce
Create an ACF Options Page (e.g. via acf_add_options_page()), with at least one field group assigned to it. Set a value for one of its fields in wp-admin. On any front-end page/template - a plain shortcode, a template file, anything outside Elementor's own editor - call get_field('your_field_name', 'option') (the singular form). With Elementor Pro active, this returns null/empty, even though the field genuinely has a value. Deactivate Elementor Pro (or remove/dequeue its ACF Dynamic Tags module) and repeat step 3 - the same call now correctly returns the field's value. Expected Behavior
get_field($field, 'option') should resolve the Options Page field's value regardless of whether Elementor Pro is active, exactly as it does with Elementor Pro deactivated.
Actual Behavior
The call silently returns null/empty with no PHP error, warning, or notice (yikes) nothing indicates ACF's resolution was intercepted. This makes the bug very difficult to diagnose from the calling plugin's side: it looks exactly like "the field has no value," not "something upstream hijacked post-ID resolution."
When It Does Not Occur (useful for isolating the cause)
Reading the same field from wp-admin (e.g. inside an admin-ajax handler, cron job, or wp-admin page render) are unaffected. Reading with the plural form, get_field($field, 'options') are unaffected. (This is coincidental, not a real fix - see Root Cause below.) Reading a field on a normal numeric post ID (get_field($field, 123)) is unaffected, since Elementor's callback happens to return the same value ACF would have used anyway for a real post ID. Deactivating Elementor Pro entirely is unaffected (confirms the interception is the cause, not something else in our stack). Root Cause
Confirmed by directly instrumenting ACF Pro's own resolver, acf_get_valid_post_id() (includes/api/api-helpers.php in the installed ACF Pro copy), and by dumping the live registered callbacks on $wp_filter['acf/pre_load_post_id'] in a production/dev environment. ACF Pro's function begins:
function acf_get_valid_post_id( $post_id = 0 ) { ... $preload = apply_filters( 'acf/pre_load_post_id', null, $post_id ); if ( $preload !== null ) { return $preload; // <-- returns here, skipping everything below } if ( $post_id === 'option' ) { $post_id = 'options'; // never reached once short-circuited above } ... } The registered callback intercepting this is:
ElementorPro\Modules\DynamicTags\ACF\Module::filter_post_in_preview, hooked to acf/pre_load_post_id at the default priority (10).
Its implementation returns the raw $post_id value it was passed rather than null, and - based on our observed behavior - does this on every request where the filter fires, not gated to only Elementor's own preview/editor context. Since ACF treats any non-null return as final, this pre-empts ACF's own 'option' → 'options' normalization and field-group lookup entirely for the singular 'option' identifier. 'options' (plural) isn't visibly affected only because the raw value being echoed back is already the correct one in that case — it's not that Elementor's filter behaves differently for it.
We were not able to inspect the exact file/line of filter_post_in_preview in your shipped source from our end (we don't have your plugin's source tree open in this environment) - the class and method name above come directly from a live dump of $wp_filter, which is authoritative for which callback is firing, but we'd ask your team to confirm the exact file/line against the installed version listed above.
Suggested Fix
filter_post_in_preview should only return a non-null override when it's actually resolving a dynamic tag against an active preview/edit context - e.g. gated on whatever your codebase uses to detect "currently rendering an Elementor dynamic tag" or "currently in preview mode" (such as a check against Plugin::$instance->preview or equivalent), rather than firing unconditionally for every acf/pre_load_post_id call site on every request. Concretely: if it isn't actually resolving an Elementor dynamic tag/preview, it should return $value/null unchanged instead of $post_id, so ACF's own normal resolution (including the 'option' → 'options' aliasing) can proceed untouched.
Our Workaround
Since we can't modify Elementor Pro directly, we added a filter of our own on acf/pre_load_post_id at priority 20 (i.e., running after Elementor's priority-10 callback), which discards Elementor's return value and restores null - but only for the two options-page identifiers, leaving Elementor's real preview behavior for actual posts completely untouched:
add_filter('acf/pre_load_post_id', function($value, $post_id) { if ($post_id === 'option' || $post_id === 'options') { return null; } return $value; }, 20, 2); This is a workaround, not a fix - it relies on knowing Elementor's exact priority and hook behavior, and any other plugin relying on the same filter for options-page reads without this specific workaround will hit the identical silent failure.
Steps to reproduce
Already explained
Expected behavior
Already explained
Elementor System Info
not neededAgreement
- I confirm I have read and followed all the guidelines and instructions outlined in the Elementor Bug Report form.
- 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.
Source: elementor/elementor