Skip to main content

Reading Field Values in Templates

Reliable template output starts with deterministic value retrieval and context-correct escaping, not direct field echoing.

Learning Focus

You will implement robust retrieval patterns for scalar, structured, and relational ACF values, choose correct escaping per output context, and validate real runtime payloads with WP-CLI before release.

Concept Overview

ACF retrieval in templates is a data-contract problem, not a convenience problem. get_field() returns values for logic, while direct output helpers can hide validation and escaping decisions. In production-grade templates, you should retrieve first, validate type and emptiness, then render with context-specific escaping.

Different fields return different shapes: simple scalar strings, arrays (Group/Image), object arrays (Relationship), row sets (Repeater/Flexible), and option-scoped globals. If rendering code assumes the wrong shape, failures often appear as empty output, warnings, or malformed markup.

The safest pattern is explicit normalization. Convert uncertain payloads into predictable internal variables before output. That enables easy fallbacks, consistent escaping, and testable component behavior. In code-first teams, you should also prove payload shape via WP-CLI commands, not visual guessing in admin screens.

Core Idea

Read values with get_field(), normalize shape, escape by output context, and render only when contract checks pass.

Why It Matters

ApproachWhat HappensImpact in Production
Retrieve to variable then escape on renderValidation and output concerns stay explicitLower XSS risk and fewer rendering regressions
Echo field values directly in markupHard to test shape/emptiness and escape correctnessInconsistent output and hidden security debt
Normalize arrays/IDs/objects before renderingReturn-format drift becomes manageableFewer runtime notices and faster incident resolution
Use context-aware fallback rulesComponents remain usable when optional fields are emptyBetter UX continuity during editorial drafting
Assume payload shape without checks (wrong pattern)Array/object/int mismatches break templates at runtimeProduction defects that are hard to reproduce

Reference Table

Term/APISignature/SyntaxPurposeKey Notes
get_field()`get_field(string $selector, intstring $post_id = false, bool $format_value = true): mixed`Retrieve value for logic and rendering
the_field()`the_field(string $selector, intstring $post_id = false): void`Directly echo a field value
esc_html()esc_html(string $text): stringEscape plain text in HTML bodyUse for headings, labels, and inline text
esc_attr()esc_attr(string $text): stringEscape attribute valuesUse for tel:, id, data-*, and class fragments
esc_url()esc_url(string $url): stringEscape URL output for links/srcRequired for href and image URLs
wp_kses_post()wp_kses_post(string $content): stringAllow safe subset of HTMLUse for trusted rich-text field output
Option context readget_field('field_name', 'option')Retrieve global option-scoped valueACF Pro Required when Options Pages are used
have_rows()`have_rows(string $selector, intstring $post_id = false): bool`Iterate Repeater/Flexible rows
wp evalwp eval '<php-code>'Inspect payload type and value in runtimeCritical for debugging return-format assumptions

Practical Use Cases

Use Case 1 — Harden hero component output with shape and context guards

A marketing hero component depends on four fields: title, subtitle, CTA label, and CTA URL. Editors sometimes leave one field blank during drafts. You need safe fallback behavior and strict output escaping.

  1. Retrieve all hero values with get_field().
  2. Normalize each to string and apply fallback only where allowed.
  3. Render CTA only when both label and URL are valid.
  4. Escape text, URLs, and attributes per context.
  5. Verify value retrieval and fallback behavior via CLI.
wp-content/themes/clinic-pro/partials/hero.php
<?php

declare(strict_types=1);

add_action('template_redirect', function (): void {
if (!is_page()) {
return;
}

$title = (string) get_field('hero_title');
$subtitle = (string) get_field('hero_subtitle');
$ctaLabel = (string) get_field('hero_cta_label');
$ctaUrl = (string) get_field('hero_cta_url');

if ($title === '') {
$title = 'Talk to our specialists today';
}

echo '<section class="hero">';
echo '<h1>' . esc_html($title) . '</h1>';

if ($subtitle !== '') {
echo '<p class="hero-subtitle">' . esc_html($subtitle) . '</p>';
}

if ($ctaLabel !== '' && $ctaUrl !== '') {
echo '<a class="hero-cta" href="' . esc_url($ctaUrl) . '">' . esc_html($ctaLabel) . '</a>';
}

echo '</section>';
});

add_action('wp', function (): void {
if (!is_page()) {
return;
}

$title = (string) get_field('hero_title');
$ctaUrl = (string) get_field('hero_cta_url');
error_log('[hero-read] page=' . get_the_ID() . ' title_len=' . strlen($title) . ' cta_len=' . strlen($ctaUrl));
});
terminal: command
wp post create --post_title="Template Read Drill" --post_status=publish --post_type=page
wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; update_field("hero_title","",$id); update_field("hero_subtitle","Launch in days, not months.",$id); update_field("hero_cta_label","Book Demo",$id); update_field("hero_cta_url","/book-demo",$id); echo (get_field("hero_title",$id) ?: "fallback") . "|" . get_field("hero_cta_url",$id) . PHP_EOL;'
terminal: output
Success: Created post 1360.
fallback|/book-demo
note

