Skip to main content

Form Queries

The Form query returns a Form object containing its metadata and field objects. From there you can either render the form using the pre-selected formatting template by calling form.render() or take control of it by iterating over its fields and using form.renderTag and form.renderClosingTag methods.

Freeform will automatically insert javascript in the footer of the page for features such as Spam Protection, Submit disable on click, and other special field types. If you prefer to have this load inside the <form></form> tags, you can adjust the Freeform Javascript Insertion Location setting.

Parameters

The Form query is always constructed the same way. The first assumed parameter should contain the form ID or handle, and the second parameter (optional) should contain an object of overrides, typically used for applying a class globally to specific types of inputs, etc.

{{ craft.freeform.form("FORMHANDLE", {OVERRIDES}) }}

Following the above, your code might look something like this:

{{ craft.freeform.form("myFormHandle", {
labelClass: "form-label",
inputClass: "form-control",
instructionsBelowField: true,
overrideValues: {
hiddenFieldHandle: entry.id,
}
}).render() }}

inputClass

Applies a class name for all input elements.

submitClass

Applies a class name for all submit elements.

rowClass

Applies a class name for all row <div> elements.

columnClass

Applies a class name for all field column <div> elements.

labelClass

Applies a class name for all <label> elements.

errorClass

Applies a class name for all error <ul> elements.

instructionsClass

Applies a class name for all instruction <div> elements.

instructionsBelowField

A boolean value, if set to true, it will render field instructions below the <input> element.

class

Applies a <form> class name.

id

Overrides the <form> ID attribute. If using AJAX and loading the same form more than once in the same template, be sure to specify this parameter for each instance so that AJAX submit can account for it correctly.

returnUrl

Overrides the return URL for the form. You can override the return URL manually with a hidden field or checkbox, etc named formReturnUrl, allowing for a more dynamic return URL dependent on the user's choice or action, as long as you hash the value:

<input type="checkbox" name="formReturnUrl" value="{{ 'whatever/my-url'|hash }}" />

method

Overrides the <form> method attribute. POST by default. This is a parameter that typically should not be touched.

name

Overrides the <form> name attribute.

action

Overrides the <form> action attribute. This is a parameter that typically should not be touched.

status

Overrides the default status of the generated submission. Can be either an ID or handle of an existing Status record.

formattingTemplate

Allows you to override/set a formatting template for a form at template level. When using file-based email notification templates, be sure to include the file extension:

formattingTemplate: 'template-name.html'

suppress

Allows you to suppress email notifications, API integrations and Element Connections for a form by passing an object of suppressions you wish to enable, typically used for editing submissions, e.g.:

suppress: {
adminNotifications: true,
dynamicRecipients: true,
submitterNotifications: true,
api: true
}

You can also just set this to true (suppress: true) to enable all suppressions at once.

relationsPro

This is a powerful feature that allows you to relate the posted submission to any other Craft Element, as long as it contains a Freeform Submissions element field type. This allows you to quickly turn Freeform into a plugin replacement for Comments, Reviews, Ratings, and more!

You can specify as many element ID's to relate to as you wish. All you have to do is specify the Freeform Submissions Field handle and supply an array of element ID's or just a single element ID that you wish to relate submissions for.

relations: {
myFreeformSubmissionsFieldHandle: [55, 106],
myOtherFreeformSubmissionFieldHandle: 251
}

The above would map the posted submission to the Elements with ID's 55, 106 and 251. More specifically, adding the submission to the field with handle myFreeformSubmissionsFieldHandle for elements with ID's 55 and 106 and one to the field with handle myOtherFreeformSubmissionFieldHandle for element with ID 251.

overrideValues

Allows you to override the value inside Text fields, or pre-select a default option for multi-option field types (specify option values in this case). E.g.:

  • hiddenFieldHandle: entry.id - pull in an entry ID from a Craft Entry.
  • stateSelect: "AZ" - pre-select Arizona state in a State select field.
  • availability: ["tue", "thu"] - pre-check Tuesday and Thursday checkbox options in a checkbox group field type.
  • firstName: currentUser.name - pull in the currently logged in user's name into the Name field.
