Add input fields dynamically with wtforms

Sean Vieira's answer works great for StringFields (previously TextField), but things get a little trickier for dynamic SelectFields. For anyone interesting in how to implement dynamic SelectFields with wtforms, see https://stackoverflow.com/a/57548509/7781935


WTForms has a meta-field called FormField and another meta-field called FieldList. These two combined together will get you what you want:

class AddressEntryForm(FlaskForm):
    name = StringField()

class AddressesForm(FlaskForm):
    """A form for one or more addresses"""
    addresses = FieldList(FormField(AddressEntryForm), min_entries=1)

To create entries in the AddressesForm, simply use a list of dictionaries:

user_addresses = [{"name": "First Address"},
                  {"name": "Second Address"}]
form = AddressesForm(addresses=user_addresses)
return render_template("edit.html", form=form)

Then, in your template, simply loop over the sub-forms:

{% from 'your_form_template.jinja' import forms %}
{% for address_entry_form in form.addresses %}
    {{ address_entry_form.hidden_tag() }}
    {# Flask-WTF needs `hidden_tag()` so CSRF works for each form #}
    {{ forms.render_field(address_entry_form.name) }}
{% endfor %}

WTForms will automatically prefix the names and the IDs correctly, so when you post the data back you will be able to just get form.addresses.data and get back a list of dictionaries with the updated data.