Fallbacks should be policy-driven. Apply them to strategic fields (like hero title), not globally to every value.

Use Case 2 — Render option-level defaults plus post-level overrides safely

A service page should use page-specific support phone if present, otherwise global support phone from options. You need clear retrieval precedence and safe tel-link rendering.

  1. Read page-level value first.
  2. Read option-level fallback with 'option' context.
  3. Select final value with explicit precedence.
  4. Render tel-link only when final value is non-empty.
  5. Verify both paths (override and fallback) through CLI.
wp-content/themes/clinic-pro/partials/contact-cta.php
<?php

declare(strict_types=1);

add_action('acf/init', function (): void {
if (!function_exists('acf_add_options_page')) {
return;
}

acf_add_options_page([
'page_title' => 'Clinic Global Contact',
'menu_title' => 'Global Contact',
'menu_slug' => 'clinic-global-contact',
'capability' => 'manage_options',
'redirect' => false,
]);
});

add_action('template_redirect', function (): void {
if (!is_page()) {
return;
}

$pagePhone = (string) get_field('page_contact_phone');
$globalPhone = (string) get_field('global_support_phone', 'option');

$finalPhone = $pagePhone !== '' ? $pagePhone : $globalPhone;
if ($finalPhone === '') {
return;
}

$telHref = preg_replace('/[^0-9+]/', '', $finalPhone);
echo '<a class="contact-phone" href="tel:' . esc_attr((string) $telHref) . '">' . esc_html($finalPhone) . '</a>';
});

add_action('wp', function (): void {
if (!is_page()) {
return;
}

$pagePhone = (string) get_field('page_contact_phone');
$globalPhone = (string) get_field('global_support_phone', 'option');
$source = $pagePhone !== '' ? 'page' : ($globalPhone !== '' ? 'option' : 'none');
error_log('[contact-phone] source=' . $source . ' page=' . get_the_ID());
});
terminal: command
wp eval 'update_field("global_support_phone", "+65 6700 1234", "option"); echo get_field("global_support_phone", "option") . PHP_EOL;'
wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; update_field("page_contact_phone", "", $id); $page=(string)get_field("page_contact_phone",$id); $global=(string)get_field("global_support_phone","option"); echo ($page !== "" ? $page : $global) . PHP_EOL;'
terminal: output
+65 6700 1234
+65 6700 1234
ACF Pro Required

Options page retrieval (get_field(..., 'option')) in this workflow assumes ACF Pro options-page usage.

Use Case 3 — Edge case: image return-format mismatch (array vs ID)

A template assumes image field returns an array (url, alt) but field settings were changed to return attachment ID. The component starts throwing notices and broken <img> tags.

❌ Fragile Pattern

wp-content/themes/clinic-pro/partials/hero-image-fragile.php
<?php

declare(strict_types=1);

add_action('template_redirect', function (): void {
if (!is_page()) {
return;
}

$image = get_field('hero_image');
echo '<img src="' . esc_url($image['url']) . '" alt="' . esc_attr($image['alt']) . '">';
});

✅ Robust Pattern

wp-content/themes/clinic-pro/partials/hero-image-robust.php
<?php

declare(strict_types=1);

add_action('template_redirect', function (): void {
if (!is_page()) {
return;
}

$image = get_field('hero_image');
$url = '';
$alt = '';

if (is_array($image)) {
$url = (string) ($image['url'] ?? '');
$alt = (string) ($image['alt'] ?? '');
} elseif (is_numeric($image)) {
$url = (string) wp_get_attachment_image_url((int) $image, 'large');
$alt = (string) get_post_meta((int) $image, '_wp_attachment_image_alt', true);
}

if ($url === '') {
return;
}

echo '<img class="hero-image" src="' . esc_url($url) . '" alt="' . esc_attr($alt) . '">';
});

add_action('wp', function (): void {
if (!is_page()) {
return;
}

$image = get_field('hero_image');
error_log('[hero-image-shape] page=' . get_the_ID() . ' type=' . gettype($image));
});
terminal: command
wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; $image=get_field("hero_image",$id); echo gettype($image) . PHP_EOL;'
wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; $image=get_field("hero_image",$id); if(is_array($image)){echo "array" . PHP_EOL;} elseif(is_numeric($image)){echo "id" . PHP_EOL;} else {echo "none" . PHP_EOL;}'
terminal: output
integer
id
warning

Return format can change during schema edits. Normalize payloads before rendering to make template behavior resilient.

Common Mistakes