overrideValues: {
firstName: currentUser.name,
myHiddenFieldHandle: entry.id,
anotherField: "something else",
}

Specify the field handle as key, and provide the custom value override as its value. If a Field uses an overrideValue attribute, it will take precedence over the value specified in this attribute.

formAttributes

An object of attributes which will be added to the form.

formAttributes: {
"novalidate": true,
"data-form-id": "test"
}

inputAttributes

An object of attributes which will be added to all input fields.

inputAttributes: {
"readonly": true,
"data-field-id": "test"
}

useRequiredAttribute

Adds required attribute to input fields that have been set to be required in the form builder.

useRequiredAttribute: true

fieldIdPrefix

Adds a prefix value on field outputs. Helpful if you have more than 1 form on the same template and are sharing fields.

fieldIdPrefix: 'myform-'

dynamicNotification

Allows using a dynamic template level notification for a more fine-grained control.

dynamicNotification: {
recipients: ["admin@example.com", "support@example.com"],
template: "test.html"
}
  • Hard code values or pass a value from another element such as an Entry.
  • For Database entry based templates, specify the handle for template.
  • For Twig file based templates, specify the full file name including .html for template.

When using PHP Sessions for form context, this feature may not work properly if the page is cached with something like Varnish, etc. In order for it to work correctly, you would have to refresh the tokens similar to what is described in this guide.

disableRecaptcha

Allows you to disable Captchas such as reCAPTCHA v2 Invisible and v3 per form at template level.

disableRecaptcha: true

disableHoneypot

Allows you to disable the Freeform Honeypot feature per form at template level.

disableHoneypot: true

postForwardingPro

Allows you to override POST Forwarding settings (in form builder) for the form at template level.

postForwarding: {
url: "https://somesite.com/stuff",
triggerPhrase: "success"
}

submissionTokenPro

Provide the submission token from the submission object to switch the form into edit mode and allow editing of submissions on front end.

Custom Form Properties

You can pass custom properties and values into the form payload and then display or perform conditionals on them. For example, in your main template (where showFormTitle is just a custom property):

{{ form.render({ showFormTitle: true }) }}

Then in your formatting template, use:

{% if form.properties.showFormTitle %}
{{ form.name }}
{% endif %}

If displaying the exact same form more than once in a single template, some of the <form> tag attributes set on one form may carry over to other ones. To work around this, you can unset the attribute on the other forms (unless they have their own attributes set). For example, if one form has class: 'something', it may end up applying to other instances of the form, but you can add class: null to those others to work around it.

Usage in Templates

Render the form using its formatting template:

{{ craft.freeform.form("myForm").render() }}

Automated Rendering of Forms

Render the form using its formatting template, but overriding some classes:

{{ craft.freeform.form("myForm", {
labelClass: "form-label",
inputClass: "form-control",
instructionsBelowField: true,
submitClass: "btn btn-success",
overrideValues: {
hiddenFieldHandle: entry.id,
}
}).render() }}

Get the form object and manually iterate through fields:

{% set form = craft.freeform.form("myForm", {
id: "myform",
class: "form-class",
rowClass: "sample-row-class",
submitClass: "button",
}) %}

{{ form.renderTag }}

{% if form.hasErrors %}
<div class="freeform-form-has-errors">
{{ "Error! Please review the form and try submitting again."|t('freeform') }}

{% if form.errors|length %}
<ul>
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}

{% for row in form %}
<div class="{{ form.customAttributes.rowClass }}">
{% for field in row %}
{% set columnClass = "sample-column " ~ form.customAttributes.columnClass %}
{% if field.type == "submit" %}
{% set columnClass = columnClass ~ " submit-column" %}
{% endif %}

