fluentform-custom-fields
Builds and reviews third-party Fluent Forms input fields with the Free-core BaseFieldManager API. Covers fluentform/loaded bootstrap, editor component schema, frontend rendering, input-name mapping, conditional logic, server-side normalization and validation, response formatting,
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/fluentform/fluentform-custom-fields
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
git clone https://github.com/Lonsdale201/wp-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Fluent Forms custom fields
Build fields against the documented Free-core BaseFieldManager contract. Do
not copy a Pro component and accidentally make the extension depend on Pro.
Read field-contract.md when implementing a new field or debugging nested values, response rendering, editor settings, or Pro feature detection.
Availability contract
| Surface | Availability in 6.2.7 |
|---|---|
FluentForm\App\Services\FormBuilder\BaseFieldManager |
Free |
| Editor registration, frontend render hook, parser input type, conditional support | Free |
| Element input/validation/response filters | Free |
| Phone, range slider, NPS, ranking, dynamic field, chained select, repeater, rich text, file upload | Pro implementations |
The Pro fields demonstrate the same Free base class. Referencing their element keys, JavaScript, uploader, data-source, or server classes is still Pro-only.
Workflow
- Inspect the installed versions and feature-detect every class or constant used.
- Choose a globally unique, lowercase element key and a configurable input
attributes.name; never use the element key as a permanent business ID. - Bootstrap on
fluentform/loadedand instantiate the field once. - Return a complete editor component with
element,attributes,settings, andeditor_options. - Render with the inherited markup helpers so labels, error placement, conditional logic, repeated form instances, and accessibility remain intact.
- Normalize before rule validation, validate on the server, then add a separate display formatter for entries/emails.
- Test editor insertion, saved/reloaded configuration, classic and conversational rendering, valid/invalid submission, conditional visibility, entry display, email/feed value, and two instances of the same form.
Bootstrap and field skeleton
use FluentForm\App\Services\FormBuilder\BaseFieldManager;
use FluentForm\Framework\Helpers\ArrayHelper as Arr;
add_action('fluentform/loaded', static function (): void {
if (!class_exists(BaseFieldManager::class)) {
return;
}
new Acme_Order_Code_Field();
});
final class Acme_Order_Code_Field extends BaseFieldManager
{
public function __construct()
{
parent::__construct(
'acme_order_code',
__('Order code', 'acme-addon'),
['order', 'reference', 'code'],
'advanced'
);
}
public function getComponent()
{
return [
'index' => 20,
'element' => $this->key,
'attributes' => [
'type' => 'text',
'name' => 'acme_order_code',
'value' => '',
'class' => '',
'placeholder' => '',
],
'settings' => [
'label' => __('Order code', 'acme-addon'),
'admin_field_label' => '',
'label_placement' => '',
'help_message' => '',
'container_class' => '',
'validation_rules' => [
'required' => [
'value' => false,
'message' => __('This field is required.', 'acme-addon'),
],
],
'conditional_logics' => [],
],
'editor_options' => [
'title' => __('Order code', 'acme-addon'),
'icon_class' => 'ff-edit-text',
'template' => 'inputText',
],
];
}
public function render($data, $form)
{
$data['attributes']['id'] = $this->makeElementId($data, $form);
$data['attributes']['class'] = trim(
'ff-el-form-control ' . Arr::get($data, 'attributes.class', '')
);
$input = '<input ' . $this->buildAttributes($data['attributes'], $form) . '>';
$html = $this->buildElementMarkup($input, $data, $form);
$this->printContent(
'fluentform/rendering_field_html_' . $this->key,
$html,
$data,
$form
);
}
}
Keep render() output escaped. The inherited helpers escape attributes and
produce Fluent Forms-compatible wrappers; they do not make arbitrary custom HTML
or JavaScript safe.
Normalize and validate
add_filter(
'fluentform/input_data_acme_order_code',
static fn($value) => is_string($value) ? strtoupper(trim($value)) : $value,
10,
1
);
add_filter(
'fluentform/validate_input_item_acme_order_code',
static function ($error, $field, $formData, $fields, $form, $errors = []) {
$name = (string) ($field['name'] ?? '');
$value = (string) ($formData[$name] ?? '');
if ($value !== '' && !preg_match('/^[A-Z0-9-]{6,32}$/', $value)) {
$error = is_array($error) ? $error : ($error ? [$error] : []);
$error['acme_format'] = __('Use 6–32 letters, numbers, or dashes.', 'acme-addon');
}
return $error;
},
10,
6
);
Scope form-specific rules with (int) $form->id. Never rely only on browser
validation. Do not add raw request keys to fluentform/white_listed_fields merely
to make a field persist; a registered input type and a valid attributes.name
are the correct path.
Response formatting
Use response formatting only for display. Preserve the stored machine value.
add_filter(
'fluentform/response_render_acme_order_code',
static function ($response, $field, $formId, $isHtml) {
$value = (string) $response;
return $isHtml ? esc_html($value) : $value;
},
10,
4
);
Critical rules
- Treat
attributes.nameas the key connecting browser data,$formData, the submissionresponseJSON, entry details, smart codes, and feed mappings. - Keep filters pure and deterministic; rendering and formatting can occur more than once in a request.
- Register scripts/styles only when the target form contains the field and use unique handles. Do not enqueue Pro assets from a Free-only addon.
- Support arrays intentionally. A scalar-looking renderer or validator is not automatically safe for repeaters, containers, or multi-value inputs.
- Do not use the unrelated
FluentForm\App\Modules\Component\BaseComponentAPI as the default extension path;BaseFieldManageris the documented and core/Pro-used field manager in 6.2.7.
Cross-references
- Use
fluentform-submission-lifecyclefor hook timing and transformations. - Use
fluentform-entries-datafor stored response and entry-detail semantics. - Use
wp-security-auditwhen the custom renderer outputs complex HTML.
References
- Official Base Field Manager documentation: https://developers.fluentforms.com/api/classes/base-field-manager/
- Verified Free source paths:
fluentform/app/Services/FormBuilder/BaseFieldManager.phpfluentform/app/Services/FormBuilder/Components/BaseComponent.phpfluentform/app/Services/Parser/Form.phpfluentform/app/Services/Form/FormValidationService.php
- Verified Pro examples, required only when their features are used:
fluentformpro/src/Components/RangeSliderField.phpfluentformpro/src/Components/DynamicField/DynamicField.phpfluentformpro/src/Components/RepeaterField.php
Files (wp-agent-skills)
-
agents
-
openai.yaml 306 B
interface: display_name: "Fluent Forms custom fields" short_description: "Build Free-compatible Fluent Forms field extensions" default_prompt: "Use this skill to implement or review a Fluent Forms custom field, including editor schema, rendering, validation, stored values, and Free/Pro boundaries."
-
-
references
-
field-contract.md 5.2 KB
# Fluent Forms field contract reference Read this file while implementing or debugging a custom field. The contract was verified against Fluent Forms Free and Pro 6.2.7. ## Registration effects Constructing `BaseFieldManager` immediately calls `register()` and attaches: | Hook | Purpose | |---|---| | `fluentform/editor_components` | Adds the component to its editor group. | | `fluentform/editor_element_settings_placement` | Chooses general/advanced editor controls. | | `fluentform/editor_element_search_tags` | Adds builder search terms. | | `fluentform/render_item_{key}` | Renders the frontend field. | | `fluentform/form_input_types` | Makes the parser treat the element as an input. | | `fluentform/editor_element_customization_settings` | Adds custom editor controls. | | `fluentform/supported_conditional_fields` | Makes it available to conditional rules. | Instantiating twice duplicates hook callbacks and often duplicates the component. Own registration in one bootstrap class or guard it with a static flag. ## Minimum component shape `getComponent()` should return: - `index`: ordering hint. - `element`: exact manager key. - `attributes.name`: persisted field key; the admin may later rename it. - `attributes.type`, `value`, `class`, and relevant HTML attributes. - `settings.label`, `admin_field_label`, `label_placement`, `help_message`, `container_class`, `validation_rules`, and `conditional_logics`. - `editor_options.title`, `icon_class`, and a supported `template`. Editor control names returned by `getGeneralEditorElements()` and `getAdvancedEditorElements()` must correspond to keys in `attributes` or `settings`. Add non-standard controls through `generalEditorElement()`, `advancedEditorElement()`, and `getEditorCustomizationSettings()` only after verifying the current editor component schema. ## Input-to-entry mapping The main path is: ```text component attributes.name -> serialized browser key -> FormFieldsParser input map -> recursive Fluent Forms sanitization -> accepted $formData key -> fluentform_submissions.response JSON -> fluentform_entry_details.field_name projection -> response_render_{element} for human display ``` Unknown browser keys are removed before validation. `fluentform/white_listed_fields` is intended for protocol/control keys such as CAPTCHA and payment tokens; those keys are excluded from entry details. Do not use it to bypass field registration. For nested fields, parser keys may contain bracket notation. `getEntryInputs()` removes child keys containing `[` so the top-level field owns entry display. Test the exact stored JSON and detail rows instead of assuming a scalar contract. ## Hook signatures ```php // Normalize a present field before validation rules run. apply_filters( 'fluentform/input_data_{element}', $value, $field, $formData, $form ); // Add field-specific errors after the general validator has run. apply_filters( 'fluentform/validate_input_item_{element}', $error, $field, $formData, $fields, $form, $errors ); // Format a stored response for entries, emails, and other display contexts. apply_filters( 'fluentform/response_render_{element}', $response, $field, $formId, $isHtml ); ``` An input filter runs only when the key exists in accepted `$formData`. A validation filter runs for every parsed field, so handle missing optional values. Return an array of error messages or the existing error value. Do not return an array containing an already-array value. ## Rendering checklist - Generate a per-form-instance ID with `makeElementId()`. - Use `buildAttributes()` and `buildElementMarkup()` for normal inputs. - Include `ff-el-form-control` where the stock frontend expects it. - Keep label association, `aria-invalid`, required state, help text, and Fluent Forms error placement working. - Preserve conditional wrapper classes by using `buildElementMarkup()`. - Escape values by output context. `buildAttributes()` covers attributes passed to it; it does not sanitize custom HTML assembled elsewhere. - Do not use a random ID or mutable label as the submission key. ## Free and Pro boundary The following field implementations were verified in Pro 6.2.7 and must be feature-detected before use: - `FluentFormPro\Components\PhoneField` - `FluentFormPro\Components\RangeSliderField` - `FluentFormPro\Components\DynamicField\DynamicField` - `FluentFormPro\Components\ChainedSelect\ChainedSelect` - `FluentFormPro\Components\RepeaterField` and `RepeaterContainer` - rich-text/post fields, uploaders, ranking/NPS, color picker, and save progress Do not check only `defined('FLUENTFORMPRO')`; also check the exact class or method needed. This survives incomplete activation and version skew more safely. ## Test matrix 1. Free active, Pro inactive: addon boots and Free-only field works. 2. Free and Pro 6.2.7 active: no duplicate keys or assets. 3. Add field, rename input, save, reload editor. 4. Render two copies of the same form: IDs remain unique. 5. Submit missing, invalid, valid, zero-like (`"0"`), and array-shaped values. 6. Hide/show through conditional logic and confirm hidden-field behavior. 7. Inspect raw `response` JSON, entry details, admin entry, email, export, and feed. 8. Run classic and conversational forms if the addon claims both.
-
-
SKILL.md 8.2 KB
--- name: fluentform-custom-fields description: >- Builds and reviews third-party Fluent Forms input fields with the Free-core BaseFieldManager API. Covers fluentform/loaded bootstrap, editor component schema, frontend rendering, input-name mapping, conditional logic, server-side normalization and validation, response formatting, accessibility, assets, and Free versus Pro feature boundaries. Use when code extends BaseFieldManager, adds fluentform/render_item_* or fluentform/validate_input_item_* hooks, creates a custom field for the form builder, or must make a field appear correctly in entries, emails, feeds, and conditional rules. metadata: wp-skills-author: "Soczó Kristóf" wp-skills-contact: "mailto:lonsdale201@hotmail.com" wp-skills-plugin: "fluentform" wp-skills-plugin-version-tested: "6.2.7" wp-skills-wp-version-tested: "7.0.2" wp-skills-php-min: "7.4" wp-skills-last-updated: "2026-07-20" --- # Fluent Forms custom fields Build fields against the documented Free-core `BaseFieldManager` contract. Do not copy a Pro component and accidentally make the extension depend on Pro. Read [field-contract.md](references/field-contract.md) when implementing a new field or debugging nested values, response rendering, editor settings, or Pro feature detection. ## Availability contract | Surface | Availability in 6.2.7 | |---|---| | `FluentForm\App\Services\FormBuilder\BaseFieldManager` | Free | | Editor registration, frontend render hook, parser input type, conditional support | Free | | Element input/validation/response filters | Free | | Phone, range slider, NPS, ranking, dynamic field, chained select, repeater, rich text, file upload | Pro implementations | The Pro fields demonstrate the same Free base class. Referencing their element keys, JavaScript, uploader, data-source, or server classes is still Pro-only. ## Workflow 1. Inspect the installed versions and feature-detect every class or constant used. 2. Choose a globally unique, lowercase element key and a configurable input `attributes.name`; never use the element key as a permanent business ID. 3. Bootstrap on `fluentform/loaded` and instantiate the field once. 4. Return a complete editor component with `element`, `attributes`, `settings`, and `editor_options`. 5. Render with the inherited markup helpers so labels, error placement, conditional logic, repeated form instances, and accessibility remain intact. 6. Normalize before rule validation, validate on the server, then add a separate display formatter for entries/emails. 7. Test editor insertion, saved/reloaded configuration, classic and conversational rendering, valid/invalid submission, conditional visibility, entry display, email/feed value, and two instances of the same form. ## Bootstrap and field skeleton ```php use FluentForm\App\Services\FormBuilder\BaseFieldManager; use FluentForm\Framework\Helpers\ArrayHelper as Arr; add_action('fluentform/loaded', static function (): void { if (!class_exists(BaseFieldManager::class)) { return; } new Acme_Order_Code_Field(); }); final class Acme_Order_Code_Field extends BaseFieldManager { public function __construct() { parent::__construct( 'acme_order_code', __('Order code', 'acme-addon'), ['order', 'reference', 'code'], 'advanced' ); } public function getComponent() { return [ 'index' => 20, 'element' => $this->key, 'attributes' => [ 'type' => 'text', 'name' => 'acme_order_code', 'value' => '', 'class' => '', 'placeholder' => '', ], 'settings' => [ 'label' => __('Order code', 'acme-addon'), 'admin_field_label' => '', 'label_placement' => '', 'help_message' => '', 'container_class' => '', 'validation_rules' => [ 'required' => [ 'value' => false, 'message' => __('This field is required.', 'acme-addon'), ], ], 'conditional_logics' => [], ], 'editor_options' => [ 'title' => __('Order code', 'acme-addon'), 'icon_class' => 'ff-edit-text', 'template' => 'inputText', ], ]; } public function render($data, $form) { $data['attributes']['id'] = $this->makeElementId($data, $form); $data['attributes']['class'] = trim( 'ff-el-form-control ' . Arr::get($data, 'attributes.class', '') ); $input = '<input ' . $this->buildAttributes($data['attributes'], $form) . '>'; $html = $this->buildElementMarkup($input, $data, $form); $this->printContent( 'fluentform/rendering_field_html_' . $this->key, $html, $data, $form ); } } ``` Keep `render()` output escaped. The inherited helpers escape attributes and produce Fluent Forms-compatible wrappers; they do not make arbitrary custom HTML or JavaScript safe. ## Normalize and validate ```php add_filter( 'fluentform/input_data_acme_order_code', static fn($value) => is_string($value) ? strtoupper(trim($value)) : $value, 10, 1 ); add_filter( 'fluentform/validate_input_item_acme_order_code', static function ($error, $field, $formData, $fields, $form, $errors = []) { $name = (string) ($field['name'] ?? ''); $value = (string) ($formData[$name] ?? ''); if ($value !== '' && !preg_match('/^[A-Z0-9-]{6,32}$/', $value)) { $error = is_array($error) ? $error : ($error ? [$error] : []); $error['acme_format'] = __('Use 6–32 letters, numbers, or dashes.', 'acme-addon'); } return $error; }, 10, 6 ); ``` Scope form-specific rules with `(int) $form->id`. Never rely only on browser validation. Do not add raw request keys to `fluentform/white_listed_fields` merely to make a field persist; a registered input type and a valid `attributes.name` are the correct path. ## Response formatting Use response formatting only for display. Preserve the stored machine value. ```php add_filter( 'fluentform/response_render_acme_order_code', static function ($response, $field, $formId, $isHtml) { $value = (string) $response; return $isHtml ? esc_html($value) : $value; }, 10, 4 ); ``` ## Critical rules - Treat `attributes.name` as the key connecting browser data, `$formData`, the submission `response` JSON, entry details, smart codes, and feed mappings. - Keep filters pure and deterministic; rendering and formatting can occur more than once in a request. - Register scripts/styles only when the target form contains the field and use unique handles. Do not enqueue Pro assets from a Free-only addon. - Support arrays intentionally. A scalar-looking renderer or validator is not automatically safe for repeaters, containers, or multi-value inputs. - Do not use the unrelated `FluentForm\App\Modules\Component\BaseComponent` API as the default extension path; `BaseFieldManager` is the documented and core/Pro-used field manager in 6.2.7. ## Cross-references - Use `fluentform-submission-lifecycle` for hook timing and transformations. - Use `fluentform-entries-data` for stored response and entry-detail semantics. - Use `wp-security-audit` when the custom renderer outputs complex HTML. ## References - Official Base Field Manager documentation: <https://developers.fluentforms.com/api/classes/base-field-manager/> - Verified Free source paths: - `fluentform/app/Services/FormBuilder/BaseFieldManager.php` - `fluentform/app/Services/FormBuilder/Components/BaseComponent.php` - `fluentform/app/Services/Parser/Form.php` - `fluentform/app/Services/Form/FormValidationService.php` - Verified Pro examples, required only when their features are used: - `fluentformpro/src/Components/RangeSliderField.php` - `fluentformpro/src/Components/DynamicField/DynamicField.php` - `fluentformpro/src/Components/RepeaterField.php`
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.