I'm building a custom dependent dropdown form with 2 select inputs.

I've managed to build the form inputs and update them accordingly, but I'm not able to get the dependent select's value in the submit hook.

I think this happens because I changed (via AjaxResponse()/ReplaceCommand) the $form in the AJAX callback, and as such, the second input values aren't updated in the $form_state.

My problem is that I have no simple way of knowing which options will be available either in the first-level select nor in the dependent select, since they come from a 3rd-party API.

Is there any way I can trigger the update of the #options in the dependent select, without having to render the nested dropdown and changing the $form so that the nested values are available in the submit hook?

Or, alternatively, is there anyway I can update the $form_state so that it knows the newly added #options of the second select?

  • $form is rebuilt in each ajax request before your ajax callback is even invoked. And then in your callback you are only allowed to use form elements from $form, because otherwise the form element is not processed and can't be submitted.
    – 4uk4
    Commented Nov 27, 2017 at 10:15
  • @4k4 thanks for your feedback, but I'm not sure what you mean. My dependent select element already exists in the form when it is first built, it just doesn't have the #options populated. After I populate them in the AJAX callback, they show up in the markup, but aren't available in the submit hook to be processed. Can you suggest how to handle this?
    – Joum
    Commented Nov 27, 2017 at 10:23
  • You populate the options in the form rebuild. The form build function knows that it is rebuilding when $form_state is filled with user input, which it is not on the first build.
    – 4uk4
    Commented Nov 27, 2017 at 10:28
  • 1
    See the topic "The Big Idea" in drupal.org/docs/7/api/javascript-api/ajax-forms-in-drupal-7
    – 4uk4
    Commented Nov 27, 2017 at 12:20
  • 1
    Please go ahead with your answer, sure this will help a lot of people, because ajax callbacks are a bit counterintuitive. I will upvote it.
    – 4uk4
    Commented Nov 27, 2017 at 13:03

1 Answer 1


With @4k4's help I managed to find the root cause of my issue and fix it.

I was wrongfully assuming that the $form elements could be somehow modified in an AJAX call to produce my desired effect.

While it is true that the elements could be modified and re-rendered in an AJAX response, that is not the correct way to alter a form after an AJAX call.

The 2 main things to know are:

  • forms are rebuilt everytime an AJAX call is made (when it is declared in the #ajax array of the form element triggering the call)
  • the AJAX function being called should (ideally) only return the element suffering modifications. It cannot modify the $form in any way, otherwise those modification will either be lost when it is rebuilt, or trigger the An illegal choice has been detected. Please contact the site administrator. message.

This being said, the general flow for this should be:

  • create the buildForm function with code that builds both your select elements. Wrap in a conditional clause code that tests if the parent select was already changed or not. This will be used to determine if the nested select should be populated or not;
  • set an #ajax property in the parent select element of the form, referring a callback to be used;
  • define the callback to return only the element which needs to change in the form, ie, the nested select;
  • when Drupal tries to rebuild the form, your parent select will already have a value that can be used in the above mentioned test-clause to allow for population of the nested callback.

@4k4's suggested documentation was difficult for me to find, but clear enough to follow trough once found - link here. (Even though it is for Drupal 7, in Drupal 8 it functioned exactly the same way).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.