ACF Options Page Helper — Global Theme Settings PHP
A thin wrapper around ACF options page fields — type-safe helpers with default fallbacks, proper escaping, and one function call per field. No scattering get_field(..., 'option') across your theme.
This ACF options page helper PHP snippet wraps ACF options page fields with default fallbacks, proper escaping, and one clean function call per global theme setting.
Use this when your theme has an ACF Options page for global settings — logo, social links, footer text, analytics scripts — and you want a single, predictable way to access every field with safe defaults.
ACF options page helper — The Code
This ACF options page helper helper registers your options page and gives you a clean wrapper function for retrieving global settings.
We’ll build this in 5 small steps. Each step adds one new idea — starting from raw ACF calls scattered across your theme, up to a complete type-safe options helper that you or any developer can use without reading the implementation.
Step 1: The problem. This is what most themes look like — get_field(..., 'option') repeated across every template file. No fallback values. No type safety. If the admin hasn’t filled in a field, the template shows nothing or breaks. After six months you have 40+ scattered calls and no single source of truth.
// ACF options page helper — production-tested PHP snippet by Mosharaf Hossain
<?php
// BAD: get_field(..., 'option') repeated across every template.
// No fallback. No type safety. If the field is empty, the page breaks silently.
$logo = get_field( 'site_logo', 'option' ); // ACF field type: Image
$footer_text = get_field( 'footer_text', 'option' ); // ACF field type: Text
$social_fb = get_field( 'facebook_url', 'option' ); // ACF field type: URL
$analytics = get_field( 'analytics_code', 'option' ); // ACF field type: Textarea
// In three months you will have 40 get_field calls across 15 files
// and no way to know which ones have fallbacks and which ones don't.
Step 2: A simple wrapper. theme_option() wraps get_field(..., 'option') with a default value. The first parameter is the field name and the second is what to return if the field is empty. Now every options field has a fallback — your theme works even before the admin touches the settings page.
// ACF options page helper
<?php
/**
* Step 2 — One function, one fallback pattern.
*
* New in this step:
* theme_option() wraps get_field(..., 'option') with a default value.
* If the admin hasn't set the field, your theme still has something to show.
* You'll find this helper at the end of this section.
*/
// ACF field type: Text — footer copyright line.
$footer = theme_option( 'footer_text', '© ' . date( 'Y' ) . ' All rights reserved.' );
// Returns the ACF value if set, otherwise the default string.
echo esc_html( $footer );
// ── The helper ──────────────────────────────────
function theme_option( string $field, mixed $default = '' ): mixed {
$value = get_field( $field, 'option' );
return $value !== null && $value !== '' ? $value : $default;
}
Step 3: Type-specific helpers. The simple wrapper doesn’t know what kind of field you’re reading. A text field needs esc_html(), a URL needs esc_url(), and a WYSIWYG editor needs wp_kses_post(). Instead of remembering the right escaping everywhere, use named helpers: theme_option_text(), theme_option_url(), theme_option_wysiwyg(). Each wraps the right escaping function for its type.
// ACF options page helper
<?php
/**
* Step 3 — One helper per type, with the right escaping built in.
*
* New in this step:
* theme_option_text() → returns string, pre-escaped with esc_html()
* theme_option_url() → returns string, pre-escaped with esc_url()
* theme_option_wysiwyg() → returns string, safe for wp_kses_post()
*
* You never think about the right escaping — the helper handles it.
* You'll find the full helpers at the end of this section.
*/
// ACF field type: Text.
$phone = theme_option_text( 'contact_phone', 'Call us' );
// ACF field type: URL — returns a raw URL field from ACF.
$facebook = theme_option_url( 'facebook_url' );
// ACF field type: WYSIWYG — returns rich text, safe for output.
$footer_rich = theme_option_wysiwyg( 'footer_content', '<p>Footer default.</p>' );
if ( $phone ) : ?>
<a href="tel:<?php echo esc_attr( str_replace( ' ', '', $phone ) ); ?>">
<?php echo $phone; // Already escaped by theme_option_text(). ?>
</a>
<?php endif;
if ( $facebook ) : ?>
<a href="<?php echo $facebook; ?>" target="_blank" rel="noopener">Facebook</a>
<?php endif;
if ( $footer_rich ) : ?>
<div class="footer-content"><?php echo $footer_rich; ?></div>
<?php endif;
Step 4: Images and repeaters. Complex field types need more than a string. theme_option_image() accepts an ACF Image field (returning an attachment ID) and returns a clean array with url, alt, width, and height — or null if no image is set. theme_option_repeater() returns the rows array, or an empty array so foreach never breaks. Both follow the same pattern as the simpler helpers.
// ACF options page helper
<?php
/**
* Step 4 — Complex field types with complete return structures.
*
* New in this step:
* theme_option_image() → returns [url, alt, width, height] or null.
* theme_option_repeater() → returns the repeater rows, or empty array.
*
* These wrap ACF's native return types so template code doesn't need to
* know that image fields return IDs and repeater fields return arrays.
* You'll find the helpers at the end of this section.
*/
// ACF field type: Image — returns Image ID.
$logo = theme_option_image( 'site_logo' );
// ACF field type: Repeater
$socials = theme_option_repeater( 'social_links' );
if ( $logo ) : ?>
<img src="<?php echo esc_url( $logo['url'] ); ?>"
alt="<?php echo esc_attr( $logo['alt'] ); ?>"
width="<?php echo esc_attr( $logo['width'] ); ?>"
height="<?php echo esc_attr( $logo['height'] ); ?>" />
<?php endif;
if ( $socials ) :
foreach ( $socials as $s ) : ?>
<a href="<?php echo esc_url( $s['platform_url'] ?? '' ); ?>">
<?php echo esc_html( $s['platform_name'] ?? '' ); ?>
</a>
<?php endforeach;
endif;
Step 5: The full production file. All seven helpers in one file — inc/helpers.php. Each helper is a one-liner that delegates to theme_option(). The naming is consistent (theme_option_{type}). The escaping is handled. Template code never touches get_field(..., 'option') directly again.
// ACF options page helper
<?php
/**
* ACF Options Page Helper — type-safe access with defaults and escaping.
*
* Place in inc/helpers.php and require from functions.php.
* All helpers share one pattern: read from 'option', return a default
* if the field is empty, escape based on the expected type.
*/
function theme_option( string $field, mixed $default = '' ): mixed {
$value = get_field( $field, 'option' );
return ( $value !== null && $value !== '' ) ? $value : $default;
}
function theme_option_text( string $field, string $default = '' ): string {
$value = theme_option( $field, $default );
return is_string( $value ) ? esc_html( $value ) : $default;
}
function theme_option_url( string $field, string $default = '' ): string {
$value = theme_option( $field, $default );
return is_string( $value ) ? esc_url( $value ) : $default;
}
function theme_option_wysiwyg( string $field, string $default = '' ): string {
$value = theme_option( $field, $default );
return is_string( $value ) ? wp_kses_post( $value ) : $default;
}
function theme_option_textarea( string $field, string $default = '' ): string {
$value = theme_option( $field, $default );
return is_string( $value ) ? esc_textarea( $value ) : $default;
}
function theme_option_image( string $field ): ?array {
$image_id = theme_option( $field, 0 );
if ( ! $image_id || ! is_numeric( $image_id ) ) return null;
$img = wp_get_attachment_image_src( (int) $image_id, 'full' );
if ( ! $img ) return null;
$alt = get_post_meta( (int) $image_id, '_wp_attachment_image_alt', true );
return [
'url' => $img[0],
'width' => $img[1],
'height' => $img[2],
'alt' => $alt ?: '',
'id' => (int) $image_id,
];
}
function theme_option_repeater( string $field ): array {
$rows = theme_option( $field, [] );
return is_array( $rows ) ? $rows : [];
}
// ── Usage ────────────────────────────────────────
// $logo = theme_option_image( 'site_logo' );
// $phone = theme_option_text( 'contact_phone', 'N/A' );
// $facebook = theme_option_url( 'facebook_url' );
// $footer = theme_option_wysiwyg( 'footer_about' );
// $socials = theme_option_repeater( 'social_links' );
Where to put the code
Save Step 5 in inc/helpers.php and require it from functions.php:
require_once get_template_directory() . '/inc/helpers.php';
Then replace every get_field(..., 'option') call with the matching helper. The field name stays the same — you’re only changing the function you call.
How ACF options page helper Works — Why Each Layer Exists
Here is how this ACF options page helper works step by step: Five layers, each solving a real friction point.
Five layers, each solving a real friction point from the previous step.
Raw get_field — discoverability and safety gaps
Raw get_field( 'field_name', 'option' ) works. The problem is not correctness — it’s discoverability and safety. When a developer sees get_field( 'footer_text', 'option' ), they have three questions: “What type is this field?”, “What happens if it’s empty?”, and “Should I escape this before output?” None of those questions are answered by the code.
theme_option() — fallback as documentation
theme_option() answers the fallback question. The second parameter provides a default that the theme uses when the admin hasn’t populated the field. This is important for two reasons. First, the theme works immediately after installation — no blank areas because settings haven’t been configured. Second, the default value acts as documentation — any developer reading the call sees what the fallback is without tracing the ACF field definition.
Type-specific helpers — escaping by convention
Type-specific helpers solve a correctness problem. A text field value is a plain string and needs esc_html(). A URL field needs esc_url(), which validates the protocol and strips dangerous characters. A WYSIWYG field contains user-authored HTML and needs wp_kses_post(), which allows only safe tags. If you mix these up — outputting a URL through esc_html() — the link breaks. If you output WYSIWYG content through esc_html(), the formatting disappears.
By tying the escaping to the helper name, the decision is made once in the implementation instead of remembered at every call site. theme_option_url() always applies esc_url(). The developer using it doesn’t need to know which escaping function to use — the name tells them.
Image and repeater helpers — complex return shapes
theme_option_image() handles the full retrieval chain. ACF Image fields can return an attachment ID, a URL, or an array depending on the Return Format setting. The helper assumes Image ID — the most reliable format — and calls wp_get_attachment_image_src() to get the URL and dimensions. It retrieves the alt text from WordPress core’s attachment meta. The return value is a consistent array shape or null — no variation between call sites.
theme_option_repeater() is simpler but crucial. ACF returns null for an empty repeater field. Passing null to foreach triggers a PHP warning in PHP 8.1+. The helper returns an empty array instead, so foreach always works.
Production file — naming as documentation
The production file combines all seven helpers with consistent naming: theme_option_{type}. Each one calls theme_option() and applies its specific transformation — escaping, image resolution, or empty-array safety. Template code reads from left to right: “I need the phone number as text” → theme_option_text( 'phone' ). No lookup table. No remembering. The function name is the documentation.
ACF options page helper — Setup & Integration
To integrate this ACF options page helper into your project: The helper is one PHP file. Wire it into your theme, create an ACF Options page, and replace your raw field calls.
The helper is a single PHP file. Wire it into your theme and replace your raw ACF calls.
Create inc/helpers.php and require it
Create inc/helpers.php with the Step 5 code. In functions.php, add the require statement at the top of the file — before any template includes — so the helpers are available everywhere:
require_once get_template_directory() . '/inc/helpers.php';
Create the ACF Options Page
Create an ACF Options Page. In functions.php, add:
if ( function_exists( 'acf_add_options_page' ) ) {n acf_add_options_page( [n 'page_title' => 'Theme Settings',n 'menu_title' => 'Theme Settings',n 'menu_slug' => 'theme-settings',n ] );n}
Then create a field group assigned to Options Page → Theme Settings. Add fields for logo, social URLs, contact info, footer text, analytics scripts, or any global content your theme needs.
Replace get_field() calls with helpers
Replace your raw get_field(..., 'option') calls. In header.php, change:
// Beforen$logo = get_field( 'site_logo', 'option' );nn// Aftern$logo = theme_option_image( 'site_logo' );
In footer.php, change:
// Beforen$text = get_field( 'footer_text', 'option' );nif ( $text ) echo esc_html( $text );nn// Afternecho theme_option_text( 'footer_text', '© 2026' );
The field names stay the same. You’re only changing which function you call.
Migrate incrementally — no downtime
If your existing theme already has get_field(..., 'option') calls in many files, find and replace them incrementally. Start with the fields you touch most often — logo, footer text, social links. Leave the rest for when you edit those templates. There is no migration cost — the old get_field() calls continue to work alongside the new helpers.
Making This ACF options page helper Your Own
Customise this ACF options page helper to match your specific needs: The helpers are a pattern, not a closed set. Add your own for custom field types, multiple options pages, or debugging.
Add a helper for a custom field type
Add a helper for your most-used complex field. If your theme has a Color Picker field for a brand colour, add:
function theme_option_color( string $field, string $default = '#c9a96e' ): string {n $color = theme_option( $field, $default );n return is_string( $color ) ? sanitize_hex_color( $color ) : $default;n}
Every new helper follows the same skeleton: call theme_option(), apply type-specific sanitization, return the result.
Support multiple options pages
If your theme has multiple options pages (e.g., Theme Settings and Header Settings), add a second wrapper that hardcodes the options page slug:
function header_option_text( string $field, string $default = '' ): string {n $value = get_field( $field, 'header_settings' );n $value = ( $value !== null && $value !== '' ) ? $value : $default;n return is_string( $value ) ? esc_html( $value ) : $default;n}
This keeps the pattern but targets a specific options page without passing 'option' every time.
Add a boolean helper for True/False fields
Add a theme_option_boolean() helper for True/False fields. ACF returns 1, 0, or null for boolean fields:
function theme_option_bool( string $field, bool $default = false ): bool {n $value = theme_option( $field, $default );n return (bool) $value;n}nn// Usagenif ( theme_option_bool( 'show_cookie_banner', true ) ) {n get_template_part( 'template-parts/cookie-banner' );n}
Add a debug helper for development
Add a debug helper for development that prints all registered options values:
function theme_option_debug(): void {n if ( ! current_user_can( 'administrator' ) ) return;n $fields = get_field_objects( 'option' );n if ( ! $fields ) return;n echo '<!-- Theme Options: -->';n foreach ( $fields as $name => $field ) {n echo '<!-- ' . esc_html( $name ) . ': ' . esc_html( var_export( $field['value'], true ) ) . ' -->';n }n}
Call it in your footer during development to see exactly which options are set without opening the admin panel.
3 things that will silently break:
1. ACF field Return Format mismatch. theme_option_image() expects the Image field to return an Image ID. If your field is set to return an Image URL, wp_get_attachment_image_src() receives a URL string instead of an integer and returns false. Fix: in ACF, set Return Format → Image ID.
2. Calling a text helper on a WYSIWYG field. theme_option_text() calls esc_html(), which strips all HTML tags including formatting. WYSIWYG content will render as plain text with tags visible. Use theme_option_wysiwyg() for any field that stores HTML.
3. Forgetting to require the helpers file. If you move inc/helpers.php or rename it, all helper calls will throw fatal errors. The require statement in functions.php must match the actual file path.
Performance notes:
- ACF caches internally: Every
get_field(..., 'option')call reads from ACF’s in-memory cache after the first access for that request. Callingtheme_option_text( 'phone' )twice on the same page doesn’t make two database queries — it makes one on the first call and reads from cache on the second. - Options page fields load once: ACF loads all options-page field values in a single query on the first
get_field(..., 'option')call, regardless of which field you request. Calling 10 different helpers on one page costs the same as calling one. - Image helpers do one extra query:
theme_option_image()callswp_get_attachment_image_src()which queries thewp_poststable for attachment metadata. This is a single indexed lookup — fast, but not zero-cost. Cache the result in a local variable if you use the same image multiple times in one template.

No comments yet. Be the first.