Elzo Forms renders its front-end markup from template files, and a theme can replace any of them by placing a file with the same name in an elzo-forms folder. Use a template override when a hook or CSS cannot produce the markup you need.
Before you begin
- Put overrides in a child theme, or in a theme you control. Overrides placed in a parent theme you did not write are lost when that theme updates.
- Do not edit files inside the Elzo Forms plugin folder. Plugin updates replace them.
How overrides are located
When Elzo Forms loads a front-end template, it looks for the file in this order and uses the first one it finds:
wp-content/themes/your-child-theme/elzo-forms/TEMPLATEwp-content/themes/your-parent-theme/elzo-forms/TEMPLATE, checked only when a child theme is active- the plugin’s own
templates/TEMPLATE
Because the plugin copy is the final fallback, you only need to copy the templates you actually intend to change.
Override a template
- Create an
elzo-formsfolder in your theme. - Copy the template you want to change out of the plugin’s
templatesfolder, keeping its filename and any subfolder. - Edit your copy.
- Load a page containing a form and confirm your version is being used.
For example, to change the markup around a single field:
wp-content/plugins/elzo-forms/templates/field.php
→ wp-content/themes/your-child-theme/elzo-forms/field.php
To change how one field type renders, keep the field-types subfolder:
wp-content/themes/your-child-theme/elzo-forms/field-types/field-select.php
Templates you can override
| Template | Renders |
|---|---|
form.php |
The form wrapper, the <form> element, hidden inputs, and the steps container. |
step.php |
One step, including its fields. |
field.php |
The wrapper around a single field: label, required marker, and helper text. |
field-types/field-text.php |
The Text field control, for every Text variant. |
field-types/field-textarea.php |
The Textarea field control. |
field-types/field-select.php |
The Select field control. |
field-types/field-checkbox.php |
The Checkbox field control. |
field-types/field-radio.php |
The Radio field control. |
field-types/field-range.php |
The Range field control. |
field-types/field-file.php |
The File Upload field control. |
field-types/field-hidden.php |
The Hidden field control. |
field-types/field-content.php |
The Content field. |
field-types/field-button.php |
The Button field. |
A field type template is chosen by the base type, so an Email or Date field — a Text variant — renders through field-text.php, which receives the variant as $field_subtype. These templates are the same in Elzo Forms Free and Elzo Forms PRO.
Templates that cannot be overridden
Admin templates are always loaded from the plugin, so nothing in the form builder can be replaced this way. The submission notification template, templates/email/admin-email.php, is also loaded directly rather than through the template loader; customize submission emails with the email filters instead. See Email Customization Hooks.
Variables available in a template
Each template receives its data as ordinary local variables. The docblock at the top of every plugin template lists what that template gets, which is the reliable place to check after an update.
form.php receives $form_id, $form_post, $form_object, $form_settings, $texts_settings, $style_settings, $steps, $steps_total, $form_wrapper_styles, $form_attr_id, $form_attr_class, $form_data_attrs_string, $form_instance_suffix, and $atts.
step.php receives $step, $step_index, $steps_total, $form_id, $form_settings, $texts_settings, and $form_instance_suffix.
field.php and the field type templates receive the field’s prepared data, including $field, $field_data, $field_type, $form_id, $step_index, $id, $label, $required, $under_label, $under_field, $shows_label_wrapper, $shows_under_field, $form_settings, $texts_settings, and $form_instance_suffix.
Repeated forms and $form_instance_suffix
The same form can be rendered more than once on a page. $form_instance_suffix is empty for the first copy and holds -2, -3 and so on for later copies. Elzo Forms appends it to every ID it generates — the form, each field and wrapper, the nonce input — so the IDs stay unique and labels point at the input of their own copy. The $id a field template receives already includes it.
An override that builds IDs of its own should append the suffix too, and an override of form.php or step.php has to pass it on to the next template, as the plugin templates do.
Keep the parts the form depends on
Templates produce markup that the plugin’s own JavaScript and submission handler rely on. When you edit a copy, keep the following intact:
- the nonce input and the hidden
action,elzo_form_id,elzo_form_time, andelzo_form_settingsinputs inform.php; - the form’s
idandclassattributes, and the data attributes produced by$form_data_attrs_string; - the field wrapper attributes returned by
$field->get_wrapper_attributes( $form_instance_suffix ), which carry the state used by conditional logic; - each input’s
nameattribute; $form_instance_suffix, passed fromform.phptostep.phpand on to each field.
Warning: removing the nonce input, the hidden inputs, or an input’s name attribute causes submissions to be rejected or saved without that field’s value.
Also escape output in your copy exactly as the original does. Templates render visitor-supplied values, and dropping esc_html() or esc_attr() introduces a cross-site scripting vulnerability.
Filters
Five filters and actions adjust how templates are located and loaded. They apply to front-end templates only; admin templates bypass all of them.
elzo_forms/templates/theme_dir
Changes the folder name searched inside the theme. The default is elzo-forms.
add_filter( 'elzo_forms/templates/theme_dir', function () {
return 'my-theme-forms';
} );
Register it anywhere that runs before a form is rendered, including a theme’s functions.php.
Most sites do not need this filter. Use the default elzo-forms folder unless the name conflicts with something already in your theme.
elzo_forms/templates/locate_template
Overrides the resolved template path. Receives the theme template path found so far (an empty string when none was found), the template name, the template path, and the template arguments. Return an absolute path to a readable file, or an empty string to fall through to the plugin template.
add_filter( 'elzo_forms/templates/locate_template', function ( $template, $template_name, $template_path, $args ) {
if ( 'field.php' === $template_name && ! empty( $args['field_type'] ) && 'file' === $args['field_type'] ) {
$custom = plugin_dir_path( __FILE__ ) . 'templates/field-upload.php';
if ( file_exists( $custom ) ) {
return $custom;
}
}
return $template;
}, 10, 4 );
elzo_forms/templates/template_args
Filters the variables passed into a template before they are extracted. Receives the arguments array, the template name, and the resolved template path. Return the array.
add_filter( 'elzo_forms/templates/template_args', function ( $args, $template_name, $template ) {
if ( 'field.php' === $template_name ) {
$args['under_field'] = '';
}
return $args;
}, 10, 3 );
elzo_forms/templates/before_template and elzo_forms/templates/after_template
Actions that run immediately before and after a template is included. Both receive the template name, the template path, the resolved file path, and the arguments. Use them to output markup around a template without copying it.
add_action( 'elzo_forms/templates/before_template', function ( $template_name, $template_path, $template, $args ) {
if ( 'form.php' === $template_name ) {
echo '<div class="my-form-frame">';
}
}, 10, 4 );
add_action( 'elzo_forms/templates/after_template', function ( $template_name, $template_path, $template, $args ) {
if ( 'form.php' === $template_name ) {
echo '</div>';
}
}, 10, 4 );
Maintaining an override
An override pins your copy of the markup at the version you copied it from. When Elzo Forms updates a template, your copy keeps rendering the old markup, and a change the plugin relies on may be missing from it.
Each plugin template carries a @version in its docblock. After a plugin update, compare that version with the one in your copy and merge any changes.
Version 1.1.0 changed six templates: form.php, step.php, field.php, field-types/field-checkbox.php, field-types/field-radio.php and field-types/field-file.php. Copies made from 1.0.0 keep working, but miss the following:
form.php,step.phpandfield.phppass$form_instance_suffixalong. Without it, a second copy of the same form on a page repeats the IDs of the first.field.phprenders the label of a Checkbox or Radio field as an element with the ID{$id}-labelinstead of a<label>, andfield-checkbox.phpandfield-radio.phpmark their option lists as a group labelled by it, so screen readers announce the question with the options.field-file.phpgives the remove button an accessible name and exposes the upload progress bar to screen readers.
Because of this maintenance cost, prefer the smallest change that works: CSS first, then the template filters above or a field type filter, and a full template copy last.
Next steps
- Field Type Filters — change field rendering without copying a template.
- Build a Custom Field — add a new field type with its own markup.
- Rendering Forms with Shortcodes and PHP — the block, the shortcode, and repeated forms on one page.
- PHP Hooks and Filters Reference — the full list of extension points.