MistakeRoot CauseWhat Breaks in ProductionCorrect Pattern
Using the_field() in complex templatesOutput and validation coupledHard-to-debug malformed outputUse get_field() + explicit render logic
Escaping with wrong functionURL escaped as text or vice versaBroken links/attributes or over-escaped outputMatch esc_html / esc_attr / esc_url to context
Assuming fixed return formatField setting changed without template updateArray/ID/object mismatch noticesNormalize by is_array, is_numeric, instanceof guards
Rendering wrappers before emptiness checksEmpty blocks clutter layoutVisual artifacts and QA noiseGuard section-level output with contract checks
No option-vs-page precedence strategyInconsistent fallback behaviorDifferent pages render conflicting contact dataDefine explicit precedence and log source used
Skipping CLI payload checksDrift discovered only in browserSlower debugging and incident responseAdd wp eval payload-type checks to release process
Deep Dive: Why Escaping Bugs Often Survive Code Review

Escaping bugs are subtle because the rendered page may look fine in normal content states. Problems usually surface with unusual values: special characters in titles, malformed URLs, or rich text in plain contexts. Reviewers often focus on component structure and miss context mismatches (esc_html used for URLs, no esc_attr in attributes). A practical fix is to include context-focused CLI checks and value-shape assertions during QA. Treat escaping as part of the component contract, not an afterthought.

wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; echo "title=" . gettype(get_field("hero_title",$id)) . PHP_EOL; echo "cta_url=" . gettype(get_field("hero_cta_url",$id)) . PHP_EOL;'

Best Practices

  1. Retrieve first, render second: never mix retrieval and unescaped output in one expression.
  2. Use one normalization step per field group component: shape-check arrays/IDs/objects early.
  3. Escape at the output edge: esc_html for text, esc_url for links, esc_attr for attributes.
  4. Define fallback policy per field, not globally: critical text may fallback; optional sections may hide.
  5. Log source selection for option/page precedence in critical components.
  6. Audit payload types with wp eval before releases involving schema changes.
  7. Prefer explicit variables over nested function calls in template markup for readability and safety.

Hands-On Practice

Exercise 1: Refactor one component to retrieval-first pattern

Refactor a template partial to use get_field() variables and run:

wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; echo (string)get_field("hero_title",$id) . PHP_EOL;'

After completing this exercise, output should be a plain string (or blank if intentionally empty).

Exercise 2: Implement CTA fallback policy

Run:

wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; update_field("hero_title","",$id); update_field("hero_cta_label","Book Demo",$id); update_field("hero_cta_url","/book-demo",$id); echo (get_field("hero_title",$id) ?: "Talk to our specialists today") . PHP_EOL;'

After completing this exercise, output should be:

Talk to our specialists today

Exercise 3: Test option-level fallback behavior

Run:

wp eval 'update_field("global_support_phone", "+65 6700 1234", "option");'
wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; update_field("page_contact_phone","",$id); $page=(string)get_field("page_contact_phone",$id); $global=(string)get_field("global_support_phone","option"); echo ($page!==""?$page:$global) . PHP_EOL;'

After completing this exercise, output should be:

+65 6700 1234

Exercise 4: Audit image return shape

Run:

wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; $image=get_field("hero_image",$id); echo gettype($image) . PHP_EOL;'

After completing this exercise, output should be one of:

array

or

integer

Exercise 5: Build a release check for field shape assumptions

Run:

wp eval '$id=(int)get_page_by_title("Template Read Drill", OBJECT, "page")->ID; echo "hero_title=" . gettype(get_field("hero_title",$id)) . PHP_EOL; echo "hero_image=" . gettype(get_field("hero_image",$id)) . PHP_EOL; echo "related_resources=" . gettype(get_field("related_resources",$id)) . PHP_EOL;'

After completing this exercise, output pattern should be:

hero_title=string
hero_image=integer
related_resources=array

CLI Reference

CommandPurposeReal Example Output
wp eval '$id=1360; echo (string)get_field("hero_title",$id) . PHP_EOL;'Read scalar field valueWelcome to Clinic Pro
wp eval '$id=1360; echo (string)get_field("hero_cta_url",$id) . PHP_EOL;'Read URL contract field/book-demo
wp eval '$id=1360; $v=get_field("hero_image",$id); echo gettype($v) . PHP_EOL;'Inspect image return shapeinteger
wp eval 'echo get_field("global_support_phone", "option") . PHP_EOL;'Read option-context fallback value+65 6700 1234
wp post meta get 1360 hero_titleInspect raw stored hero titleTalk to our specialists today
wp eval '$id=1360; $r=get_field("related_resources",$id); echo gettype($r) . PHP_EOL;'Inspect relational payload top-level typearray
wp eval '$id=1360; $rows=get_field("service_faq_items",$id); echo count((array)$rows) . PHP_EOL;'Count structural rows for template branch checks2
`wp plugin list --status=activegrep advanced-custom-fields-pro`Confirm ACF Pro dependency

What's Next

tip

Revisit this lesson whenever schema settings change (return formats, option contexts, field types), because those changes often require template retrieval updates.