• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to build custom ACF Pro dynamic fields extensions utilizing modern WordPress Options API schemas

How to build custom ACF Pro dynamic fields extensions utilizing modern WordPress Options API schemas

Leveraging the Options API for Advanced ACF Pro Dynamic Field Extensions

Advanced Custom Fields (ACF) Pro offers a powerful mechanism for creating dynamic field values. While the built-in options are extensive, complex application requirements often necessitate custom solutions. This guide details how to extend ACF Pro’s dynamic field capabilities by deeply integrating with the WordPress Options API, enabling sophisticated data retrieval and manipulation for field choices, values, and more.

Understanding ACF Dynamic Field Hooks

ACF Pro exposes several filter hooks that allow developers to modify the behavior of dynamic fields. The most relevant for extending Options API integration are:

  • acf/load_field: This filter allows modification of field settings before they are loaded. It’s ideal for dynamically populating choices or setting default values based on external data.
  • acf/update_value: This filter allows modification of the value before it’s saved to the database. Useful for sanitizing or transforming data.
  • acf/format_value: This filter allows modification of the value when it’s displayed (e.g., in the backend or frontend).

For dynamic field *choices*, the acf/load_field hook is paramount. We’ll focus on this hook to demonstrate how to pull data from the WordPress Options API and map it to ACF field choices.

Structuring Options API Data for Dynamic Fields

The WordPress Options API stores data as serialized arrays or single values under specific option names in the `wp_options` database table. To effectively use this data for ACF dynamic fields, a structured approach is crucial. We’ll assume a common pattern where an option stores an array of key-value pairs suitable for ACF’s ‘Select’, ‘Radio Button’, or ‘Checkbox’ field types.

Implementing a Custom ACF Field Loader

Let’s create a PHP function that hooks into acf/load_field. This function will check if the current field being loaded is one we want to dynamically populate. If so, it will retrieve data from a specific WordPress option and format it for ACF.

Example: Populating a Select Field from a Custom Option

Suppose we have a custom option named my_plugin_custom_settings which stores an array like this:

$settings = array(
    'option_1' => 'Label for Option 1',
    'option_2' => 'Another Choice',
    'option_3' => 'Third Item',
);
update_option( 'my_plugin_custom_settings', $settings );

Now, we’ll write the PHP code to load this into an ACF Select field. We’ll assume the ACF field has a ‘name’ property set to my_dynamic_select_field.

/**
 * Load dynamic choices for ACF fields from WordPress options.
 *
 * @param array $field The ACF field array.
 * @return array The modified ACF field array.
 */
function my_acf_load_dynamic_options_field( $field ) {

    // Check if this is the field we want to modify.
    if ( 'my_dynamic_select_field' === $field['name'] ) {

        // Retrieve the custom settings from the WordPress Options API.
        $custom_options = get_option( 'my_plugin_custom_settings' );

        // Ensure we have valid options data.
        if ( is_array( $custom_options ) && ! empty( $custom_options ) ) {

            // Format the options for ACF.
            // ACF expects an associative array where keys are values and values are labels.
            $formatted_choices = array();
            foreach ( $custom_options as $key => $label ) {
                // Ensure key and label are strings to avoid potential issues.
                $formatted_choices[ (string) $key ] = (string) $label;
            }

            // Assign the formatted choices to the field.
            $field['choices'] = $formatted_choices;

            // Optionally, set a default value if needed.
            // $field['default_value'] = 'option_2';

            // Optionally, set the field type if it's not already set correctly.
            // $field['type'] = 'select';
        } else {
            // Handle cases where the option is not set or is empty.
            // You might want to log an error or provide a fallback.
            $field['choices'] = array( '' => __( 'No options available', 'your-text-domain' ) );
        }
    }

    // Return the modified field array.
    return $field;
}
add_filter( 'acf/load_field', 'my_acf_load_dynamic_options_field' );

This function:

  • Hooks into acf/load_field.
  • Checks the $field['name'] to target the correct field.
  • Uses get_option() to fetch the serialized array from the database.
  • Iterates through the retrieved options and formats them into an associative array suitable for ACF’s choices property.
  • Assigns the formatted choices to $field['choices'].
  • Returns the modified field array.

Handling Different Field Types and Data Structures

The above example is for a ‘Select’ field. For other field types that use choices (Radio Buttons, Checkboxes, Multi-Select), the logic remains similar. The key is to ensure the data retrieved from get_option() is structured correctly for the specific field type.

Dynamic Values from a Database Table

Sometimes, your dynamic data might reside in a custom database table rather than a single WordPress option. In such cases, you’ll need to perform a direct database query within your ACF loader function. Ensure you sanitize and validate all data retrieved from the database.

/**
 * Load dynamic choices for ACF fields from a custom database table.
 *
 * @param array $field The ACF field array.
 * @return array The modified ACF field array.
 */
