Skip to main content

Form object

The Form object contains its metadata and field objects. You can either render the form using the pre-selected formatting template by calling form.render() or achieve a more fine-grained control over it by iterating over its rows and 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 fieldtypes. If you prefer to have this load inside the <form></form> tags, you can adjust the Script Insert Location Location setting.

​Properties

​name

Outputs the name of the form.

​handle

Outputs the handle of the form.

​id

Outputs the unique ID of the form.

​description

Outputs the description of the form.

​iconNew in 5.17.3+

Returns the configured Craft system icon name, such as envelope, or an empty string when no icon is selected. The saved name remains available on Craft 4, where the icon picker is hidden.

​iconSvgNew in 5.17.3+

Returns Twig-safe SVG markup for the form's icon. Render it directly without |raw. Returns null on Craft 4 or when the icon is blank, invalid, or unavailable. SVGs are resolved only when this property is accessed and cached by icon name across forms.

{% if form.iconSvg %}
<span aria-hidden="true">{{ form.iconSvg }}</span>
{% endif %}
{{ form.name }}

Add dimensions and color styles to suit your layout. Both icon properties are available on forms returned by freeform.form() and freeform.forms(). See Displaying Form Icons for a list example.

​submissionTitleFormat

Outputs the submission title format used when creating new submissions based on this form.

​returnUrl

Outputs the return URL of the form.

​successMessage

Outputs the custom success message configured in the Validation tab for your form inside the form builder.

​errorMessage

Outputs the custom error message configured in the Validation tab for your form inside the form builder.

​hasErrors

A bool variable, which will be true if there are any errors in any of the fields or the form. It might be true even if form.errors list is empty, (e.g. if one or more fields have an error). This can be used in conjunction with the Errors object for displaying special and general errors.

​pages

Returns a list of Page objects each containing its label and index.

​currentPage

Returns the current Page object containing its label and index.

​duplicateRenamed and Revised in 5.0+

A bool variable, which will be true if the form has the Duplicate Check setting enabled (duplicate submission check) and the user has already submitted the form.

In Freeform 5.17+, a form using Scope by Field checks for a duplicate of the current item's value. Populate the scope field before reading this property. See Scoped Duplicate Checking for examples. Email-based checks also need the email value before they can identify a duplicate.

​isAvailable()New in 5.17+

Returns whether the form can be displayed according to its schedule and enabled builder submission limit. Also accessible as form.available. This does not check visitor duplicates. A template-only cap with the builder toggle off does not change this value; use form.isSubmissionLimitReached() for that check.

form.render() handles schedules and enabled builder submission caps automatically. Use the availability check when writing your own layout with form.renderTag() and field helpers. Calling form.render() in the unavailable branch preserves the appropriate message, including intentionally blank messages. See the custom layout example.

​isSubmissionLimitReached()New in 5.17+

Returns whether the effective submission cap is reached, using the builder settings or template overrides. Also accessible as form.submissionLimitReached. Returns false when no limit applies or when editing an existing submission. See Limit Submissions.

​submissionLimitCountNew in 5.17+

Returns the stored submission count for the effective limit period, excluding spam and deleted submissions. Uses the all-time count when no limit applies. Unlike freeform.submissionCount(form), this respects a configured calendar period.

​submissionLimitMessageNew in 5.17+

Returns the translated builder Limit Reached Message, or a submissionLimitMessage template override. Without the builder feature enabled, the default is “Submission limit reached”. This is the configured message, even before the cap is reached; check form.isSubmissionLimitReached() before displaying it manually. Treat it as plain text.

​scheduleMessageNew in 5.17+

Returns the translated Pending Message before the scheduled start or Expired Message after the end. Returns an empty string while available, or when the corresponding message was left blank. Treat it as plain text when rendering it yourself.

​stopSubmissionsAfterNew in 5.11.8+

Returns the form's configured end date and time, or null. In Freeform 5.17+, this is labeled Schedule End Date in the form builder; the property name remains unchanged. Access the start with form.settings.startSubmissionsAt.

Use form.isAvailable() for availability checks so that the schedule toggle and both boundaries are respected. See Form Scheduling for examples and compatibility with older date-only cutoffs.

​isIntegrationEnabledNew in 5.6+

Allows you to check if an integration is enabled for a form. Use the integration handle you created for each integration.

For Singles, the handles are:

  • Honeypot: honeypot
  • Javascript Test: javascript-test
  • Google Tag Manager: gtm
  • Post Forwarding: post-forwarding
<ul>
{% if form.isIntegrationEnabled("mailchimp") %}
<li>Mailchimp is enabled</li>
{% endif %}
{% if form.isIntegrationEnabled("honeypot") %}
<li>Honeypot is enabled</li>
{% endif %}
{% if form.isIntegrationEnabled("recaptchaV3") %}
<li>reCAPTCHA v3 is enabled</li>
{% endif %}
</ul>

​Template OverridesNew in 5.0+

Template overrides let you customize form and field attributes, values, and rendering. All overrides are optional.

OptionPurpose
attributesControls form-tag, row, and banner attributes.
buttonsControls Submit button output.
fieldsControls field values, attributes, and rendering.
captchasAdds attributes to the CAPTCHA wrapper automatically inserted by Freeform.
tagChanges supported HTML tags. Learn more.New in 5.10+

See Template Overrides for syntax and examples.

In Freeform 5.17+, the default AJAX renderer also uses banner attributes and field-error styling from compatible formatting templates. JavaScript overrides remain available for additional customization. See AJAX Forms for details.

Need to pass a custom value to your formatting template, such as whether to display the form title? See Passing a Custom Property to a Formatting Template.

​Usage in Templates

​Simple Render

A basic implementation might look like this:

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

{{ form.render() }}

​Override Classes & Values

Once Template Overrides have been implemented, your code might look something like this:

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

{{ form.render({
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,
},
},
}) }}

​Manually Iterate

Manually iterate through form fields inside your regular template:

{# Set the form #}
{% set form = freeform.form("myFormHandle") %}

{# Render the opening form tag #}
{{ form.renderTag({
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" },
},
}
},
}) }}

{# Success and error message handling for non-AJAX forms #}
{% if not form.settings.ajax %}
{% if form.submittedSuccessfully %}
<div{{ form.attributes.success|raw }}>
<p>{{ form.successMessage | t('freeform') }}</p>
</div>
{% endif %}
{% if form.hasErrors %}
<div{{ form.attributes.errors|raw }}>
<p>{{ form.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 }}

​Success Flash

When not using AJAX, the session success flash message (displays only once) when the form is successfully submitted:

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

{% if form.submittedSuccessfully %}
<div>You've successfully submitted this form!</div>
{% endif %}

{{ form.render }}

​Error when Duplicate

Display a message when the submission is a duplicate:

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

{% if form.duplicate %}
<div class="alert alert-warning duplicate">
You've already submitted this form!
</div>
{% else %}
{{ form.render }}
{% endif %}