Skip to main content

TableProImproved in 5.8+, 5.14+, 5.15+

A field that allows you to collect and handle repeating data. The field.type property value is table (for conditionals).

Table Layout slideout with Item and Quantity columns, numeric column settings, and Finish and Close and Cancel actions

​Settings

  • Limit Rows - set limits on the number of rows, including minimum, maximum, exact number, or range.New in 5.14.12+
    • Minimum number of rows - set the minimum number of rows. At least this many rows will be shown by default; rows cannot be removed below this count.
    • Maximum number of rows - set the maximum number of rows that can be added to the table.
    • Exact number of rows - set the exact number of rows. This many rows will be shown with no option to add or remove.
  • Use the Table Layout area to create columns for the table. In Freeform 5.17+, open its preview to configure columns in a content editor slideout, with Finish & Close and Cancel controls. Options are:
    • Text
    • TextareaNew in 5.8+
    • NumberNew in 5.15.27+
    • Checkbox
    • DropdownImproved in 5.8+
    • RadiosNew in 5.8+
    • File UploadNew in 5.15+
  • Enable Use built-in JavaScript for adding and removing rows to let visitors add or remove rows within the configured limits.

​Styling & Customization

​Choice ControlsImproved in 5.17.3+

In bundled styled formatting templates, radio options appear horizontally within each Table cell and wrap when needed. Radio inputs and their labels are vertically aligned, and checkbox positioning is consistent with the surrounding controls.

For custom template copies, bring across the updated Table markup and supporting CSS from the matching bundled sample to receive these changes.

​Manual Rendering

For manual rendering of the Table field inside your form for full customizability, your code might look something like this:

<table class="table">
<thead>
<tr>
{% for column in field.tableLayout %}
<th>{{ column.label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% if field.value is empty %}
<tr>
{% for index, column in field.tableLayout %}
{% set colName = field.handle ~ "[0][" ~ index ~ "]" %}
<td>
{% switch column.type|default("text") %}
{% case "dropdown" %}
<select name="{{ colName }}">
{% for option in column.value|default("")|split(";") %}
<option value="{{ option }}">{{ option }}</option>
{% endfor %}
</select>
{% case "checkbox" %}
<input type="checkbox" name="{{ colName }}" value="{{ column.value|default("Yes") }}" />
{% default %}
<input type="text" name="{{ colName }}" value="{{ column.value|default("") }}" />
{% endswitch %}
</td>
{% endfor %}
</tr>
{% else %}
{% for rowIndex, row in field.value %}
<tr>
{% for index, column in field.tableLayout %}
{% set colName = field.handle ~ "[" ~ rowIndex ~ "][" ~ index ~ "]" %}
{% set colValue = row[index]|default("") %}
<td>
{% switch column.type|default("text") %}
{% case "dropdown" %}
<select name="{{ colName }}">
{% for option in colValue|default("")|split(";") %}
<option value="{{ option }}"{{ option == colValue ? " selected" }}>{{ option }}</option>
{% endfor %}
</select>
{% case "checkbox" %}
<input type="checkbox" name="{{ colName }}" value="{{ column.value|default("Yes") }}" {{ colValue ? "checked" }} />
{% default %}
<input type="text" name="{{ colName }}" value="{{ colValue }}" />
{% endswitch %}
</td>
{% endfor %}
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>

​Templating

The Table field type includes various parameter options to override labels and finer control of classes and attributes on each part of the Table field.

Here's how you can use field.render for an automated approach to render your Table field inside your formatting templates:

{{ field.render({
addButtonLabel: "Add +",
removeButtonLabel: "X",
tableAttributes: {
table: { class: "table table-sm table-borderless" },
input: { class: "form-control" },
textarea: { class: "form-control" },
dropdown: { class: "form-control form-select" },
radios: { class: "form-check-input" },
checkbox: { class: "form-check-input" },
fileUpload: { class: "form-control" },
removeButton: { class: "btn btn-sm btn-danger" },
addButton: { class: "btn btn-sm btn-success" },
},
}) }}

​Examples

​Submission Data

Here's how to render a Table field's data inside the Submission object:

<table class="table">
<thead>
<tr>
{% for column in field.tableLayout %}
<th>{{ column.label|default("-") }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in submission[field.handle].value %}
<tr>
{% for index, column in field.tableLayout %}
<td>
{% set cell = row[index] ?? null %}
{% if cell is iterable %}
{{ cell|join(', ') }}
{% else %}
{{ cell }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

​Email Notification Template

By default, Table field data will be rendered as comma separated values in email notifications, which is not ideal. To account for Table fields in your email notification template, you can use the template code below:

<p>The following submission came in on {{ dateCreated|date('l, F j, Y \\a\\t g:ia') }}.</p>
<p>Here are the details:</p>
<ul>
{% for field in allFields %}
<li>
{{ field.label }}:
{% if field.implements('options') %}
{{ field.labelsAsString }}
{% elseif field.type == "table" %}
<table>
<thead>
<tr>
{% for column in field.tableLayout %}
<th>{{ column.label|default("-") }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in field.value %}
<tr>
{% for index, column in field.tableLayout %}
<td>
{% set cell = row[index] ?? null %}
{% if cell is iterable %}
{{ cell|join(', ') }}
{% else %}
{{ cell }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
{{ field.valueAsString }}
{% endif %}
</li>
{% endfor %}
</ul>

​Resources