I'm trying to set up a front end form where the user can choose their city and state, which are stored as categories. I have states set up as the parent category and individual cities as sub categories.

I'd like to have the two select fields tied together so that when the state is chosen, the other select field is narrowed down to the cities in that state and vice-versa, e.g. if a user chooses Chicago only the Illinois select field should be available.

Is this possible?


Choose San Francisco --> California is selected

Choose Colorado -- > Only Colorado cities are available

Here are the two select fields. I know that using .level('1') and .level('2') is the wrong way, but I'm not sure what the correct way would be.

{% set userOptionIds = user.providerArea.limit(null).ids() %}

<label for="locationCategories">Parent locationCategories:</label>
{% set parentCategories = craft.categories.group('locationCategories').level('1') %}

    <select id="providerArea" name="fields[providerArea][]">
    {% for category in parentCategories %}
        <option id="locationCategories" name="fields[locationCategories]" {% if category.id in userOptionIds %}selected{% endif %} value="{{ category.id }}">{{ category.title }}</option>
    {% endfor %}

<label for="locationCategories">Child locationCategories:</label>
{% set childCategories = craft.categories.group('locationCategories').level('2') %}

    <select id="providerArea" name="fields[providerArea][]">
    {% for category in childCategories %}
       <option id="locationCategories" name="fields[locationCategories]" {% if category.id in userOptionIds %}selected{% endif %} value="{{ category.id }}">{{ category.title }}</option>
    {% endfor %}}

1 Answer 1


You would need to implement a front-end solution of some kind using jquery and ajax, that listens to the .on('change') event on the select fields and loads the corresponding data from a template (i.e. '_cityState.html') using the selected city or state as post parameters; and then repopulate the other select field's options dynamically by setting the html of the select field to the return results.

Have a look at jquery's .on('change') event and $.ajax() and .html() methods for more information on the jQuery part.

In your ajax only '_cityState.html' template you would want something like this: (note: this assumes that cities and states have a 'code' field that will represent the drop-down option value, but modify as needed):

{% if craft.request.isAjax %}

    {# retreive the passed city or state code #}
    {% set city = craft.request.getParam('city') %}
    {% set state = craft.request.getParam('state') %}

    {% if city or state %}

        {% if city %}

            {# get all states; set the selected state to parent of 'city' #}
            {% set entries = craft.categories.group('locationCategories').limit(null).level(1) %}
            {% set selected = craft.categories.group('locationCategories).code(city).first().parent %}

        {% else %}

            {# get all cities that are a decendent of 'state'; set selected to first city #}
            {% set entries = craft.categories.group('locationCategories').limit(null).code(state).first().decedents(1) %}
            {% set selected = entries.first() %}

        {% endif %}

        {# build the return html #}
        {% for entry in entries %}
            <option value={{ entry.code }} label="{{ entry.title }}" {% if entry == selected %}selected{% endif %}/> 
        {% endfor %}

    {% endif %}

{% endif %}

Not tested. But should be pretty close. If anyone sees any errors, please edit or leave a comment.

  • You're a machine, Douglas.
    – Brad Bell
    Commented Nov 18, 2014 at 3:52
  • I really like your answers on Craft with ajax topics, Douglas. Thanks!
    – carlcs
    Commented Nov 18, 2014 at 12:23

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.