<div class="{{ columnClass }}"{{ field.rulesHtmlData }}>
{{ field.render({
class: field.type != "submit" ? "freeform-input" : "",
labelClass: "sample-label" ~ (field.required ? " required" : ""),
errorClass: "sample-errors",
instructionsClass: "sample-instructions",
}) }}
</div>
{% endfor %}
</div>
{% endfor %}


{{ form.renderClosingTag }}

Manual Rendering of Forms

Form formatting can also be extremely manual, if that is something you prefer. Here's an example of different levels of manual you can use:

{% set form = craft.freeform.form("myForm") %}

{{ form.renderTag({returnUrl: "contact/success"}) }}

{% if form.hasErrors %}
<div class="freeform-form-has-errors">
{{ "There was an error submitting this form"|t }}

{% if form.errors|length %}
<ul>
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}

{% set firstName = form.get("firstName") %}
{% set company = form.get("company") %}
{% set lastName = form.get("lastName") %}
{% set mySelect = form.get("mySelect") %}
{% set recipients = form.get("recipients") %}

<label>{{ firstName.label }}</label>
<input name="{{ firstName.handle }}" value="{{ firstName.value }}" />
{{ firstName.renderErrors() }}

<label>{{ lastName.label }}</label>
<input name="{{ lastName.handle }}" value="{{ lastName.value }}" />
{{ lastName.renderErrors() }}

{{ company.renderLabel() }}
{{ company.renderInput() }}
{{ company.renderErrors() }}

<label>Email Address</label>
<input name="email" />
{{ form.get("email").renderErrors() }}

<label>Phone</label>
<input name="phone" />
{% if form.get("phone").hasErrors %}
This field is required!
{% endif %}

<label>My Select Field</label>
<select name="{{ mySelect.handle }}">
{% for option in mySelect.options %}
<option value="{{ option.value }}" {{ option.checked ? "selected" : "" }}>{{ option.label }}</option>
{% endfor %}
</select>

<label>Recipient</label>
<select name="{{ recipients.handle }}">
{% for recipient in recipients.options %}
<option value="{{ loop.index0 }}" {{ recipient.checked ? "selected" : "" }}>{{ recipient.label }}</option>
{% endfor %}
</select>

<button type="submit" data-freeform-action="submit">Submit</button>

{{ form.renderClosingTag }}

When manually building submit buttons in forms, be sure to include a data-freeform-action attribute to it.

The attribute value differs between various types of submit buttons:

  • For the regular submit button which advances forms forward, it's value has to be submit
  • For the back buttons in multi-page forms, the value has to be back
  • For the Save & Continue Later buttons, the value has to be save
<button type="submit" data-freeform-action="submit">Submit</button>
<button type="submit" data-freeform-action="back">Go Back</button>
<button type="submit" data-freeform-action="save">Save & Continue Later</button>

Manual Multi-page Forms

You can manually format multi-page forms as well. The key is to wrap page context with form.currentPage.index conditionals. Here's an example of different levels of manual you can use:

{% set form = craft.freeform.form("myFormHandle") %}

{{ form.renderTag() }}

{# Display any general errors upon submit #}
<div class="form-heading-errors">
{% if form.hasErrors %}
<div class="freeform-form-has-errors">
{{ "There was an error submitting this form"|t }}

{% if form.errors|length %}
<ul>
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}
</div>

