Fluent Forms custom fields

SkillMedia

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Fluent Forms custom fields skill

What this skill tells your AI

The instructions your AI receives, as published by lonsdale201/wp-agent-skills in fluentform/fluentform-custom-fields/SKILL.md and read by ahel’s review.

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

SurfaceAvailability in 6.2.7
FluentForm\App\Services\FormBuilder\BaseFieldManagerFree
Editor registration, frontend render hook, parser input type, conditional supportFree
Element input/validation/response filtersFree
Phone, range slider, NPS, ranking, dynamic field, chained select, repeater, rich text, file uploadPro 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

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.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

Signals

GitHub stars
22
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
fluentform-custom-fields
Source
github.com/lonsdale201/wp-agent-skills