Create self-populating ajax drop-down forms in Drupal with the Form API


At risk of making the title of this post a mouth full, I thought I’d share some of our experience with interacting with Drupal’s Form API (Specficially Drupal 7.x).

There is a lot of documentation on different ways you can interact with the Form API to accomplish a wide range of tasks from collecting information, processing information or building complex search queries.

The latter is where we ventured in our most recent work interacting with Drupal’s Form API. We worked on a project where the requirement was to build a drop-down based filtration system where content category choices in the drop-down boxes would auto populate dependent taxonomies based on the relationship of the taxonomies , content fields and content in general. After choosing all the drop-down boxes, the end-user would then be able to click a “Go” button to build a list of results that apply the selected filters.

We did quite a bit of research on how to auto populate drop down boxes based on preceding choices. It turns out that Drupal’s Form API already has everything we need to accomplish this! I’ll break down our process into a few different sections and hopefully our research will help you!

Create a Drupal Module

This isn’t completely necessary however we felt that the best strategy for us was to put the entire process of building, validating and processing the form into a Drupal module.

There’s quite a few guides out there with respect to building your own that you could check out (if you’re new at this) to create your own drupal module

In the module, you will be creating module functions to create, validate and process the form. Validation isn’t 100% necessary and is usually reserved if you have more dynamic form input. Drop-selections limit the scope of input so you may not need validation.

function shift8_dependent_dropdown($form, &$form_state) {
    // This is where you will build your form options and call other functions to generate options

function shift8_dependent_dropdown_reset($form, $form_state) {
    // This function will be called when the end-user wants to "Reset" the form and return all the selection options to default

function shift8_dependent_dropdown_validate($form, &$form_state) {
    // This function is where you validate the input. This acts as a validation function after the user hits submit and can block data from being passed to the submit functions if it is invalid.

function shift8_dependent_dropdown_submit($form, &$form_state) {
    // This is the submit function and is responsible for taking all the input data and does whatever you want with it. You can build a dynamic query and display search results in this function, for exmaple

Create your dropdown options

The next thing you need to do is create your drop-down options. In our example here, we will have a total of 4 drop-down options. The only drop-down that will be pre-populated when you first load the page will be the first one. All the subsequent drop-downs will populate based on the preceding choice.

        $options_first = _shift8_get_first_dropdown_options();
        $form['dropdown_first'] = array(
                '#type' => 'select',
                '#title' => 'Category',
                '#prefix' => '

‘, ‘#options’ => $options_first, ‘#default_value’ => ‘– Select Category –‘, ‘#ajax’ => array( ‘callback’ => ‘shift8_dependent_dropdown_callback’, ‘wrapper’ => ‘dropdown-second-replace’, ), );

The above code is the form interaction to create the first dropdown. The things to note are the #ajax options that are passed and the $options_first variable.

The $options_first variable being defined is populated by triggering the function _shift8_get_first_dropdown_options. This will be a common theme you will see in the examples within this post. We are using callback or trigger functions in order to perform queries so that we can dynamically populate these options. This allows us to simply change the taxonomy relationships or content fields in drupal itself in order to manipulate these options instead of actually editing the module’s code. We want something dynamic and easy to use!

The shift8_dependent_dropdown_callback is a key callback. We are using the Form API’s ajax capability to trigger these functions and auto populating the subsequent drop-downs via Ajax so that we dont need a page refesh. And it looks fancy. So this callback function is triggered as soon as you make a selection. Remember that as it is the main way that this entire exercise functions.

Populate options with Ajax

For me to better illustrate how these ajax callback’s work with the drop-down auto populating fields, I’ll start with the second dropdown in our example , instead of the first dropdown thats shown above. This is because in our example, the second dropdown does some actual MySQL queries to populate the options.

        // Second option
        $form['dropdown_second'] = array(
                '#type' => 'select',
                '#title' => 'Second Option',
                '#prefix' => '

‘, ‘#options’ => isset($form_state[‘values’][‘dropdown_first’]) ? _shift8_get_second_dropdown_options($selected) : 0, ‘#default_value’ => ”, ‘#ajax’ => array( ‘callback’ => ‘shift8_dependent_dropdown_callback_second’, ‘wrapper’ => ‘dropdown-third-replace’, ), );

So there’s two things to look at here. The first is the #options field in this form query :

isset($form_state['values']['dropdown_first']) ? _shift8_get_second_dropdown_options($selected) : 0

The options for this dropdown does a simple check to see if the first option was ever selected, and if so to trigger the function _shift8_get_second_dropdown_options :

function shift8_get_second_dropdown_query($firstchoice) {
    if ($_SESSION['first_selected']) {
        $query = db_select('taxonomy_term_data');
        $subquery = db_select('field_data_field_custom');
        $subquery->fields('field_data_field_custom', array('entity_id',));
        $subquery->condition('field_custom_tid', $first_selected_option, 'IN');
        $query->fields('taxonomy_term_data', array('tid', 'name', 'vid',));
        $query->condition('tid', $subquery, 'IN');
        $query->orderBy('name', 'ASC');
        $results = $query->execute();
        $result_name = array();
        $result_name[] = '-- Select Third Option --';
        foreach ($results as $result) {
            $result_name[] = $result->name;
        return $result_name;

What the heck is happening here? You don’t need to really look at the mysql query itself other than to understand that I’m doing a database interaction to dynamically pull information from Drupal in order to build a populated dropdown list of options. The only really important thing to remember is that I’m returning the results as an array that are called when the second dropdown option function is triggered.

After the options are returned as an array and the dropdown is populated, we want to trigger that callback function when you actually make a choice. If you think of the option population function and the ajax callback as a one-two linear progression of populating the dropdown option and then triggering the subsequent dropdown to populate, it may be easier to understand :

function shift8_dependent_dropdown_callback_second($form, $form_state) {
        $commands[] = ajax_command_insert('#dropdown-third-replace', drupal_render($form['dropdown_third']));
        return array('#type' => 'ajax', '#commands' => $commands);

So this callback is simply triggering the $form[‘dropdown_third’] query. The same things happen for dropdown_third as did with second. One thing to note is that you may want to save the selected optons in a $_SESSION variable in order to ensure that the selected options are not lost or accidentally overwritten. In the form reset function (as outlined at the top), you can simply unset the $_SESSION variables to clean them out when needed.

There are many many other interesting things that we had to research when we completed this Form API project. Most of them are specific to our Drupal content arrangement and may or may not be useful to the average person. We had some very interesting nested multi-layered mysql database queries as a result of this auto populating dropdown form. Maybe we’ll save that for another post!

I hope this has been helpful!

Notify of
Newest Most Voted
Inline Feedbacks
View all comments
4 years ago

Finally!!!!! Thank you. The example module covers 1 dependent dropdown, and I’ve seen several recommendations to just use the ‘hierarchical_select’ module (overkill?). No doubt, you have saved me from self-induced baldness. Did I already say THANK YOU~

4 years ago
Reply to  Aaron

Glad to hear it was helpful for you!