WAIT: Custom attribute dynamic tags don't resolve per-loop-item inside Query Loops

Browser: Chrome 150.0.7871.125
OS: macOS
WP: 7.0.2
PHP: 8.3.29
URL: localhost

Discovered while building w/Novamira Pro. Summary from Claude

Summary

When a dynamic tag (e.g. {post_terms_some_taxonomy}, {acf_some_field}, or any other provider’s tag) is used inside an element’s custom Attributes panel setting on an element living inside a Query Loop, the tag always resolves against the outer/template post — never against the current loop item. Every element in the loop ends up with the same (often blank) attribute value, regardless of which post is actually being rendered.

This is inconsistent with the same dynamic tags used in ordinary element content (headings, text, etc.), which correctly resolve per loop item.

Steps to reproduce

  1. Create a Query Loop of any post type with a taxonomy or custom field that varies per post.

  2. Inside the loop template, add a text element whose content uses a dynamic tag for that field (e.g. {post_terms_your_taxonomy:plain}) — confirm it renders correctly per post.

  3. On another element in the same loop item, open the Attributes panel and add a custom attribute (e.g. data-my-value) using the same dynamic tag.

  4. Preview/publish and inspect the rendered HTML across multiple loop items.

Expected: each loop item’s attribute reflects that post’s actual value, matching the text element from step 2.

Actual: the attribute renders the same value (usually empty) on every loop item, never tracking the loop.

Root cause

File: wp-content/themes/bricks/includes/elements/base.php, class Bricks\Element.

get_custom_attributes() (~line 2739) resolves its value without any loop-context check:

$attribute_value = isset( $field['value'] ) ? bricks_render_dynamic_data( $field['value'], $this->post_id ) : '';

bricks_render_dynamic_data() is a tag-agnostic wrapper around Providers::render_content() — no special-casing per provider. It simply resolves against whatever $post_id it’s given, and here that’s always the element’s own/template post, never the current loop item.

Compare with the correct pattern already used elsewhere in the same class, render_dynamic_data_tag() (~line 4249):

if ( ! $post_id ) {
    $post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id;
}

This correctly falls back to Query::get_loop_object_id() when inside a loop. get_custom_attributes() is missing this same check.

Scope

Not ACF-specific — bricks_render_dynamic_data()/Providers::render_content() is tag-source-agnostic, so this affects any dynamic tag (WP core, ACF, Metabox, Pods, WooCommerce, JetEngine, etc.) used in a custom attribute inside any Query Loop. Any site relying on dynamic tags in _attributes for loop-aware data attributes (JS hooks, conditional styling, tracking, etc.) is affected.

Suggested fix

$post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id;
$attribute_value = isset( $field['value'] ) ? bricks_render_dynamic_data( $field['value'], $post_id ) : '';

Current workaround

Deployed a narrow mu-plugin hooking bricks/dynamic_data/render_tag to override the post ID for our specific tag when Query::is_looping() is true. The real fix belongs in get_custom_attributes() so it works generally, for any tag.

Hi @eblos,

hmm, I’m not able to replicate this one. The same data shows in the attribute and in the element. Can you copy-paste your structure so I test it directly in yours? Maybe also add a screenshot or short video where you show the data that is failing?

Thank you,
Matej