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 Script Insert Location setting.
Parameters
The Form
query is always constructed the same way. The first assumed parameter should contain the form handle or ID, and the second parameter (optional) contains Template Overrides and other available settings, typically used for applying a class globally to specific types of inputs, etc.
Please see the Template Overrides documentation for detailed instructions.
A basic implementation of the Form
query would look like this:
{{ freeform.form("myFormHandle").render() }}
Once Template Overrides have been implemented, your code might look something like this:
{{ freeform.form("myFormHandle", {
attributes: {
novalidate: true,
class: "my-form-class",
},
buttons: {
attributes: {
submit: { class: "form-field-button blue" },
back: { class: "form-field-button gray" },
},
},
fields: {
"@global": {
attributes: {
input: {
class: "form-field-input",
},
label: {
class: "form-field-label",
},
},
},
":required": {
attributes: {
label: { "+class": "form-field-required" },
},
},
":errors": {
attributes: {
input: { "+class": "form-field-is-invalid" },
},
},
"@dropdown": {
attributes: {
container: {
"data-select-container": true,
},
input: {
"+class": "form-field-select fullwidth",
},
},
},
"@checkboxes, @radios" : {
attributes: {
input: {
"+class": "form-field-options",
},
},
},
"myFieldHandle": {
value: entry.id,
},
},
}).render() }}
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:
- Checkbox
- Radios
- Hidden
<label>
<input
type="checkbox"
name="formReturnUrl"
value="{{ 'whatever/my-url'|hash }}"
/>
<span>My Label</span>
</label>
<label>
<input
type="radio"
name="formReturnUrl"
value="{{ 'whatever/my-url/one'|hash }}"
/>
<span>Option One</span>
</label>
<label>
<input
type="radio"
name="formReturnUrl"
value="{{ 'whatever/my-url/two'|hash }}"
/>
<span>Option Two</span>
</label>
<label>
<input
type="radio"
name="formReturnUrl"
value="{{ 'whatever/my-url/three'|hash }}"
/>
<span>Option Three</span>
</label>
<input
type="hidden"
name="formReturnUrl"
value="{{ 'whatever/my-url'|hash }}"
/>
status
Overrides the default status of the generated submission. Can be either a handle
or ID
of an existing Status record.
- Handle
- ID
{{ form.render({
status: "pending",
attributes: {
class: "my-class",
novalidate: true,
},
}) }}
{{ form.render({
status: 1,
attributes: {
class: "my-class",
novalidate: true,
},
}) }}
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.twig'
disable
Revised and renamed in 5.0+
Allows you to disable email notifications, API integrations and Element integrations for a form by passing an object of items you wish to disable, typically used for editing submissions, e.g.:
adminNotifications: true
- disable Admin notificationsconditionalNotifications: true
New in 5.0+ - disable Conditional notificationsuserSelectNotifications: true
Renamed in 5.0+ - disable User Select notificationsemailFieldNotifications: true
Renamed in 5.0+ - disable Email Fields notificationsapi: true
- disable any CRM and Email Marketing integrationselements: true
Renamed in 5.0+ - disable any Element integrationsThis will not disable Element validation, e.g. required fields, username already in use, etc.
payments: true
- disable Payment integrationswebhooks: true
- disable any Webhookscaptchas: true
New in 5.0+ - disable any Captchashoneypots: true
New in 5.0+ - disable the Freeform HoneypotjavascriptTest: true
New in 5.0+ - disable the Freeform Javascript TestsubmitButtons
New in 5.0+ - disable the automatic insertion of Submit buttons
You can also just set this to true
(disable: true
) to disable all items (except submitButtons
) at once.
disable: {
adminNotifications: true,
emailFieldNotifications: true,
api: true,
captchas: true
}
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
.
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: "my-notification-template.twig"
}
- 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.
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.
Template OverridesNew in 5.0+
You can control the attributes, values and more at the template level for forms, fields and buttons. Each one of them is entirely optional. There are several namespaces:
attributes
- whatever you specify here will be set as an attribute on the form.buttons
- controls the output of the Submit button(s).fields
- controls the output of all Fields.captchas
- add attributes to the main Captcha wrapper automatically inserted by Freeform when using reCAPTCHA or hCaptcha.
Please see the Template Overrides documentation for detailed instructions.
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:
{{ freeform.form("myForm").render() }}
Automated Rendering of Forms
Render the form using its formatting template, but overriding some classes:
{{ freeform.form("myForm", {
attributes: {
novalidate: true,
class: "my-form-class",
},
buttons: {
attributes: {
submit: { class: "form-field-button blue" },
back: { class: "form-field-button gray" },
},
},
fields: {
"@global": {
attributes: {
input: {
class: "form-field-input",
},
label: {
class: "form-field-label",
},
},
},
":required": {
attributes: {
label: { "+class": "form-field-required" },
},
},
":errors": {
attributes: {
input: { "+class": "form-field-is-invalid" },
},
},
"@dropdown": {
attributes: {
container: {
"data-select-container": true,
},
input: {
"+class": "form-field-select fullwidth",
},
},
},
"@checkboxes, @radios" : {
attributes: {
input: {
"+class": "form-field-options",
},
},
},
"myFieldHandle": {
value: entry.id,
},
},
}).render() }}
Get the form object and manually iterate through fields:
Manually Iterate
Manually iterate through form fields inside your regular template:
{# Set the form and overrides #}
{% set form = freeform.form("myFormHandle", {
attributes: {
row: { class: "freeform-row" },
success: { class: "freeform-form-success" },
errors: { class: "freeform-form-errors" },
novalidate: true,
},
buttons: {
attributes: {
container: { class: "freeform-button-container" },
column: { class: "freeform-button-column" },
buttonWrapper: { class: "freeform-button-wrapper" },
submit: { class: "freeform-button-submit" },
back: { class: "freeform-button-back" },
save: { class: "freeform-button-save" },
},
},
fields: {
"@global": {
attributes: {
container: { class: "freeform-column" },
input: {
"data-field": true,
class: "freeform-input",
},
label: { class: "freeform-label" },
instructions: { class: "freeform-instructions" },
error: { class: "freeform-errors" },
},
},
":required": {
attributes: {
label: { "+class": "freeform-required" },
},
},
":errors": {
attributes: {
input: { "+class": "is-invalid has-validation" },
},
},
"@group": {
attributes: {
label: { "+class": "group-label" },
},
},
"@signature": {
attributes: {
input: { "-class": "freeform-input" },
},
}
},
}) %}
{# Render the opening form tag #}
{{ form.renderTag() }}
{# Success and error message handling for non-AJAX forms #}
{% if not form.settings.ajax %}
{% if form.submittedSuccessfully %}
<div{{ form.attributes.success|raw }}>
<p>{{ form.settings.successMessage | t('freeform') }}</p>
</div>
{% endif %}
{% if form.hasErrors %}
<div{{ form.attributes.errors|raw }}>
<p>{{ form.settings.errorMessage | t('freeform') }}</p>
{% if form.errors|length %}
<ul>
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}
{% endif %}
{# Render page tabs if multi-page #}
{% if form.pages|length > 1 %}
<ul class="freeform-pages">
{% for page in form.pages %}
<li {% if form.currentPage.index == page.index %}class="active"{% endif %}>
{% if form.currentPage.index == page.index %}
<b>{{ page.label }}</b>
{% else %}
{{ page.label }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{# Build form rows and fields #}
{% macro renderRows(form, rows) %}
{% for row in rows %}
{% set width = (12 / (row|length)) %}
<div{{ form.attributes.row|raw }}>
{% for field in row %}
{% do field.setParameters({
attributes: {
container: { class: [
"freeform-column-" ~ width,
"freeform-fieldtype-" ~ field.type,
]},
},
}) %}
{% if field.type == "group" %}
<div class="freeform-group">
<label{{ field.attributes.label }}>
{{ field.label }}
</label>
<div>
{{ _self.renderRows(form, field.layout) }}
</div>
</div>
{% else %}
{{ field.render }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endmacro %}
{# Display form field rows and columns #}
{{ _self.renderRows(form, form.rows) }}
{# Render the closing form tag #}
{{ 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 = freeform.form("myForm") %}
{{ form.renderTag({
returnUrl: "contact/success"},
disable: ["submitButtons"]
}) }}
{% 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 myDropdown = form.get("myDropdown") %}
<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 Dropdown Field</label>
<select name="{{ myDropdown.handle }}">
{% for option in myDropdown.options %}
<option value="{{ option.value }}" {{ option.value in field.value ? "selected" }}>{{ option.label }}</option>
{% endfor %}
</select>
{# The submit buttons will no longer be automatically inserted by Freeform with `disable: ["submitButtons"]` #}
{# We need to set the `page` variable from the `form.pages.current` in order for this to work #}
{% set page = form.pages.current %}
{# Complete buttons can be rendered with `page.buttons.renderSubmit`, etc, but for maximum control, you can just get the properties attributes only #}
<button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
Submit
</button>
{% if form.pages.current.buttons.back %}
<button {{ page.buttons.backRenderProps }}>
Back
</button>
{% endif %}
{% if form.pages.current.buttons.save %}
<button {{ page.buttons.saveRenderProps }}>
Save
</button>
{% endif %}
{{ form.renderClosingTag }}
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 = freeform.form("myFormHandle") %}
{{ form.renderTag({
disable: ["submitButtons"]
}) }}
{# 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") %}
{# 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>
{# The submit buttons will no longer be automatically inserted by Freeform with `disable: ["submitButtons"]` #}
{# We need to set the `page` variable from the `form.pages.current` in order for this to work #}
{% set page = form.pages.current %}
{# Complete buttons can be rendered with `page.buttons.renderSubmit`, etc, but for maximum control, you can just get the properties attributes only #}
<button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
Submit
</button>
{% if form.pages.current.buttons.back %}
<button {{ page.buttons.backRenderProps }}>
Back
</button>
{% endif %}
{% if form.pages.current.buttons.save %}
<button {{ page.buttons.saveRenderProps }}>
Save
</button>
{% endif %}
{# 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.value in field.value ? "selected" }}>{{ option.label }}</option>
{% endfor %}
</select>
</div>
{# The submit buttons will no longer be automatically inserted by Freeform with `disable: ["submitButtons"]` #}
{# We need to set the `page` variable from the `form.pages.current` in order for this to work #}
{% set page = form.pages.current %}
{# Complete buttons can be rendered with `page.buttons.renderSubmit`, etc, but for maximum control, you can just get the properties attributes only #}
<button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
Submit
</button>
{% if form.pages.current.buttons.back %}
<button {{ page.buttons.backRenderProps }}>
Back
</button>
{% endif %}
{% if form.pages.current.buttons.save %}
<button {{ page.buttons.saveRenderProps }}>
Save
</button>
{% endif %}
{% endif %}
{{ form.renderClosingTag }}
Iterate over Fields instead of Rows
You can also iterate over fields directly with layout.fields
instead of Row objects:
{% set form = freeform.form("myForm") %}
{% for field in form.layout.fields %}
<div>{{ field.label }}</div>
{% endfor %}
Disabling Notifications and more
Rendering the form that allows editing a specific submission and disables all of the email notifications, api integrations, payments and element integrations:
{{ freeform.form("myForm").render({
submissionToken: mySubmissionTokenVariable,
disable: {
api: true,
elements: true,
adminNotifications: true,
emailFieldNotifications: 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 #}
{{ freeform.form("contactForm").render({
relations: {
contactFormSubmissions: entry.id,
},
}) }}