Express Forms Express Forms for Craft
2.x ✓ Latest

Templating Overview

This documentation page provides the specifics of what's available, and gives an overview of how to add forms to your templates.

TIP

Be sure to check out the following resources for more help:

Example Form

Introduction

To render a form in your templates, you'll need to specify the following:

{% set form = craft.expressforms.form("myFormHandle") %}
    {# General Errors, Success message, etc #}
{{ form.openTag({ return: 'contact/thanks' }) }}
    {# Form Contents #}
{{ form.closeTag }}
1
2
3
4
5

Both form.openTag() and form.closeTag handle the opening and closing <form> and </form> form tags. These are mandatory, because there are some features that depend on these methods to be called.

Form Parameters

The following parameters are available to use on form.openTag():

  • return - the return URL for the form upon successful submit, e.g. return: '?success=1'.
  • attributes - an array of HTML attributes for the form:
    • class - sets the class attribute for the form tag, e.g. class: 'my-class'. Not included by default.
    • id - sets the id attribute for the form tag, e.g. id: 'my-form'. Not included by default.
    • name - sets the name attribute for the form tag, e.g. name: 'my-form'. Not included by default.
    • method - overrides the method attribute for the form tag, e.g. method: 'post'. Default is post.
    • action - overrides the action attribute for the form tag, e.g. action: '/path-to/something-else/'.
      • This would bypass all of Express Forms validation, data saving and email notification features, etc.
    • And anything else you can think of, really. Could be novalidate, could be "data-some-key", etc.
  • dynamicRecipients
    • Lets you to establish a Dynamic Recipients field type in your form, which allows you to have form users securely select a predefined recipient to receive the email notification.
      {{ form.openTag({
        dynamicRecipients: {
          myFieldHandle: {
            map: {
              sales: "sales@example.com",
              service: "service@example.com",
              support: ["support@example.com", "another@example.com"]
            },
            template: "template-filename.twig",
          },
        }
      }) }}
      
      <select name="myFieldHandle">
        <option value="sales">Sales</option>
        <option value="service">Service</option>
        <option value="support">Support</option>
      </select>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
  • dynamicNotifications
    • Lets you to establish a template-level Dynamic Notification in your form, which allows you to dynamically feed a recipient to receive the email notification (e.g. passing an email address from a Craft Entry field, etc).



       
       
       
       


      {% set form = craft.expressforms.form("formHandle") %}
      
      {{ form.openTag({
        dynamicNotifications: {
          to: ["recipient-one@example.com", "recipient-two@example.com"],
          template: "template-filename.twig",
        }
      }) }}
      
      1
      2
      3
      4
      5
      6
      7
      8
  • yourCustomParameter
    • You can arbitrarily add custom dynamic parameters to forms that allow you to "securely" (uses a unique salt to encrypt the data with the OpenSSL library) and secretly pass data along with the rest of the posted data for later retrieval, for instance - in email notifications. The encrypted data is never stored in the database, and only resides in the POST array for that single request. Here's an example:


       


      {{ form.openTag({
          return: '?success=1',
          customBcc: 'mc@hammer.time',
      }) }}
      
      1
      2
      3
      4
      Then in your email notification template, call it with form.parameters.yourCustomParameter:






       



      ---
      name: 'Admin Notification'
      fromName: '{firstName} {lastName}'
      fromEmail: '{email}'
      replyTo: '{email}'
      cc:
      bcc: '{{ form.parameters.customBcc }}'
      subject: 'New submission from your {{ form.name }} form'
      ...
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
  • recaptcha: true
    • When disabling the reCAPTCHA Load Script setting and adding the reCAPTCHA script manually (e.g. to change the language), you need to also add recaptcha: true to your form.openTag to get the proper reCAPTCHA validation working.

Form Properties

  • name - the name of the form, e.g. My Contact Form.
  • handle - the handle of the form, e.g. myContactForm.
  • id - the unique ID for the form, e.g. 13.
  • description - the description of the form, e.g.
    • Please fill out the form below and we'll get back to you as soon as possible.
  • color - the color of the form, as a hex code, e.g. #ff0000.

Automated Field Rendering

If you wish to use Express Forms' field rendering automation, you would do so with form.fields. This isn't practical for all forms, but for simpler and/or more basic ones, it allows you to loop through your fields without having to manually hardcode each field, and can save you time. Any later changes/updates to the form inside the Form Builder would automatically update in your template. For manual field rendering, skip this and proceed to Manual Rendering section.

TIP

Check out the Field Types docs for complete information about available field types.

The bare bones of using form.fields would look something like this:

 
 

























 
 


{# Start field render automation #}
{% for field in form.fields %}
    {% if field.type == "hidden" %}
        {# Hidden field formatting contents #}
    {% elseif field.type == "textarea" %}
        {# Textarea field formatting contents #}
    {% elseif field.type == "file" %}
        {# File field formatting contents #}
    {% else %}
        {# Check on a Field Handle for Exception to formatting #}
        {% if field.handle == "myFieldHandle" %}
            {# Specific field formatting overrides #}
        {% else %}
            {# All other fields formatting - EXAMPLE #}
            <div>
                <label for="{{ field.handle }}"{{ field.isRequired ? ' class="required"' }}>{{ field.label }}</label>
                <input id="{{ field.handle }}" type="{{ field.type }}" name="{{ field.handle }}" value="{{ field.value }}">
                {% if field.errors %}
                    <ul class="errors">
                        {% for error in field.errors %}
                            <li>{{ error|t }}</li>
                        {% endfor %}
                    </ul>
                {% endif %}
            </div>
        {% endif %}
    {% endif %}
{% endfor %}
{# End field render automation #}
<input type="submit" value="Submit">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Available Properties

The following properties are available to use when automating field rendering:

  • field.label - renders as the label set inside form builder for the field.
  • field.value - renders as the value submitted previously for the form for that field.
    • When using multi-option fields (e.g. Options field type) like radios or checkboxes, you can have the form select the previously chosen option before the triggered error with a conditional like {{ "myValue" in fields.value ? " checked" }}.
  • field.type - renders as the field type for the field, e.g. textarea or file.
  • field.handle - renders as the field handle for the field.
  • field.errors - loops through available error validation for the field.
    • Should be wrapped in a {% if field.errors %} conditional. See below for example...
  • field.isRequired - typically used as a conditional to add an extra CSS class to a field to style it as required (show asterisk, etc).
    • E.g. <label for="{{ field.handle }}"{{ field.isRequired ? ' class="required"' }}>

Error handling after error on submit

{% elseif field.type == "file" %}
    <p>
        <label for="{{ field.handle }}"{{ field.isRequired ? ' class="required"' }}>{{ field.label }}</label>
        <input type="{{ field.type }}" name="{{ field.handle }}[]" multiple>
        {% if field.errors %}
            <ul class="errors">
                {% for error in field.errors %}
                    <li>{{ error|t }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    </p>
{% else %}
1
2
3
4
5
6
7
8
9
10
11
12
13

Manual Field Rendering

If you wish to render your form fields manually, you can code your form fields to look something like this if you desire:

<div>
    <label for="name" class="required">Name</label>
    <input id="name" type="text" name="name" value="">
</div>
1
2
3
4

Or, you can slightly automate and improve this with the following:

<div>
    <label for="name"{{ form.fields.name.isRequired ? ' class="required"' }}>{{ form.fields.name.label }}</label>
    <input id="name" type="text" name="name" value="{{ form.fields.name.value }}">
    {% if form.fields.name.hasErrors %}
        {{ "This field is required!"|t }}
    {% endif %}
</div>
1
2
3
4
5
6
7

The benefits of the above are:

  • form.fields.name.label will automatically update itself if the label is updated in the Form Builder in the control panel.
  • form.fields.name.value will maintain the user's inputed value in the event the page needs to reload due to an error.
  • form.fields.name.hasErrors allows you to display an inline error message for that field. See Error Handling section for more info about this.
  • form.fields.name.isRequired - will automatically add a CSS class (asterisk, etc) to your form based on whether or not the field is set to required in the Form Builder in the control panel.

Available Properties

The following automation is available when manually rendering data for a given field:

  • form.fields.myFieldHandle.label - renders as the label set inside form builder for the field.
  • form.fields.myFieldHandle.value - renders as the value submitted previously for the form for that field.
    • When using multi-option fields (e.g. Options field type) like radios or checkboxes, you can have the form select the previously chosen option before the triggered error with a conditional like {{ "myValue" in form.fields.myFieldHandle.value ? " checked" }}. See code example below.
  • form.fields.myFieldHandle.type - renders as the field type for the field, e.g. textarea or file.
  • form.fields.myFieldHandle.handle - renders as the field handle for the field.
  • form.fields.myFieldHandle.errors - loops through available error validation for the field.
    • Should be wrapped in a {% if form.fields.myFieldHandle.hasErrors %} conditional. See code example below, or Error Handling section for more info about this.
  • form.fields.myFieldHandle.isRequired - typically used as a conditional to add an extra CSS class to a field to style it as required (show asterisk, etc).
    • E.g. <label for="message"{{ form.fields.message.isRequired ? ' class="required"' }}>{{ form.fields.message.label }}</label>

Handling option checking and error handling after error on submit





 








 
 
 
 
 
 
 
 


<p>
    <label{{ form.fields.howHeard.isRequired ? ' class="required"' }}>{{ form.fields.howHeard.label }}</label>
    <div class="checkbox">
        <label>
            <input type="checkbox" name="howHeard[]" value="newspaper"{{ "newspaper" in form.fields.howHeard.value ? " checked" }} /> Newspaper
        </label>
        <label>
            <input type="checkbox" name="howHeard[]" value="radio"{{ "radio" in form.fields.howHeard.value ? " checked" }} /> Radio
        </label>
        <label>
            <input type="checkbox" name="howHeard[]" value="friend"{{ "friend" in form.fields.howHeard.value ? " checked" }} /> Friend
        </label>
    </div>
    {% if form.fields.howHeard.hasErrors %}
        <ul class="errors">
            {# Loop through available Errors #}
            {% for error in form.fields.howHeard.errors %}
                <li>{{ error|t }}</li>
            {% endfor %}
        </ul>
    {% endif %}
</p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Error Handling

You can use hasErrors to check and see if the form or fields contain any errors, allowing you to:

  • Display a general error message at the top of the form, e.g. {% if form.hasErrors %}
    • To display a general error message at the top of the form triggered by field errors too, use {% if not form.valid %} instead.
  • Display an error message directly for a field, e.g. {% if form.fields.myFieldHandle.hasErrors %}

You can also pair it with the errors object to display more specific error(s).

Here's several examples of how you might implement error handling in your forms:

General Errors

Display a general error message at the top of a form, along with any available general errors:

{% if form.hasErrors %}
    <div class="errors">
        {{ "Error! Please review the form and try submitting again."|t }}
        <ul>
            {% for error in form.errors %}
                <li>{{ error|t }}</li>
            {% endfor %}
        </ul>
    </div>
{% endif %}
1
2
3
4
5
6
7
8
9
10

The code above will only display errors when there are general errors related to the form, like reCAPTCHA failure or Honeypot failure (if errors enabled for it), etc. If you'd like to always display a general error heading if there are field errors and/or form errors, you'll want to swap out if form.hasErrors for not form.valid:

{% if not form.valid %}
    <div class="errors">
        {{ "Error! Please review the form and try submitting again."|t }}
        {% if form.errors|length %}
            <ul>
                {% for error in form.errors %}
                    <li>{{ error|t }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    </div>
{% endif %}
1
2
3
4
5
6
7
8
9
10
11
12

Simple Errors on Fields

Display simple 'required field' errors on fields in Manual forms:




 
 
 


<p>
    <label for="name"{{ form.fields.name.isRequired ? ' class="required"' }}>{{ form.fields.name.label }}</label>
    <input id="name" type="text" name="name" value="{{ form.fields.name.value }}">
    {% if form.fields.name.hasErrors %}
        {{ "This field is required!"|t }}
    {% endif %}
</p>
1
2
3
4
5
6
7

Automation of Errors on Fields

Automate error checking and handling on fields in Manual forms:




 
 
 
 
 
 
 
 


<p>
    <label for="attachment"{{ form.fields.attachment.isRequired ? ' class="required"' }}>{{ form.fields.attachment.label }}</label>
    <input type="file" name="attachment[]" multiple>
    {% if form.fields.attachment.hasErrors %}
        <ul class="errors">
        {# Loop through available Errors #}
            {% for error in form.fields.attachment.errors %}
                <li>{{ error|t }}</li>
            {% endfor %}
        </ul>
    {% endif %}
</p>
1
2
3
4
5
6
7
8
9
10
11
12

Field Errors for Automated Forms

Automate error checking and handling on fields in Automated forms:





 
 
 
 
 
 
 



{% else %}
    <p>
        <label for="{{ field.handle }}"{{ field.isRequired ? ' class="required"' }}>{{ field.label }}</label>
        <input id="{{ field.handle }}" type="{{ field.type }}" name="{{ field.handle }}" value="{{ field.value }}">
        {% if field.errors %}
            <ul class="errors">
                {% for error in field.errors %}
                    <li>{{ error|t }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    </p>
{% endif %}
1
2
3
4
5
6
7
8
9
10
11
12
13

Macro for Errors

Clean up your templates with a macro for handling the errors:

{% macro renderErrors(field) %}
    {% if field.hasErrors and field.errors|length %}
        <ul class="errors">
            {% for error in field.errors %}
                <li>{{ error|t }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endmacro %}
{% import _self as forms %}
1
2
3
4
5
6
7
8
9
10

For use in Manual forms:




 


<div>
    <label for="name"{{ form.fields.name.isRequired ? ' class="required"' }}>{{ form.fields.name.label }}</label>
    <input id="name" type="text" name="name" value="{{ form.fields.name.value }}">
    {{ forms.renderErrors(form.fields.name) }}
</div>
1
2
3
4
5

For use in Automated forms:




 


<div>
    <label for="{{ field.handle }}"{{ field.isRequired ? ' class="required"' }}>{{ field.label }}</label>
    <input id="{{ field.handle }}" type="{{ field.type }}" name="{{ field.handle }}" value="{{ field.value }}">
    {{ forms.renderErrors(field) }}
</div>
1
2
3
4
5

Flash Success Message

You can use form.submittedSuccessfully as a conditional for checking if the form was submitted successfully and display a Flash success message at top of form. The Flash success message will only work when the form is submitted to the same page.

 



 

{% if form.submittedSuccessfully %}
    <div class="success">
        {{ "Form has been submitted successfully!"|t }}
    </div>
{% endif %}
1
2
3
4
5

Spam Protection

When using the built-in Honeypot Spam Protection feature, Express Forms will automatically insert the field inside the form for it to work.

Visit the Spam Protection documentation page to learn more about reCAPTCHA, how the Honeypot field works, etc.

Refreshing Cached Forms

When caching your forms, you'll need to update the CSRF token so that your form continues to work properly. You can do this with the JS example provided below:









 
 
 
 
 
 
 
 

{% set form = craft.expressforms.form("contact") %}

{% cache %}
    {{ form.openTag }}
        {# Form layout code goes here #}
    {{ form.closeTag }}
{% endcache %}

{# Update CSRF token with JS #}
<script>
  // Find the form DOM node
  const form = document.getElementById("{{ form.id }}");
  // Update the CSRF token of the cached form
  const csrfInput = form.querySelector("input[name={{ craft.app.config.general.csrfTokenName }}]");
  csrfInput.value = "{{ craft.app.request.csrfToken }}";
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Examples

To see some complete starter examples, check out the Templating Examples documentation.