{# Set up your needed form page fields #}
{% set firstName = form.get("firstName") %}
{% set lastName = form.get("lastName") %}
{% set company = form.get("company") %}
{% set email = form.get("email") %}
{% set phone = form.get("phone") %}
{% set state = form.get("state") %}
{% set recipients = form.get("recipients") %}

{# Show page tabs for visual reference only - these cannot be linked #}
<ul class="nav nav-tabs">
<li class="nav-item">
<span class="nav-link{% if form.currentPage.index == 0 %} active font-weight-bold{% else %} disabled{% endif %}">Page 1</span>
</li>
<li class="nav-item">
<span class="nav-link{% if form.currentPage.index == 1 %} active font-weight-bold{% else %} disabled{% endif %}">Page 2</span>
</li>
</ul>

{# Show page 1 contents only #}
{% if form.currentPage.index == 0 %}

<div class="form-field">
{# Very manual #}
<label>{{ firstName.label }}</label>
<input name="firstName" value="{{ firstName.value }}" />
{{ firstName.renderErrors() }}
</div>

<div class="form-field">
<label>{{ lastName.label }}</label>
<input name="lastName" value="{{ lastName.value }}" />
{{ lastName.renderErrors() }}
</div>

<div class="form-field">
{# Somewhat manual #}
{{ company.renderLabel() }}
{{ company.renderInput() }}
{{ company.renderErrors() }}
</div>

<button type="submit">Continue</button>

{# Show page 2 contents only #}
{% elseif form.currentPage.index == 1 %}

<div class="form-field">
{# Completely manual #}
<label>Email Address</label>
<input name="email" value="{{ email.value }}" />
{{ form.get("email").renderErrors() }}
</div>

<div class="form-field">
{# Manual errors #}
<label>Phone</label>
<input name="phone" />
{% if form.get("phone").hasErrors %}
This field is required!
{% endif %}
</div>

<div class="form-field">
{# Manual multi-option field #}
<label>State</label>
<select name="state">
{# You may also manually hardcode each option as well, as long as these options exist inside the form builder #}
{% for option in state.options %}
<option value="{{ option.value }}" {{ option.checked ? "selected" : "" }}>{{ option.label }}</option>
{% endfor %}
</select>
</div>

<div class="form-field">
{# Manual Dynamic Recipients field as Select #}
<label>Recipient</label>
<select name="recipients">
{% for recipient in recipients.options %}
{# value is required to be 0, 1, 2, etc instead of actual email value #}
<option value="{{ loop.index0 }}">{{ recipient.label }}</option>
{% endfor %}
</select>
</div>

<button type="submit" name="form_previous_page_button" data-freeform-action="back">Previous</button>
<button type="submit" name="form_page_submit" data-freeform-action="submit">Finish</button>

{% endif %}

{{ form.renderClosingTag }}

When manually building submit buttons in forms, be sure to include a data-freeform-action attribute to it.

The attribute value differs between various types of submit buttons:

  • For the regular submit button which advances forms forward, it's value has to be submit
  • For the back buttons in multi-page forms, the value has to be back
  • For the Save & Continue Later buttons, the value has to be save
<button type="submit" data-freeform-action="submit">Submit</button>
<button type="submit" data-freeform-action="back">Go Back</button>
<button type="submit" data-freeform-action="save">Save & Continue Later</button>

Iterate over Fields instead of Rows

You can also iterate over fields directly with layout.fields instead of Row objects:

{% set form = craft.freeform.form("myForm") %}

{% for field in form.layout.fields %}
<div>{{ field.label }}</div>
{% endfor %}

Supressing Notifications and more

Rendering the form that allows editing a specific submission and suppresses all of the email notifications, api integrations, payments and element connections:

{{ craft.freeform.form("myForm").render({
submissionToken: mySubmissionTokenVariable,
suppress: {
api: true,
connections: true,
adminNotifications: true,
dynamicRecipients: true,
submitterNotifications: true,
payments: true,
webhooks: true,
},
}) }}

Relating Submissions to other Elements

You can feed Element ID(s) however you like, but the below example shows how you might pass off a loaded Entry ID to the Freeform form so that the submission will be related to it.

If you have a Craft Entry with a slug of submitted-contacts that contains a Freeform Submissions Field with a handle of contactFormSubmissions, and you would like to attach each submission to the Craft Entry field, you could do something like the following:

{# Fetch the entry however you wish #}
{% set entry = craft.entries.slug("submitted-contacts").one %}

{# Bind the entry relationship to the form #}
{{ craft.freeform.form("contactForm").render({
relations: {
contactFormSubmissions: entry.id,
},
}) }}