Claude Skill

fluentform-submission-lifecycle

Implements and audits Fluent Forms server-side submission behavior from parsed field data through sanitization, validation, persistence, entry details, notifications, integrations, and confirmation. Selects the correct fluentform/input_data_*, fluentform/validation_errors, fluent

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-fluentform_fluentform-submission-lifecycle-52f6020.zip · 5 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/fluentform/fluentform-submission-lifecycle
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git 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 submission lifecycle

Choose hooks from the actual server-side order. Browser data is untrusted, and an action that sounds “before insert” is not a replacement for validation.

Read hook-order.md before changing stored values, adding a remote side effect, handling payment forms, or reacting to Pro drafts.

Canonical lifecycle

raw serialized request
  -> form field parsing and accepted-key allowlist
  -> recursive Fluent Forms sanitization
  -> restrictions / CAPTCHA / input normalization / validation
  -> spam checks
  -> response-data and insert-row filters
  -> submission row insert
  -> before-actions hook
  -> entry-details projection
  -> submission-inserted hooks and feeds
  -> confirmation filters

Unknown request keys are removed before validation unless explicitly placed on Fluent Forms' protocol-key allowlist. The accepted $formData is therefore not the same object as raw $_POST or the controller's parsed input.

Hook selection

Need Use Important timing
Normalize one element before rules fluentform/input_data_{element} Before validator
Change rule/message definitions fluentform/validations Before validator
Add cross-field errors fluentform/validation_errors Before rejection/insert
Change accepted response data fluentform/insert_response_data After validation, before JSON encode
Change DB row columns fluentform/filter_insert_data Low-level, immediately before insert
Observe imminent insert fluentform/before_insert_submission Action, after validation; cannot return changed data
React to a durable entry fluentform/submission_inserted Row and entry details exist
Change browser confirmation fluentform/submission_confirmation Last response stage

Do not use fluentform/before_insert_submission for normal validation. In 6.2.7 it is an action after handleValidation() and receives $insertData by value. Use validation filters so the browser gets field-shaped errors and no row is created.

Form-scoped cross-field validation

add_filter(
    'fluentform/validation_errors',
    static function ($errors, $formData, $form, $fields) {
        if ((int) $form->id !== 42) {
            return $errors;
        }

        $start = (string) ($formData['start_date'] ?? '');
        $end   = (string) ($formData['end_date'] ?? '');

        if ($start !== '' && $end !== '' && $end < $start) {
            $errors['end_date'][] = __(
                'The end date must not precede the start date.',
                'acme-addon'
            );
        }

        return $errors;
    },
    10,
    4
);

Use exact field names from the form definition. Preserve existing errors and return an array keyed by the input name. Validate dates or numbers semantically; the short comparison above is valid only for normalized ISO dates.

Stored-value transformation

add_filter(
    'fluentform/insert_response_data',
    static function ($formData, $formId, $inputConfigs) {
        if ((int) $formId !== 42 || !isset($formData['customer_code'])) {
            return $formData;
        }

        $formData['customer_code'] = strtoupper(
            sanitize_text_field((string) $formData['customer_code'])
        );

        return $formData;
    },
    10,
    3
);

This filter runs after field validation. Revalidate any materially changed value, or normalize it earlier with input_data_{element}. Never inject secrets, payment tokens, raw request bodies, or unbounded payloads into response.

Durable side effects

add_action(
    'fluentform/submission_inserted',
    static function ($entryId, $formData, $form): void {
        if ((int) $form->id !== 42) {
            return;
        }

        $alreadyDone = \FluentForm\App\Helpers\Helper::getSubmissionMeta(
            $entryId,
            '_acme_synced',
            false
        );

        if ($alreadyDone) {
            return;
        }

        // Queue a bounded background job with $entryId as its idempotency key.
    },
    20,
    3
);

Prefer Fluent Forms feed integrations for configurable external delivery. If a plain hook is enough, queue slow HTTP/email work and design for replay. Do not assume the full submission process is one database transaction.

Security and correctness rules

  • Treat Fluent Forms sanitization as input normalization, not authorization or business validation. Enforce ownership/capabilities in privileged custom flows.
  • Public forms do not become authenticated because a WordPress nonce exists. In 6.2.7 submission nonce verification defaults off; rate limits, CAPTCHA, validation, and service-specific anti-abuse controls remain relevant.
  • Keep fluentform/submission_form_data pure: current 6.2.7 applies it twice in processSubmissionData(). Never send mail, charge, or call an API from it.
  • Preserve existing filter data. Do not replace another addon's fields or errors.
  • Scope every callback by form ID and, where needed, element key or form type.
  • Use namespaced slash hooks. Deprecated underscore aliases remain for compatibility but should not be used in new code.
  • Do not treat notify_on_form_submit as “all processing completed”; it fires directly after the submission row insert and before entry-detail recording.