function my_acf_load_dynamic_db_options_field( $field ) {

    if ( 'my_dynamic_db_select' === $field['name'] ) {

        global $wpdb;
        $table_name = $wpdb->prefix . 'my_custom_data_table'; // Replace with your table name

        // Ensure the table exists before querying.
        if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) ) {
            $results = $wpdb->get_results( "SELECT id, name FROM {$table_name} ORDER BY name ASC", ARRAY_A );

            if ( ! empty( $results ) ) {
                $formatted_choices = array();
                foreach ( $results as $row ) {
                    // Assuming 'id' is the value and 'name' is the label.
                    $formatted_choices[ (string) $row['id'] ] = (string) $row['name'];
                }
                $field['choices'] = $formatted_choices;
            } else {
                $field['choices'] = array( '' => __( 'No data found in table', 'your-text-domain' ) );
            }
        } else {
            $field['choices'] = array( '' => __( 'Data table not found', 'your-text-domain' ) );
        }
    }

    return $field;
}
add_filter( 'acf/load_field', 'my_acf_load_dynamic_db_options_field' );

Important Considerations for Database Queries:

  • Always use the global $wpdb object for database interactions.
  • Prefix your table names using $wpdb->prefix for portability.
  • Sanitize any user-supplied input used in queries (though in this example, we’re not using user input directly in the query).
  • Validate the existence of the table before attempting to query it.
  • Format the results into the expected key-value pair structure for ACF choices.

Dynamic Field Values (Not Choices)

Beyond populating choices, you might need to dynamically set the *value* of a field based on other data. This is typically handled within the acf/load_field hook as well, by modifying the $field['default_value'] or by directly setting the field’s value if it’s a hidden or read-only field.

/**
 * Dynamically set the value of an ACF field.
 *
 * @param array $field The ACF field array.
 * @return array The modified ACF field array.
 */
function my_acf_load_dynamic_field_value( $field ) {

    // Example: Set a hidden field's value to the current user's ID.
    if ( 'my_hidden_user_id_field' === $field['name'] ) {
        $field['value'] = get_current_user_id();
        // For hidden fields, you might want to prevent editing.
        // $field['readonly'] = true;
        // $field['disabled'] = true;
    }

    // Example: Set a text field's default value based on another option.
    if ( 'my_dynamic_text_field' === $field['name'] ) {
        $default_text = get_option( 'my_plugin_default_text' );
        if ( $default_text ) {
            $field['default_value'] = $default_text;
        }
    }

    return $field;
}
add_filter( 'acf/load_field', 'my_acf_load_dynamic_field_value' );

Caching Considerations

When retrieving data from the Options API or a database, especially for fields that are frequently loaded or whose data doesn’t change often, consider implementing caching to improve performance. WordPress’s Transients API is an excellent tool for this.

/**
 * Load dynamic choices with caching.
 *
 * @param array $field The ACF field array.
 * @return array The modified ACF field array.
 */
function my_acf_load_dynamic_options_field_cached( $field ) {

    if ( 'my_dynamic_select_field_cached' === $field['name'] ) {
        $option_name = 'my_plugin_custom_settings';
        $transient_key = 'my_plugin_dynamic_options_' . $option_name;
        $cache_duration = HOUR_IN_SECONDS; // Cache for 1 hour

        // Try to get cached data
        $custom_options = get_transient( $transient_key );

        if ( false === $custom_options ) {
            // Data not in cache, retrieve from DB
            $custom_options = get_option( $option_name );

            if ( is_array( $custom_options ) && ! empty( $custom_options ) ) {
                // Format the options
                $formatted_choices = array();
                foreach ( $custom_options as $key => $label ) {
                    $formatted_choices[ (string) $key ] = (string) $label;
                }
                // Store in cache
                set_transient( $transient_key, $formatted_choices, $cache_duration );
                $field['choices'] = $formatted_choices;
            } else {
                $field['choices'] = array( '' => __( 'No options available', 'your-text-domain' ) );
            }
        } else {
            // Data found in cache
            $field['choices'] = $custom_options;
        }
    }

    return $field;
}
add_filter( 'acf/load_field', 'my_acf_load_dynamic_options_field_cached' );

To invalidate the cache when the underlying option is updated, you would hook into the option update process:

/**
 * Invalidate cache when the relevant option is updated.
 *
 * @param string $option The name of the option being updated.
 * @param mixed  $value  The new value of the option.
 * @param mixed  $old_value The old value of the option.
 */
function my_plugin_clear_dynamic_options_cache( $option, $value, $old_value ) {
    if ( 'my_plugin_custom_settings' === $option ) {
        delete_transient( 'my_plugin_dynamic_options_' . $option );
    }
}
add_action( 'update_option', 'my_plugin_clear_dynamic_options_cache', 10, 3 );
add_action( 'add_option', 'my_plugin_clear_dynamic_options_cache', 10, 3 ); // Also clear if added
add_action( 'delete_option', 'my_plugin_clear_dynamic_options_cache', 10, 3 ); // And if deleted

Conclusion

By strategically using the acf/load_field filter hook in conjunction with the WordPress Options API (and potentially direct database queries), you can build highly dynamic and responsive ACF field configurations. This approach allows for centralized management of choices and values, making your WordPress applications more robust and maintainable. Remember to consider performance implications and implement caching where appropriate.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (662)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (5)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (647)
  • SEO & Growth (492)
  • Server (118)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (726)
  • WordPress Theme Development (357)

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala