ACF + Bricks is one of the strongest combinations for content-driven WordPress sites. Field groups give editors structured control; Bricks dynamic tags render those fields without hand-writing PHP on every template. The trouble starts when field groups grow without naming rules, return formats, and query conventions. Then every new page becomes a scavenger hunt through meta keys nobody remembers.
These patterns keep dynamic data readable for the next developer — including future you — while still giving editors the flexibility they need for service pages, case studies, and resource hubs.
Name fields for humans and queries
Prefer predictable prefixes: hero_, card_, seo_, case_, faq_. When every related service field is called something different (related, services_link, more_services), templates become guesswork. Prefixes also make ACF location rules and Bricks dynamic tag pickers easier to scan.
Use singular names for single values and plural names for repeaters or relationships. hero_title is a string; faq_items is a repeater. That tiny convention prevents half of the why-is-this-empty support threads.
// Relationship: related services on a case study
$related = get_field('case_related_services');
if ($related) {
foreach ($related as $service_id) {
printf(
'<a href="%s">%s</a>',
esc_url(get_permalink($service_id)),
esc_html(get_the_title($service_id))
);
}
}
Choose return formats on purpose
ACF lets relationship and image fields return objects, arrays, or IDs. Defaulting to full object feels convenient in the field settings UI and expensive in loops. For Bricks templates that mostly need a title, permalink, and featured image, Post Object (ID) or Image ID is usually enough. Resolve richer data only when the template truly needs it.
Images
Store image IDs. Let Bricks or wp_get_attachment_image() build the srcset. Returning an image array duplicates URLs in postmeta payloads and still leaves you formatting markup by hand.
Relationships
ID return format keeps queries light. When a Bricks query loop needs titles, use dynamic tags against the loop object instead of nesting get_field() calls that hydrate entire posts repeatedly.
// Prefer IDs in field settings, then resolve in context
$ids = get_field('case_related_services') ?: [];
$query = new WP_Query([
'post_type' => 'service',
'post__in' => array_map('intval', $ids),
'orderby' => 'post__in',
'posts_per_page' => count($ids),
]);
Repeater to nestable loop
For FAQ items, pricing tiers, logo rows, and process steps, a repeater mapped to a Bricks query loop beats hand-duplicated blocks. Editors can add a fifth FAQ without asking a developer to clone markup. Keep the repeater shallow — one level of sub fields — because nested repeaters are where edits go to die.
{
"field": "faq_items",
"sub_fields": ["question", "answer", "icon"]
}
In Bricks, bind the loop to the ACF repeater, then map nestable children to sub fields. Expose only the fields editors should change. If a visual treatment is constant (card border, icon size), keep it in CSS classes, not in ACF.
Field groups by template, not by everything
Resist one mega field group attached to all pages. Split by template intent: Service Hero, Service Proof, Case Study Metrics, Blog SEO. Smaller groups load faster in the editor and reduce the chance an editor fills the wrong subtitle field because five of them appeared on screen.
- Service pages: hero, problem, proof, pricing notes, FAQ
- Case studies: client label, metrics, stack, outcomes, related services
- Blog posts: optional takeaways and related resources — do not reinvent the excerpt
- Global options: social links, default CTA, announcement bar
Guardrails for editors
Use instructions and character limits on fields that feed tight layouts. A hero eyebrow that accepts 200 characters will break your mobile composition. Mark required fields only when the template truly cannot render without them; over-required forms create fake content just to save.
Where possible, provide default values that match your design system placeholders. Editors should replace labeled placeholders, not invent structure.
Debugging empty dynamic tags
When a Bricks tag renders blank, check three things in order: the field key or name, the post context (are you on the loop item or the archive?), and the return format. Add a temporary Text element that dumps the raw field in a staging-only class if needed, then remove it before launch.
// Staging-only sanity check
if (current_user_can('manage_options') && isset($_GET['debug_acf'])) {
echo '<pre class="acf-debug">';
var_export(get_fields());
echo '</pre>';
}
Scaling without spaghetti
As the site grows, document field ownership next to the template map. A short markdown table — template, field group, key fields — saves hours during handoffs. Pair that with consistent BEM classes in Bricks so CSS chunks stay load-where-used.
ACF should describe content. Bricks should describe layout. CSS should describe visual rules. When those layers stay separate, dynamic data scales. When they collapse into one overloaded field group and a Code element, every future edit becomes archaeology.




2 comments
Priya S.
The repeater → nestable loop advice should be on every ACF project kickoff doc.
Chris L.
We switched relationships to ID return format after reading this — templates got snappier.