Pro boundary

Normal completed submissions and the hooks above are Free-core behavior. fluentform_draft_submissions, Save Progress, partial-entry administration, and the fluentform/partial_submission_* hooks are Pro features in 6.2.7. A partial draft is not a completed entry and must not trigger fulfillment, enrollment, charging, or irreversible delivery.

Payment fields exist in the Free codebase, but gateway execution, order items, transactions, recurring subscriptions, and several payment hooks depend on the configured payment feature/addon. Do not infer successful payment from submission_inserted; react to the appropriate verified payment-status hook.

Cross-references

  • Use fluentform-custom-fields for element-specific normalization/validation.
  • Use fluentform-entries-data for response, detail-row, and meta semantics.
  • Use fluentform-feed-integration for configurable asynchronous delivery.

References

Files (wp-agent-skills)
  • agents
    • openai.yaml 312 B
      interface:
        display_name: "Fluent Forms submission lifecycle"
        short_description: "Choose safe hooks from validation through confirmation"
        default_prompt: "Use this skill to implement or audit Fluent Forms submission handling with the correct hook timing, data contract, idempotency, and Free/Pro boundary."
      
  • references
    • hook-order.md 5.3 KB
      # Fluent Forms submission hook order
      
      This reference reflects Fluent Forms 6.2.7 server source. Re-audit the source
      when upgrading because order and argument counts are behavioral contracts.
      
      ## Preparation
      
      `SubmissionHandlerService::prepareHandler()`:
      
      1. Loads the form or throws a validation exception.
      2. Removes empty members from array inputs.
      3. Resolves the special “Other” checkbox/radio keys.
      4. Calls `FormFieldsParser::getEssentialInputs()` using browser keys.
      5. Sanitizes recursively through `fluentFormSanitizer()` according to element.
      6. Intersects data with parsed inputs plus `Helper::getWhiteListedFields()`.
      
      Only declared/recognized fields proceed. Whitelisted protocol fields can proceed
      but are excluded from `fluentform_entry_details` later.
      
      ## Validation order
      
      `FormValidationService::validateSubmission()` performs:
      
      1. `fluentform/before_form_validation` action.
      2. Per-IP burst guard (`prevent_malicious_attacks`, default 5 submissions in 30
         seconds, both filterable).
      3. form restrictions and deny-empty checks.
      4. optional nonce verification.
      5. reCAPTCHA, hCaptcha, and Turnstile checks.
      6. `fluentform/input_data_{element}` for each present field.
      7. `fluentform/validations` for rules/messages.
      8. built-in validator and `fluentform/validation_error` when it fails.
      9. per-field built-in validation and
         `fluentform/validate_input_item_{element}`.
      10. `fluentform/validation_errors` for final cross-field errors.
      11. registration/update/post validation extensions where applicable.
      12. `ValidationException` when any error remains.
      
      Pro 6.2.7 attaches advanced form validation to the final error filter. Do not
      erase existing `$errors`, or Pro and other addons lose their results.
      
      Spam checks follow normal validation. Depending on global settings a spam entry
      may be stored and subsequent actions skipped.
      
      ## Insert preparation and persistence
      
      `prepareInsertData()`:
      
      1. Computes a per-form display serial from the latest row.
      2. Applies `fluentform/insert_response_data` to accepted form data.
      3. JSON-encodes that data into the submission row's `response`.
      4. Adds request/user/browser/device/country/IP/timestamps.
      5. Applies `fluentform/filter_insert_data` to the complete row.
      
      The serial is a display sequence, not an idempotency key or authorization token.
      Use the primary entry ID or your own opaque key for external deduplication.
      
      `insertSubmission()` then fires:
      
      1. `fluentform/before_insert_submission`
      2. `fluentform/before_insert_payment_form` for payment forms
      3. submission row insert
      4. `fluentform/notify_on_form_submit`
      5. `_entry_uid_hash` submission meta creation
      
      The two before-insert hooks are actions. Returning data from their callbacks does
      nothing. Throwing arbitrary exceptions there is a fragile validation strategy and
      does not produce the standard field-error contract.
      
      ## Post-insert processing
      
      `processSubmissionData()`:
      
      1. Fires `fluentform/before_form_actions_processing`.
      2. Applies `fluentform/submission_form_data`.
      3. Writes the `fluentform_entry_details` projection.
      4. Applies `fluentform/submission_form_data` again.
      5. Fires deprecated/current `submission_inserted` hooks.
      6. Marks `is_form_action_fired` meta.
      7. Fires `fluentform/submission_inserted_{form-type}_form`.
      8. Fires `fluentform/before_submission_confirmation`.
      9. Builds and filters the confirmation response.
      
      Core's global notification manager listens to `submission_inserted` and processes
      enabled feed settings. Most feeds default to asynchronous processing through
      `ff_scheduled_actions` and Action Scheduler.
      
      Current code catches `Exception` around inserted-action processing and normally
      does not roll back the saved row. Do not make a successful browser response your
      only proof that an external side effect succeeded.
      
      ## Confirmation filters
      
      - `fluentform/form_submission_confirmation`: change confirmation settings.
      - `fluentform/submission_message_parse`: change same-page message before smart
        code parsing.
      - `fluentform/redirect_url_value`: change the sanitized redirect URL.
      - `fluentform/submission_confirmation`: change the final response array.
      
      Validate redirects against an explicit allowlist when they depend on submitted
      data. Do not place secrets or internal error detail in confirmation output.
      
      ## Pro partial submissions
      
      Pro's draft path uses `fluentform_draft_submissions`, not
      `fluentform_submissions`. Relevant Pro hooks include:
      
      - `fluentform/partial_submission_added`
      - `fluentform/partial_submission_step_completed`
      - `fluentform/partial_submission_updated`
      - `fluentform/partial_submission_deleted`
      - `fluentform/before_partial_entry_deleted`
      - `fluentform/after_partial_entry_deleted`
      
      Treat draft payloads as incomplete and mutable. Scope lookups by form, entry/hash,
      and authenticated user/ownership rules. A final normal submission may follow a
      series of partial updates, so deduplicate analytics and external sync separately.
      
      ## Verification matrix
      
      Test each extension with:
      
      1. unknown field, missing field, zero-like value, nested array, oversized value;
      2. field validation and cross-field validation failures;
      3. anonymous and logged-in submissions;
      4. CAPTCHA/spam rejection and “store spam but skip actions” mode;
      5. duplicate/replayed requests;
      6. an integration failure after the row exists;
      7. same-page and redirect confirmations;
      8. Pro partial draft versus final completed submission when Pro is supported.
      
  • SKILL.md 7.9 KB
    ---
    name: fluentform-submission-lifecycle
    description: >-
      Implements and audits Fluent Forms server-side submission behavior from parsed
      field data through sanitization, validation, persistence, entry details,
      notifications, integrations, and confirmation. Selects the correct
      fluentform/input_data_*, fluentform/validation_errors,
      fluentform/insert_response_data, fluentform/submission_inserted, and
      fluentform/submission_confirmation hook and explains their timing. Use when
      adding cross-field validation, normalizing submitted values, reacting to an
      entry, debugging missing submission data, preventing duplicate side effects,
      or distinguishing a Pro partial draft from a completed submission.
    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 submission lifecycle
    
    Choose hooks from the actual server-side order. Browser data is untrusted, and
    an action that sounds “before insert” is not a replacement for validation.
    
    Read [hook-order.md](references/hook-order.md) before changing stored values,
    adding a remote side effect, handling payment forms, or reacting to Pro drafts.
    
    ## Canonical lifecycle
    
    ```text
    raw serialized request
      -> form field parsing and accepted-key allowlist
      -> recursive Fluent Forms sanitization
      -> restrictions / CAPTCHA / input normalization / validation
      -> spam checks
      -> response-data and insert-row filters
      -> submission row insert
      -> before-actions hook
      -> entry-details projection
      -> submission-inserted hooks and feeds
      -> confirmation filters
    ```
    
    Unknown request keys are removed before validation unless explicitly placed on
    Fluent Forms' protocol-key allowlist. The accepted `$formData` is therefore not
    the same object as raw `$_POST` or the controller's parsed input.
    
    ## Hook selection
    
    | Need | Use | Important timing |
    |---|---|---|
    | Normalize one element before rules | `fluentform/input_data_{element}` | Before validator |
    | Change rule/message definitions | `fluentform/validations` | Before validator |
    | Add cross-field errors | `fluentform/validation_errors` | Before rejection/insert |
    | Change accepted response data | `fluentform/insert_response_data` | After validation, before JSON encode |
    | Change DB row columns | `fluentform/filter_insert_data` | Low-level, immediately before insert |
    | Observe imminent insert | `fluentform/before_insert_submission` | Action, after validation; cannot return changed data |
    | React to a durable entry | `fluentform/submission_inserted` | Row and entry details exist |
    | Change browser confirmation | `fluentform/submission_confirmation` | Last response stage |
    
    Do not use `fluentform/before_insert_submission` for normal validation. In 6.2.7
    it is an action after `handleValidation()` and receives `$insertData` by value.
    Use validation filters so the browser gets field-shaped errors and no row is
    created.
    
    ## Form-scoped cross-field validation
    
    ```php
    add_filter(
        'fluentform/validation_errors',
        static function ($errors, $formData, $form, $fields) {
            if ((int) $form->id !== 42) {
                return $errors;
            }
    
            $start = (string) ($formData['start_date'] ?? '');
            $end   = (string) ($formData['end_date'] ?? '');
    
            if ($start !== '' && $end !== '' && $end < $start) {
                $errors['end_date'][] = __(
                    'The end date must not precede the start date.',
                    'acme-addon'
                );
            }
    
            return $errors;
        },
        10,
        4
    );
    ```
    
    Use exact field names from the form definition. Preserve existing errors and
    return an array keyed by the input name. Validate dates or numbers semantically;
    the short comparison above is valid only for normalized ISO dates.
    
    ## Stored-value transformation
    
    ```php
    add_filter(
        'fluentform/insert_response_data',
        static function ($formData, $formId, $inputConfigs) {
            if ((int) $formId !== 42 || !isset($formData['customer_code'])) {
                return $formData;
            }
    
            $formData['customer_code'] = strtoupper(
                sanitize_text_field((string) $formData['customer_code'])
            );
    
            return $formData;
        },
        10,
        3
    );
    ```
    
    This filter runs after field validation. Revalidate any materially changed value,
    or normalize it earlier with `input_data_{element}`. Never inject secrets,
    payment tokens, raw request bodies, or unbounded payloads into `response`.
    
    ## Durable side effects
    
    ```php
    add_action(
        'fluentform/submission_inserted',
        static function ($entryId, $formData, $form): void {
            if ((int) $form->id !== 42) {
                return;
            }
    
            $alreadyDone = \FluentForm\App\Helpers\Helper::getSubmissionMeta(
                $entryId,
                '_acme_synced',
                false
            );
    
            if ($alreadyDone) {
                return;
            }
    
            // Queue a bounded background job with $entryId as its idempotency key.
        },
        20,
        3
    );
    ```
    
    Prefer Fluent Forms feed integrations for configurable external delivery. If a
    plain hook is enough, queue slow HTTP/email work and design for replay. Do not
    assume the full submission process is one database transaction.
    
    ## Security and correctness rules
    
    - Treat Fluent Forms sanitization as input normalization, not authorization or
      business validation. Enforce ownership/capabilities in privileged custom flows.
    - Public forms do not become authenticated because a WordPress nonce exists.
      In 6.2.7 submission nonce verification defaults off; rate limits, CAPTCHA,
      validation, and service-specific anti-abuse controls remain relevant.
    - Keep `fluentform/submission_form_data` pure: current 6.2.7 applies it twice in
      `processSubmissionData()`. Never send mail, charge, or call an API from it.
    - Preserve existing filter data. Do not replace another addon's fields or errors.
    - Scope every callback by form ID and, where needed, element key or form type.
    - Use namespaced slash hooks. Deprecated underscore aliases remain for
      compatibility but should not be used in new code.
    - Do not treat `notify_on_form_submit` as “all processing completed”; it fires
      directly after the submission row insert and before entry-detail recording.
    
    ## Pro boundary
    
    Normal completed submissions and the hooks above are Free-core behavior.
    `fluentform_draft_submissions`, Save Progress, partial-entry administration, and
    the `fluentform/partial_submission_*` hooks are Pro features in 6.2.7. A partial
    draft is not a completed entry and must not trigger fulfillment, enrollment,
    charging, or irreversible delivery.
    
    Payment fields exist in the Free codebase, but gateway execution, order items,
    transactions, recurring subscriptions, and several payment hooks depend on the
    configured payment feature/addon. Do not infer successful payment from
    `submission_inserted`; react to the appropriate verified payment-status hook.
    
    ## Cross-references
    
    - Use `fluentform-custom-fields` for element-specific normalization/validation.
    - Use `fluentform-entries-data` for response, detail-row, and meta semantics.
    - Use `fluentform-feed-integration` for configurable asynchronous delivery.
    
    ## References
    
    - Official submission lifecycle: <https://developers.fluentforms.com/submission-lifecycle/>
    - Official submission actions: <https://developers.fluentforms.com/hooks/actions/submission/>
    - Official submission filters: <https://developers.fluentforms.com/hooks/filters/submission/>
    - Verified Free source paths:
      - `fluentform/app/Http/Controllers/SubmissionHandlerController.php`
      - `fluentform/app/Services/Form/SubmissionHandlerService.php`
      - `fluentform/app/Services/Form/FormValidationService.php`
      - `fluentform/app/Services/Submission/SubmissionService.php`
      - `fluentform/app/Hooks/Handlers/GlobalNotificationHandler.php`
    - Verified Pro source paths:
      - `fluentformpro/src/classes/DraftSubmissionsManager.php`
      - `fluentformpro/src/classes/StepFormEntries.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related