Dynamically populate select field’s choice in Advanced Custom Fields

This tutorial will show you how to dynamically populate select fields choice with plugin Advanced Custom Fields. For example how to create Country / Area select fields where area depends on Country selected. Fields are populated from options page.

Step 1. Create fields with plugin

First step is obviously to create fields and attach them to post and options page.
In my setup it looks like this..

In the post

Options page

To make options page like this you would need addons Options page and Repeater field.

Step 2. Populate Country choices

Next step is to populate country select choices which we will populate from options page using ACF filter.

function acf_load_select_country( $field ) {
  // Reset choices
  $field['choices'] = array();

  // Get field from options page
  $countries_and_areas = get_field('countries_and_areas', 'options');

  // Get only countries in array
  foreach ($countries_and_areas as $key => $value) {
    $countries[] = $value['country'];
  }

  // Sort countries alphabetically
  natsort( $countries );

  // Populate choices
  foreach( $countries as $choice ) {
    $field['choices'][ $choice ] = $choice;
  }

  // Return choices
  return $field;

}
// Populate select field using filter
add_filter('acf/load_field/key=field_52b1b7007bfa4', 'acf_load_select_country');

Step 3. Get selected value

Next step is to get selected value from select field and make AJAX request which will return list of areas for selected country.
First we need to create .js file and enqueue it in admin, I will name this file: autopopulate.js and I will include it only if post type is ‘post’.

Enqueue script

function acf_admin_enqueue( $hook ) {

  $type = get_post_type(); // Check current post type
  $types = array( 'post' ); // Allowed post types

  if( !in_array( $type, $types ) )
      return; // Only applies to post types in array

  wp_enqueue_script( 'populate-area', get_stylesheet_directory_uri() . '/assets/js/autopopulates.js' );

  wp_localize_script( 'populate-area', 'pa_vars', array(
        'pa_nonce' => wp_create_nonce( 'pa_nonce' ), // Create nonce which we later will use to verify AJAX request
      )
  );
}

add_action( 'admin_enqueue_scripts', 'acf_admin_enqueue' );

Get value from select country

jQuery(document).ready(function($) {

	// Add default 'Select one'
	$( '#acf-field-country' ).prepend( $('<option></option>').val('0').html('Select Country').attr({ selected: 'selected', disabled: 'disabled'}) );

	/**
	 * Get country option on select menu change
	 *
	 */
	$( '#acf-field-country' ).change(function () {

		var selected_country = ''; // Selected value

		// Get selected value
		$( '#acf-field-country option:selected' ).each(function() {
			selected_country += $( this ).text();
		});

	}).change();
});

Step 4. Function to get areas by country

Next what we need is to create function that will return all areas for selected country. This function will receive one parameter ‘selected_country’ and then will return all areas for selected country.

Return areas function

// Return areas by country
function area_by_country( $selected_country ) {

  // Verify nonce
  if( !isset( $_POST['pa_nonce'] ) || !wp_verify_nonce( $_POST['pa_nonce'], 'pa_nonce' ) )
    die('Permission denied');

  // Get country var
  $selected_country = $_POST['country'];

  // Get field from options page
  $countries_and_areas = get_field('countries_and_areas', 'options');

  // Simplify array to look like: country => areas
  foreach ($countries_and_areas as $key => $value) {
    $countries[$value['country']] = $value['area'];
  }

  // Returns Area by Country selected if selected country exists in array
  if (array_key_exists( $selected_country, $countries)) {

    // Convert areas to array
    $arr_data = explode( ', ', $countries[$selected_country] );
    return wp_send_json($arr_data);

  } else {

    $arr_data = array();
    return wp_send_json($arr_data);
  }

  die();
}

add_action('wp_ajax_pa_add_areas', 'area_by_country');
add_action('wp_ajax_nopriv_pa_add_areas', 'area_by_country');

Step 5. Parse AJAX response and populate select options

Ok, now that everything is done, only thing left to do is to parse AJAX response and populate select fields options. This piece of code goes to autocomplete.js, so at the end this file would look like this:

