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.:
adminNotifications: true
- suppress Admin notificationsdynamicRecipients: true
- suppress Dynamic Recipient notificationssubmitterNotifications: true
- suppress Submitter email notificationsapi: true
- suppress CRM and Email Marketing integrationsconnections: true
- suppress creation of new elements via Element Connections feature (will not disable Element validation, e.g. required fields, username already in use, etc).payments: true
- suppress Payment integrationswebhooks: true
- suppress any Webhooks for the form
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.
relations
Pro
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
postForwarding
Pro
Allows you to override POST Forwarding settings (in form builder) for the form at template level.
postForwarding: {
url: "https://somesite.com/stuff",
triggerPhrase: "success"
}
submissionToken
Pro
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,
},
}) }}