Field API Reference
Section: Developer Guide
Topic: Fields
Applies to: Elzo Forms Free and Pro
Scope: PHP field classes, field configuration, admin save normalization, frontend rendering, validation, sanitization, JSON form data
This reference documents the \ElzoForms\Field\Field base class and the data contract used by Elzo Forms field types.
If you are building your first custom field, start with Build a Custom Field. Use this page when you need the complete method map, standard keys, normalization behavior, JSON shape, and hooks.
Field lifecycle
A field usually moves through these stages:
- A raw field configuration array comes from the WordPress admin builder or stored JSON.
- Elzo Forms creates a field object with
\ElzoForms\Field\Field::from()or a concrete field class constructor. - The constructor merges incoming data with
get_defaults(). - If the field has no
id, Elzo Forms generates one. - The field class runs
normalize_configuration(). - Frontend rendering calls
get_data(), then loads a template or falls back torender_html(). - Submission handling calls
validate()andsanitize()for submitted values.
Admin save has one extra step: after generic sanitization, Elzo Forms calls Field::normalize_definition() for each field before saving the form data.
Registering field types
Custom field types are normally registered with elzo_forms_field_types.
add_filter( 'elzo_forms_field_types', function( array $types ): array {
$types['star_rating'] = [
'label' => __( 'Star Rating', 'my-elzo-fields' ),
'class' => \MyElzoFields\Field_Star_Rating::class,
];
return $types;
} );
The field type key is the value stored in JSON as "type". The class should extend \ElzoForms\Field\Field and implement validate() and sanitize().
You can also register a class programmatically with Field::register_field_type( $type, $class_name ), but the filter is the usual integration point because it feeds the admin UI and the factory mapping.
Configuration normalization
Field configuration is the data that defines a field: label, type, options, display settings, conditional logic, and custom settings. It is different from submitted frontend values.
Use normalize_configuration() when a field-specific setting needs a reliable stored shape.
protected function normalize_configuration( array $data ): array {
$max_rating = isset( $data['max_rating'] ) ? absint( $data['max_rating'] ) : 5;
if ( $max_rating < 3 ) {
$max_rating = 3;
}
if ( $max_rating > 10 ) {
$max_rating = 10;
}
$data['max_rating'] = $max_rating;
return $data;
}
Normalization should be:
- idempotent: running it twice should produce the same result;
- side-effect free: no output, database writes, remote requests, or file writes;
- configuration-only: do not use it to sanitize submitted values;
- conservative: preserve unknown keys unless the field explicitly forbids them;
- forgiving: prefer safe fallback values over throwing for malformed configuration.
The built-in Range field uses this contract to normalize min_value, max_value, and default_value. For example, if a two-handle range default is outside the configured bounds, it is clamped before the field definition is stored.
Admin save order
When the admin builder saves a form, field data is processed in this order:
- Template rows are removed from the posted payload.
- Known nested structures are sanitized, including
options,rules,width,content, andallowed_file_types. - Checkbox-like flags are normalized to booleans:
required,logic,primary_field, andsubmission_table_field. Field::normalize_definition()creates the appropriate field class and runsnormalize_configuration().elzo_forms_field_before_savecan adjust the normalized field array.elzo_forms_step_before_savecan adjust the step.- After all fields are collected,
field_keyvalues are generated, slugified, limited to 64 characters, and made unique across the whole form.
Because field_key is finalized after field normalization, custom field classes should not rely on final field-key uniqueness inside normalize_configuration().
Standard configuration keys
| Key | Type | Purpose |
|---|---|---|
id |
int|string | Field identifier. Generated when missing. |
type |
string | Field type key, such as text, select, or a custom type. |
field_key |
string | Machine-readable key for integrations and internal references. Auto-generated and unique per form. |
label |
string | Frontend label. |
placeholder |
string | Input placeholder where the field type supports it. |
admin_label |
string | Admin-only display label. Used before label or placeholder in admin titles. |
under_label |
string | Help text displayed under the label. |
under_field |
string | Help text displayed under the input. |
options |
array | Selectable options for select, checkbox, radio, and similar fields. |
required |
bool | Whether the field must receive a value. Read-only fields may override this. |
default_value |
mixed | Default value used when no submitted or runtime value is present. |
value |
mixed | Runtime value. Usually supplied during rendering or submission handling, not stored as the default configuration. |
step_index |
int | Index of the parent form step. |
index |
int | Index of the field inside its step. |
width |
array | Responsive column width map keyed by breakpoint, for example xs, md, lg. |
custom_id |
string | Custom HTML ID for the input/control. |
custom_class |
string | Additional CSS class for the input/control. |
wrapper_custom_id |
string | Custom HTML ID for the field wrapper. |
wrapper_custom_class |
string | Additional CSS class for the field wrapper. |
logic |
bool | Enables conditional logic for the field. |
rules |
array | Conditional logic groups and rule items. |
primary_field |
bool | Marks the field as useful for identifying a submission. |
submission_table_field |
bool | Shows the field in the admin submissions table. |
Options format
Option-based fields use an array of objects with value, label, and optional description.
[
{
"value": "basic",
"label": "Basic",
"description": "For small forms"
},
{
"value": "pro",
"label": "Pro",
"description": "For advanced workflows"
}
]
In the admin builder, options are entered one per line. The supported shorthand is:
value : Label || Description
Conditional logic format
Conditional logic is stored as groups. Groups are OR conditions; rules inside a group are AND conditions.
{
"logic": true,
"rules": [
[
{
"type": "field",
"operator": "==",
"settings": {
"field_id": "101",
"value": "yes"
}
}
],
[
{
"type": "cookie",
"operator": "exists",
"settings": {
"name": "campaign"
}
}
]
]
}
Field classes can shape the conditional logic UI with get_logic_operators(), get_logic_value_options(), and get_logic_value_source().
Built-in field-specific keys
| Type | Common keys |
|---|---|
text |
subtype, placeholder, default_value, input_prepend, input_append |
textarea |
placeholder, default_value, rows_amount, max_length |
select |
options, placeholder, default_value, multiple, search |
checkbox |
options, default_value, layout, style, min_selections, max_selections |
radio |
options, default_value, layout |
range |
range_type, min_value, max_value, step, default_value |
file |
allowed_file_types, multiple, min_files, max_files, max_file_size |
hidden |
default_value |
content |
content, is_html |
button |
button_type, button_title, button_url, button_target |
Custom field types may add their own keys. Add defaults in get_defaults(), normalize them in normalize_configuration(), and document their expected JSON shape.
Core Field methods
| Method | Visibility | Purpose |
|---|---|---|
from( $field ) |
static public | Factory that creates a typed field instance from an array or numeric ID. Caches instances by field ID. |
get_registered_field_types() |
static protected | Returns the field type to class map from admin utilities and registered filters. |
register_field_type( $type, $class_name ) |
static public | Registers a field class in the local factory map. |
__construct( $field ) |
public | Merges defaults, generates an ID if needed, then normalizes configuration. |
normalize_configuration( $data ) |
protected | Normalizes field configuration from admin payloads or JSON. Override for custom settings. |
normalize_definition( $field ) |
static public | Normalizes a raw field array through its registered field class and returns the array form. |
get_defaults() |
protected | Defines default field configuration. |
generate_id() |
static public | Generates a fallback field ID. |
get( $key, $default ) |
public | Reads a field value. Explicit null is treated like a missing value. |
set( $key, $value ) |
public | Sets a field value and returns the field instance. |
to_array() |
public | Returns all field data as an array. |
get_id(), get_type(), get_label(), get_placeholder(), get_admin_label(), get_field_key() |
public | Basic field accessors. |
get_title(), get_admin_title() |
public | Returns display names with fallbacks. |
is_required(), is_read_only(), allows_empty_submission() |
public | Describe submission behavior. Read-only or special input fields can override these. |
shows_label_wrapper(), shows_under_field() |
public | Controls wrapper display behavior. |
get_logic_rules() |
public | Returns normalized conditional logic rule groups for rendering. |
get_default_value(), get_value() |
public | Returns default or runtime values. Multi-value fields can convert comma-separated defaults to arrays. |
is_multiple() |
protected | Return true for fields that submit arrays. |
get_field_name(), get_field_id(), get_field_class() |
public | Builds frontend input attributes. |
get_wrapper_id(), get_wrapper_class(), get_wrapper_attributes() |
public | Builds frontend wrapper attributes, including conditional logic data. |
get_field_attributes() |
public | Builds common frontend field attributes. |
get_data( $context ) |
public | Builds render data. Override to add field-specific template values. |
get_logic_operators() |
public | Returns allowed conditional logic operators. null means use all operators. |
get_string_logic_operators() |
protected | Helper for option/string-comparison fields. |
get_logic_value_options(), get_logic_value_source() |
public | Controls value selection in the conditional logic admin UI. |
get_admin_field_data() |
public | Returns field data with logic metadata for the admin editor. |
get_column_width_class() |
public | Returns responsive column classes for the field wrapper. |
validate( $value ) |
abstract public | Validates submitted values. Return true or WP_Error. |
sanitize( $value ) |
abstract public | Sanitizes submitted values before storage. |
render( $args ) |
public | Renders the field. Template files take priority over render_html(). |
render_html( $field_data ) |
protected | Fallback inline renderer for custom fields without a template file. |
get_template_path() |
protected | Legacy template path helper kept for backward compatibility. |
render_field_settings( $tab ) |
public | Adds field-specific admin settings and fires the tab-specific settings action. |
sanitize_urlencoded_json( $value ) |
protected | Helper used by text-like fields for URL-encoded JSON payloads. |
The base class also implements magic property access and ArrayAccess for backward compatibility. Prefer explicit methods in new code.
Rendering data keys
get_data() returns common template data such as:
field,field_id,field_index,field_type,step_index;label,placeholder,under_label,under_field;required,logic_rules,default_value,value;name,id,class,wrapper_id,wrapper_class;form_id,form_settings,texts_settings,width_class.
Field classes can add their own render data. For example, Select adds options, multiple, search, value_label, and has_custom_dropdown.
Template resolution
Rendering checks for a template first:
field-types/field-{type}.php
If a template exists, Elzo Forms loads it through \ElzoForms\Utilities\Template_Loader. If no template exists and the field class implements render_html(), that method is used instead.
Use elzo_forms/templates/locate_template when a plugin needs to provide a custom template path.
Hooks and filters
| Hook | Purpose |
|---|---|
elzo_forms_field_types |
Register or alter field type labels and class mappings. |
elzo_forms_admin_field_tabs |
Add or alter field settings tabs in the admin builder. |
elzo_forms_field_settings_{tab} |
Add custom controls to a specific field settings tab. |
elzo_forms_field_before_save |
Filter a normalized field array before it is stored. |
elzo_forms_step_before_save |
Filter a normalized step before it is stored. |
elzo_forms_form_data_before_save |
Filter the full form data array before JSON encoding and storage. |
elzo_forms_admin_field |
Filter field data used in admin displays. |
elzo_forms_field |
Filter field data during frontend step rendering. |
elzo_forms_is_field_value_empty |
Customize empty-value detection during submission handling. |
elzo_forms_validate_submission |
Add submission-level validation before field processing continues. |
elzo_forms/templates/locate_template |
Override template resolution. |
elzo_forms/templates/template_args |
Filter template arguments before extraction. |
elzo_forms/templates/before_template |
Run logic before a template is included. |
elzo_forms/templates/after_template |
Run logic after a template is included. |
elzo_forms_handle_phone_field |
Customize telephone field sanitization. |
For a broader list of hooks outside the Field API, see PHP Hooks and Filters Reference.
JSON field example
A field definition belongs inside a step in the form JSON.
{
"id": 101,
"type": "star_rating",
"field_key": "customer_rating",
"label": "How would you rate your experience?",
"admin_label": "Customer rating",
"required": true,
"max_rating": 5,
"width": {
"xs": "1/1",
"md": "1/2"
},
"logic": true,
"rules": [
[
{
"type": "field",
"operator": "==",
"settings": {
"field_id": "100",
"value": "yes"
}
}
]
]
}
JSON does not register PHP classes. A custom field type must still be registered in PHP before Elzo Forms can create, normalize, render, validate, or sanitize it.
Validation and sanitization
validate() and sanitize() handle submitted frontend values. They are not a replacement for configuration normalization.
| Method | Input | Return |
|---|---|---|
validate( $value ) |
Submitted value | true when valid, or WP_Error when the form should show an error. |
sanitize( $value ) |
Submitted value | Cleaned value to store with the submission. |
normalize_configuration( $data ) |
Field definition/configuration | Normalized field definition/configuration array. |
Best practices
- Use unique field type keys and prefix custom classes with your plugin namespace.
- Add every custom configuration key to
get_defaults(). - Normalize custom configuration in
normalize_configuration(). - Validate submitted values with
validate()and return actionableWP_Errormessages. - Sanitize submitted values with
sanitize()before storage. - Escape all HTML output in templates or
render_html(). - Use
$field_data['name']and$field_data['id']when rendering inputs. - Use
is_multiple()for fields that submit arrays. - Use
allows_empty_submission()for controls that may be absent from$_POSTwhen empty. - Do not store secrets in field configuration.