jQuery(document).ready(function($) {

	// Add default 'Select one'
	$( '#acf-field-country' ).prepend( $('<option></option>').val('0').html('Select Country').attr({ selected: 'selected', disabled: 'disabled'}) );

	/**
	 * Get country options
	 *
	 */
	$( '#acf-field-country' ).change(function () {

		var selected_country = ''; // Selected value

		// Get selected value
		$( '#acf-field-country option:selected' ).each(function() {
			selected_country += $( this ).text();
		});

		$( '#acf-field-area' ).attr( 'disabled', 'disabled' );

		// If default is not selected get areas for selected country
		if( selected_country != 'Select Country' ) {
			// Send AJAX request
			data = {
				action: 'pa_add_areas',
				pa_nonce: pa_vars.pa_nonce,
				country: selected_country,
			};

			// Get response and populate area select field
			$.post( ajaxurl, data, function(response) {

				if( response ){
					// Disable 'Select Area' field until country is selected
					$( '#acf-field-area' ).html( $('<option></option>').val('0').html('Select Area').attr({ selected: 'selected', disabled: 'disabled'}) );

					// Add areas to select field options
					$.each(response, function(val, text) {
						$( '#acf-field-area' ).append( $('<option></option>').val(text).html(text) );
					});

					// Enable 'Select Area' field
					$( '#acf-field-area' ).removeAttr( 'disabled' );
				};
			});
		}
	}).change();
});

Conclusion

That’s it, you are done.
You can further test, expand and play with this code.

