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.

PHP utility Production-tested ~4 min setup Theme helper Intermediate WP 6.0+ · ACF 6.0+ Updated May 15, 2026
TL;DR

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.

01 — Building it step by step

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.

template-parts/sections/step-1-raw.php
php
// 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.

template-parts/sections/step-2-wrapper.php
php
// 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.

template-parts/sections/step-3-typed.php
php
// 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.

template-parts/sections/step-4-complex.php
php
// 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.

inc/helpers.php
php
// 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.

02 — How it works

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.

1

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.

2

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.

3

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.

4

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.

5

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.

03 — Setup & integration

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.

wp-content/themes/your-theme/
│   ├── inc/
│   │   └── helpers.php
│   ├── template-parts/
│   │   ├── footer.php
│   │   └── header.php
│   ├── acf-json/
│   │   └── group_theme_settings.json
    └── functions.php
1

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';
2

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.

3

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.

4

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.

04 — Making it your own

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.

1

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.

2

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.

3

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}
4

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. Calling theme_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() calls wp_get_attachment_image_src() which queries the wp_posts table 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.
0 comments

No comments yet. Be the first.

Leave a Reply

Table of Contents

ACF options page helper — Code Snippet

ACF options page helper — code snippet by Mosharaf Hossain

This ACF options page helper is a production-tested PHP snippet for WordPress developers. See the ACF Options Page documentation for related official documentation.

The ACF options page helper snippet registers a WordPress admin page that stores global theme settings as ACF fields — site-wide phone number, social media links, footer copyright text, global CTA button content — readable from any template or function with a single get_field call. It removes the need to hardcode global values in template files or build a custom settings page from scratch.

The problem with hardcoded global values in theme files is that a content editor cannot change them. Every update requires a developer to edit PHP or HTML files, deploy the change, and potentially clear the cache. WordPress’s built-in Customizer handles some global settings, but it is designed for visual preview, not structured data entry. An ACF options page provides a clean admin interface for editors with no Customizer limitations, no JSON-escaping, and no live preview overhead.

The snippet uses acf_add_options_page to register the top-level admin menu item and acf_add_options_sub_page for sub-pages — one for General Settings, one for Social Profiles, one for Global CTAs. Field groups are then assigned to the options page slug in ACF admin so the right fields appear on each sub-page. Sub-page separation is optional but recommended for themes with more than five global fields.

Reading the field values uses the “option” second argument: get_field( “site_phone”, “option” ). This works in any template file, any function in functions.php, and any shortcode or widget — because the options page stores values in a single WordPress option record, not against a post ID. The values are also available via get_sub_field when you add a repeater or flexible content field to the options page.

The acf_add_options_page call must be wrapped in a function_exists check so the theme does not throw a fatal error if ACF is deactivated. Place the registration call in functions.php inside the acf/init action hook or on init with priority 5, which ensures ACF is fully loaded before registration runs.

Requires ACF Pro 5.0+ — options pages are a Pro feature. Tested on WordPress 6.0+ with ACF 6.0+ and PHP 8.0+. Browse all ACF snippets at Code Snippets or see the ACF Image Field Renderer snippet. Global options power the header and footer of the Vitamines project.

Related: ACF Flexible Content Renderer — and browse the full code snippet library.

Chat on WhatsApp