Define form fields programmatically
A layout contains stable field definitions plus a visual reference tree.
$layout = array(
'steps' => array(
array(
'id' => 'contact',
'title' => 'Contact',
'rows' => array(
array(
'id' => 'contact-row',
'columns' => array(
array(
'id' => 'contact-column',
'bootstrap_col_lg' => 12,
'field_ids' => array( 'contact_email', 'message' ),
),
),
),
),
),
),
'fields' => array(
'contact_email' => array(
'id' => 'contact_email',
'name' => 'contact_email',
'type' => 'email',
'label' => 'Email',
'required' => true,
),
'message' => array(
'id' => 'message',
'name' => 'message',
'type' => 'textarea',
'label' => 'How can we help?',
'required' => true,
),
),
);
Use the field types in the public form field reference. Keep IDs/names lowercase and stable. The visible label can change without breaking notification merge tags.
Columns use Bootstrap-style width values. Supply only widths the form needs and preserve a logical source/keyboard order. A visual two-column layout should collapse into the intended reading order on small screens.
Each field ID connects the field definition to a column’s field_ids list and to downstream submission/notification behavior. Define it once, reference it from one intended visual location, and keep the same value after publication. A missing reference leaves a defined field unrendered; a reference to an unknown ID cannot produce a usable input.
Set validation-relevant properties in the field definition rather than relying on a label or placeholder to imply a requirement. Treat labels as translatable visitor copy and IDs/names as stable integration contracts.
Do not accept an arbitrary layout array from a visitor and pass it to the renderer. The owning integration defines the schema; visitor input supplies field values only.