63 Comments

  1. I appreciate you publishing this tutorial. It is timely for me. My first step is to recreate what you’ve done. Then I will further expand it to fit my application.

    I’m getting a Warning: Invalid argument supplied foreach() for this part of your code on the following line:

    // Get only countries in array
    foreach ($countries_and_areas as $key => $value) {
    $countries[] = $value[‘country’];
    }

    I’m also getting a Warning: natsort() [function.natsort]: The argument should be an array on the following line:

    // Sort countries alphabetically
    natsort( $countries );

    And finally, I’m getting Warning: Invalid argument supplied foreach() on the following line:

    // Populate choices
    foreach( $countries as $choice ) {
    $field[‘choices’][ $choice ] = $choice;
    }

    Can you check this and see what I’ve done wrong? I have these lines of code in my functions.php file.

    Thanks,

    Tim

    1. Hi Tim,

      Both these errors are because $countries_and_areas is not an array.
      First what you should do is print_r($countries_and_areas) and see what you get there.

      Can you send screenshot of page where you add countries and areas in backend?

      BR
      Vlado

  2. Hi, thanks for publishing.
    I was wondering if it could be used in the profile section using the ‘user’ field group ? Because I tried a similar plugin ( https://github.com/Vheissu/acf-country-field#about ) which does exactly the same with Country > City relation, it works perfectly as a posts field, but as a profile/user field it shows the Country dropdown list but the City dropdown doesn’t appear to instantly refresh to correspond to the selected country. Only after updating the City drop down refreshes.

    Any thoughts? Does CSS work on profile page as it works on posts meta-boxes ? Or this shouldn’t be the problem..

  3. Hi Bobz, great job.
    I do not understand when you transmit the key of the select field area.
    The key for the country select area is key=field_52b1b7007bfa4.
    Thanks in advance.
    Andrea

    1. Hello Bobz,
      I recreated the initial situation (step 1) creating two custom fields “select html” displayed in the post. Then I created the custom field “Repeater” with sub-field country (text) and area (text-area) appear in the “options page” of acf.
      Add the correct option value to “select html” country following the Step 2, passing the key of the custom field “select html” country.

      add_filter(‘acf/load_field/key=mykeycode’, ‘acf_load_select_country’);

      Step 3
      I add the code “ENQUEUE SCRIPT” in function.php, I create autopopulate.js located in …. / wp-content/assets/js/autopopulates.js and insert the code “GET VALUE FROM SELECT COUNTRY”
      Step 4
      I add the code “RETURN FUNCTION AREAS” in function.php
      Step 5
      Replace part of the code entered in step 3 with the final code of the javascript autopopulates.js
      These are the steps I performed.

      However, the option value does not appear in the “select html” area.
      Maybe I do not understand when you declare the key or the information of the field “select html” area to populate.

      Thank you for your help.

    2. Hi Bobz,
      I understand the mistake made, now everything is working correctly.
      Do you think you can create two new “acf Filed select HTML” populated by “option page acf” without adding new text fields but using the existing fields.

      Thanks in advance.

    3. Bobz thanks for the reply.
      I hope to explain correctly,
      as in your example I have country and area values ​​inside “options” page (text and text area field).
      This values populate the “HTML select field” with the cross-checking of data (receive one parameter ‘selected country’ and then will return all areas for selected country ).
      I would like to duplicate the existing fields”select html” (county field and area field) keeping the existing relationship with country and area values ​​inside “options” page (text and text area field) for populate all select fields options (two county and two area ).

    4. Hi Bobz,
      I managed to solve my problem, however I noticed the following problem:
      publishing the post containing the HTML select fields and coming back later to edit the post will reset the HTML select field values ​​previously selected.
      Are you experiencing this problem too?

  4. it works fine till the end , values are stored in db but they are not selected when post is updated. Select fields reset to Select Country/ Select Area again

    1. Check console if there is any errors, should work.
      Or try removing this part:
      // Add default 'Select one'
      $( '#acf-field-country' ).prepend( $('').val('0').html('Select Country').attr({ selected: 'selected', disabled: 'disabled'}) );

      This one fires immediately after page is loaded.

  5. Hey Bobz,

    I managed to get your code working nicely, well done and thanks!

    I would however like to make a minor change to this and apply it only to the User section of WP dashboard. Upon looking for the get_post_type, I thought that the Users might have a post type of their own and I could adjust the post type then to something like “user” but this is not the case.

    `
    function acf_admin_enqueue( $hook ) {

    $type = get_post_type(); // Check current post type
    $types = array( ‘post’ ); // Allowed post types

    if( !in_array( $type, $types ) )
    return; // Only applies to post types in array

    wp_enqueue_script( ‘populate-area’, get_stylesheet_directory_uri() . ‘/js/autopopulate.js’ );

    wp_localize_script( ‘populate-area’, ‘pa_vars’, array(
    ‘pa_nonce’ => wp_create_nonce( ‘pa_nonce’ ), // Create nonce which we later will use to verify AJAX request
    )
    );
    }

    add_action( ‘admin_enqueue_scripts’, ‘acf_admin_enqueue’ );
    `

    How do I apply this to just users? I tried removing the check for Posts…
    `
    $type = get_post_type(); // Check current post type
    $types = array( ‘post’ ); // Allowed post types

    if( !in_array( $type, $types ) )
    return; // Only applies to post types in array
    `

    But that didn’t work either.

    1. Hi James,

      Remove the part:

      $type = get_post_type(); // Check current post type
      $types = array( ‘post’ ); // Allowed post types

      if( !in_array( $type, $types ) )
      return; // Only applies to post types in array

      Then it will be available in all screens in wp admin, or use $pagenow global variable to enqueue scripts only to user screen.
      https://codex.wordpress.org/Global_Variables

      Cheers

  6. Hi,

    thank you for the effort you have put into this code.

    I to had the issue when the post is updated that adding a default prepend was been displayed and this would cause the client some confusion.

    I then edited this out and it has worked fine.

    BUT, on the second field that is filtered by the first so “areas” in your example. when the post has been updated or we view the post again to edit. the areas field shows ” select area” but in reality we want it to show the selection we have already chosen.

    I think there needs to be some added code so that if a selection is made and stored and the post updated then it shows the “Area” when you come back to it.

    Maybe an listen to an event so that if Country changes then the Area gets filtered??

    1. Hi,

      What I do to make selected field is following:
      In step 3. when you localize script

      wp_localize_script( ‘populate-area’, ‘pa_vars’, array(
      ‘pa_nonce’ => wp_create_nonce( ‘pa_nonce’ ),
      ‘selected_country’ => get_field(‘selectd_country’, $current_post->ID)
      ‘selected_city’ => get_field(‘selectd_city’, $current_post->ID),
      ‘selected_area’ => get_field(‘selectd_area’, $current_post->ID),
      )
      );

      This way, selected city variable would be accessible with jquery like: var my_selected_city = pa_vars.selected_city.
      Then, when you populate select with AJAX, after all options are added, do: $(‘.my-select option[value=”‘ + my_selected_city +'”]’).attr(‘selected’,’selected’);

      Hope it helps.

      BR
      Vlado

  7. Hi, this look like a great bit of script. Could you advise me on how you set up the post view and the options page? I’ve got the plugin but i’m a little stuck?… Thank you

  8. Hi Bobz,
    I’m trying to make an options page that allows you to chose a pre-existing Gravity Forms form and use it as an exit intent modal. I think your script is pointing me in the right direction but I was wondering if you would have any advice on how to grab existing forms for the fields and values to populate my options select fields?

    1. Hi Farah,

      I think that now instead of targeting by key, you need to target by name, so instead of add_filter('acf/load_field/key=field_52b1b7007bfa4', 'acf_load_select_country'); it would be add_filter('acf/load_field/name=name_of_your_field_here', 'acf_load_select_country');

  9. There seems to be a number of things have changed in ACF PRO since this was posted. I had to change the JS file as I couldn’t target elements through ID in the same way as this example. However, my question is whether anyone has managed to adapt this for repeated fields?

    Let me explain; supposed I set this up as described (but it could be ‘car manufacturer’ & ‘car model’) and want these two select fields to subfields in a repeater field? There doesn’t seem to be any simple way of targeting the correct row in the field (unless I’m missing something glaringly obvious)?

    I know this is an old thread, but I’m hoping someone can help?

    1. Hi Simon,

      Late thread, late replay :) sorry.
      Yeah, looks like this one needs updating. Now you populate fields based on ‘name’, eg:

      add_filter('acf/load_field/name=select_sidebar', function ( $field ) {
      
      	$field['choices'] = [];
      
      	$sidebars = ['sidebar_1', 'sidebar_2'];
      
      	foreach ( $sidebars as $sidebar ) :
      
      		$id = sanitize_title( $sidebar );
      		$field['choices'][$id] = $sidebar;
      
      	endforeach;
      
      	return $field;
      });
      
    2. Hi Farah,

      To populate second select menu based on first select menu, you need to fire AJAX function, get details from database and populate second select field options with jQuery.
      You cannot do that with with this filter, but you need a custom function.

  10. Hi, thx for your script.
    I use it perfectly on the posts but it fails with the same way on the user pages (new & edit).
    The scripts are well registered and well fired but the ACF fields are only defined on the posts even they are presents on the users pages too.

    jQuery(document).ready(function($) {
    var selected_reg = $( ‘#acf-field-p_region’ ).val();
    console.log(‘selected_reg = [‘ + selected_reg + ‘]’);

    new post : selected_reg = []
    edit post : selected_reg = [ALSACE]

    jQuery(document).ready(function($) {
    var selected_reg = $( ‘#acf-field-u_region’ ).val();
    console.log(‘selected_reg = [‘ + selected_reg + ‘]’);

    new users : selected_reg = [undefined]
    edit users : selected_reg = [undefined]

    It seems the (document).ready function is fired before the ACF fields definition in the Users admin pages.
    Could you tell me back how I can resolve this issue ?

    Cheers

    1. Hi Esa, that is really strange it’s like selector doesn’t exist.
      Please check trough inspector to see if you use correct jQuery selector.
      Basically it should work everywhere, but I cannot tell for every specific case.

    2. Hi,
      thx for your reply.
      I just took a look on the event listeners and you’d right, on user pages, it’s missing :
      http://esa-dev.com/img/acf_p.jpg
      http://esa-dev.com/img/acf_u.jpg

      but see my enqueue :
      function acf_admin_enqueue( $hook ) {
      global $pagenow;
      $type = get_post_type(); // Check current post type
      $types = array( ‘post’ ); // Allowed post types
      if( in_array( $type, $types ) ) {
      wp_enqueue_script( ‘populate-area’, get_stylesheet_directory_uri() . ‘/js/autopopulates.js’, array( ‘jquery’, ‘underscore’, ‘acf-input’) );
      wp_localize_script( ‘populate-area’, ‘pa_vars’, array(
      ‘pa_nonce’ => wp_create_nonce( ‘pa_nonce’ ), // Create nonce which we later will use to verify AJAX request
      ));
      } else if ( in_array( $pagenow, array(‘user-new.php’,’user-edit.php’) ) ) { // Only on user pages
      wp_enqueue_script( ‘populate-area’, get_stylesheet_directory_uri() . ‘/js/autopopulatesusers.js’, array( ‘jquery’, ‘underscore’, ‘acf-input’) );
      wp_localize_script( ‘populate-area’, ‘pa_vars’, array(
      ‘pa_nonce’ => wp_create_nonce( ‘pa_nonce’ ), // Create nonce which we later will use to verify AJAX request
      ));
      }
      }

      I think all is fine from my side.

      Cheers

  11. Like you could see on my first post, I created “p_region” ACF field for posts and “u_region” for users.
    Each js file has its own jQuery(document).ready(function($) regarding the accorded field.
    Both “wp_enqueue_script” are well hooked but when the document.ready is processing on user pages, fields aren’t defined.
    At this time the process is the same but it will be different in the future, it’s why I use different fields and different js files.

    Cheers

  12. Thanks for this article. I have a few issues that I cannot quite figure out:
    I have a Repeater Field in the Options page which is named ‘country_region’ and sub-fields ‘country’ and ‘region’
    and two dropdown fields called ‘land’ and ‘bundesland’ inside the custom post type edit form.

    #1 issue: when naming the dropdown fields the same as country and region, I get a big error when useing name=country for the function, so I renamed the dropdown fields.
    #2 issue: the prepending of “Select Country” or “Select Area” doesn’t work, I only get the dropdown for the country
    #3 issue: the ajax script is loading but doesn’t work correctly, I get a disabled field after consulting this post https://stackoverflow.com/questions/30464072/advanced-custom-fields-populate-select-based-on-another-acf-field/46758747#46758747 but the regions aren’t loaded into the bundesland dropdown field.

    If I have missed anything, please let me know.
    Thanks

  13. Hi, thanks for the tutorial, the javascript are not working and I followed all the steps, I only got the countries populated, could you help?

    Thanks in advance!!

  14. Would this also work with taxonomy parent and children? I have a taxonomy ‘region’ which has country – county – city levels and I want each dropdown dependent on the previous selection. But I need them as taxonomies because I am saving custom meta data with each term and this would not be possible with just a simple list in a text field.

  15. Hi, Thanks a lot for this code.

    Is it still working?

    I get the countries but not the regions shown.

    Can anyone please help?

    Thanks

    1. Hi,

      I think, it should work.
      Maybe just update ‘/key=..’ with ‘/name=…’
      add_filter(‘acf/load_field/key=field_52b1b7007bfa4’, ‘acf_load_select_country’);

      If not working, there is probably something wrong with js selectors.
      Check console for errors.

      Cheers

  16. Hi, thanks a lot for your help.

    add_filter(‘acf/load_field/key=field_5c7f1915175b2’, ‘acf_load_select_country’); is working for me because countries are shown, however add_filter(‘acf/load_field/name=country’, ‘acf_load_select_country’); returns a 500 Internal server error.

    Could you please have a look to my config, please?

    – I´m using a mu-plugin for my functions.
    – First I created the options page with this code:

    https://pastebin.com/6aeUXBgP

    – Then I created a group of fields called Country / Area Options for the Options Page with a repeater field called Counties and Areas (countries_and_areas). Inside there are two fields: Country (text) and Area (text area).

    – In Options page I have created a couple of countries with several areas separated with “, ”

    – Later I have created a new group of fields called Country / Area for posts with two fields: Country (country) and Area (area). Both are selects fields.

    – IMPORTANT: I have setted no options for this fields, just Select Country or Select Area as defatult value ???

    – Then I use your code, this in my funcitions.php:
    https://pastebin.com/nzBjD4EM

    – And this in wp-content/themes/mytheme/assets/js/autopopulate.js:
    https://pastebin.com/cejLNSAM

    – That´s all. As a result I get countries in first select (country) but nothing in second one (Areas).

    Could you please help? I need this to work so bad…

    Thanks a million.

    1. Hi.
      Could you please use pastebin or similar when you post code in comments, it’s just making bloat and it doesn’t help anyone else who might have same problem like you.
      Anyhow, error 500 means there is something wrong with the code, can be a typo or something.
      Turn on debug mode (https://codex.wordpress.org/WP_DEBUG) and check the logs, it will point you to right direction.

  17. Thanks and sorry for all the code I left badly.

    I have seted this in wp-config.php but nothing is shown and no file is saved in wp-content/debug.log, in fact the file does not exist. Just get the 500 error.
    define(‘WP_DEBUG’, true);
    define(‘WP_DEBUG_LOG’, true);

    Anyway, do you think this is the problem? I get countries, so…

    Thanks

    1. Hi again,

      Finally It seems the problem is not the “acf/load_field/name=country” issue. As said before countries are shown ok with key=field_xxxxx, but areas are not shown.

      Can anybody, please, check the code and make any fixes needed if any? Is anyone still using this code? I have spent hours trying to discover the problem with no luck…

      Thanks a lot

    2. Thanks again Vlado.

      I have checked server logs and found two errors:

      (104)Connection reset by peer: mod_fcgid: error reading data from FastCGI server, referer: POST URL

      (104)Connection reset by peer: mod_fcgid: ap_pass_brigade failed in handle_request_ipc function, referer POST URL

      Any clues?

      Could you please update your code with NAME instead of KEY? I think I´m missing something…

      Thanks a million

    3. Ok I give up.

      Never will be able to make it working on my own.

      If anyone can fix this code for my site please contact me, I´m willing to pay.

      Thanks

    4. Never give up.
      Sounds like there is something wrong with your PHP/Server config.
      There is no magic answer to solve your error, either reinstall or try on some other environment first.
      Bottom line, as you said maybe hire someone to complete these tasks for you, best way to hire someone would be trough freelance websites like upwork, fivver etc..
      Good luck

  18. How can we implement same functionality in front end of our theme?
    i have implemented above explained code within my admin panel and it works fine but i have problem implementing selective search on basis of the option page.
    Can you please help me with it?

    1. Hi, I’m glad this works for you in admin as intended.
      If you are using ACF on front end, I believe same should work, otherwise populating select menu should be done manually using Javascript.
      You might want to check out some of the ‘select’ plugins, like selectric, selectize or Select2

  19. Vlado—

    This is extremely helpful, but I have a few unique aspects that are preventing it working. Would you mind taking a look?

    First, my functions are here: https://pastebin.com/5YGDjp2u
    and my JS is here: https://pastebin.com/wfTaUQ9d

    I have two custom post types: committees and meetings. In short, my group creates committees, each of which schedules their own meetings.

    For our purposes, committees have two ACF text fields: committee_name and committee_location. Committee_location is not a required field—not all committees meet in a consistent location—but the name is.

    Meetings also have two fields: meeting_committee (select field) and meeting_location (text field). When creating a meeting, I want a dropdown of all the committees. When I select one, I want that committee’s location (if it exists) to populate the meeting_location field as a default value. Users should be able to type in a new location for the meeting if they want.

    So when creating a meeting, it differs from your example in these ways:

    1. Rather than getting values from an option page, I loop through the committee posts to populate the meeting page dropdown.
    2. To create the associative array, I have to loop through them again and use committee_name and committee_location as the key => value pair.
    3. Instead of creating another select dropdown (as you do with your Area), I just need to populate the default value of the meeting_location text field.
    4. Once the meeting is saved, it should display the correct values in the meeting_committee dropdown and in the meeting_location (i.e., blank, default pulled from committee page, or new value typed by user).

    At this point, I can’t get the committee_location field to populate with AJAX. I tried isolating the problem by creating the associative array by hand (thinking my approach in the loop was faulty), but that didn’t work, either. (I have confirmed that autopopulate.js is loading properly.) I also can’t get the meeting_committee value to display after a save.

    Any help you can provide would be appreciated. I do think this variant would be useful to other people, as you won’t always have your data stored in an Options page or a repeater field.

    1. Hi Doug,
      Sorry, but it’s really difficult for me to understand what you are working on without seeing this in action.

    2. My code is just example for my case, idea is to give people explanation how this works, it doesn’t cover all possible scenarios. As far as I understand in your case you did everything ok, not sure what is the problem.
      If I understand correctly, when user selects meeting committee, you want to automatically populate meeting location from committee location.

      1. ‘Select’ committee is already populated when you add new meeting

      2. When you ‘select’ certain committee, you trigger AJAX to get that committee_location and this should return you ‘committee_location’ field.
      Meaning ‘pl_add_location’ should return just: get_post_meta( $selected_committee_id, ‘committee_location’ true );

      3. There is no need for you to append results to any select, instead you need to say something like

      $('input[data-name="meeting_location"]').val( response_from_ajax_var );

Leave a Reply

Your email address will not be published. Required fields are marked *