Claude Skill

fluentform-entries-data

Reads, relates, updates, and audits Fluent Forms forms, submissions, entry details, and submission meta from third-party plugins. Covers fluentFormApi, FormFieldsParser, Submission and SubmissionMeta models, form-scoped queries, response JSON versus normalized detail rows, pagina

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-entries-data-52f6020.zip · 6 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-entries-data
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 entries and data model

Use Fluent Forms' API/model layer after enforcing your own authorization. Keep the canonical response snapshot, query projection, and addon metadata separate.

Read data-contract.md before writing entry data, exposing it over REST, joining nested fields, or supporting Pro drafts/payments.

Availability contract

Data/API Availability in 6.2.7
forms, submissions, entry details, form/submission meta Free
fluentFormApi(), FormFieldsParser, Submission, SubmissionMeta Free
draft/partial submissions Pro
order items, transactions, payment subscriptions and coupons Payment/Pro feature dependent

The PHP helpers perform data access, not request authorization. A successful fluentFormApi() call does not prove the current user may see the result.

Read one form's entries

use FluentForm\App\Modules\Acl\Acl;

$formId = absint($requestedFormId);

if (!$formId || !function_exists('fluentFormApi')) {
    return new WP_Error('acme_unavailable', __('Fluent Forms is unavailable.', 'acme-addon'));
}

if (!Acl::hasPermission('fluentform_entries_viewer', $formId)) {
    return new WP_Error('acme_forbidden', __('You cannot view these entries.', 'acme-addon'), [
        'status' => 403,
    ]);
}

$form = fluentFormApi('forms')->find($formId);
if (!$form) {
    return new WP_Error('acme_not_found', __('Form not found.', 'acme-addon'), [
        'status' => 404,
    ]);
}

$page    = max(1, absint($requestedPage));
$perPage = min(100, max(1, absint($requestedPerPage)));

$result = fluentFormApi('forms')->entryInstance($form)->entries([
    'page'       => $page,
    'per_page'   => $perPage,
    'entry_type' => 'all',
    'sort_type'  => 'DESC',
    'search'     => sanitize_text_field((string) $requestedSearch),
]);

Use the form-scoped entryInstance() for a known form. The global fluentFormApi('submissions') methods are useful for trusted internal reports, but callers must constrain form IDs, user IDs, status, and page size themselves.

Read a single form-scoped entry

$entryResult = fluentFormApi('forms')
    ->entryInstance($form)
    ->entry(absint($entryId), false);

if (!$entryResult) {
    return new WP_Error('acme_entry_not_found', __('Entry not found.', 'acme-addon'), [
        'status' => 404,
    ]);
}

$entry    = $entryResult['submission'];
$response = is_array($entry->response) ? $entry->response : [];

Do not fetch by entry ID globally and authorize with a different form ID. Scope the database lookup and permission decision to the same normalized form ID.

Resolve field definitions and labels

use FluentForm\App\Modules\Form\FormFieldsParser;

$inputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
$labels = FormFieldsParser::getAdminLabels($form, $inputs);

foreach ($response as $name => $value) {
    $label = $labels[$name] ?? $name;
    // Escape $label and $value for their actual output context.
}

attributes.name, not the visible label, connects the field definition to the response. Labels and fields can change after old submissions were stored, so always provide a fallback for historical/removed keys.

Store addon state as submission meta

use FluentForm\App\Helpers\Helper;

$entryId = absint($entryId);
$formId  = absint($formId);

// First verify the entry belongs to $formId and the current operation is allowed.
Helper::setSubmissionMeta($entryId, '_acme_delivery_state', [
    'status'     => 'queued',
    'updated_at' => current_time('mysql'),
], $formId);

$state = Helper::getSubmissionMeta($entryId, '_acme_delivery_state', []);

Namespace meta keys. Store bounded operational data, not credentials or copied entry payloads. SubmissionMeta serializes values and is not encrypted.

Mutation policy

  • Prefer submission-time filters when deriving a stored field value.
  • For status changes and deletion, use SubmissionService so Fluent Forms hooks, files, logs, details, queued actions, and payment-related cleanup are considered.
  • If an existing response must be edited, treat response JSON and affected entry_details rows as one consistency boundary. Validate against the current form, preserve unknown historical keys deliberately, update the timestamp, and emit the appropriate audit hook/log.
  • Never update only fluentform_entry_details; normal entry rendering and feeds read fluentform_submissions.response.
  • Never expose generic model where/sort/column inputs directly to a request.

Security and performance rules

  • Use Acl::hasPermission('fluentform_entries_viewer', $formId) for Fluent Forms admin semantics, plus any domain-specific ownership rule your endpoint needs. Use fluentform_manage_entries for mutations.
  • Add nonce verification to cookie-authenticated writes; a nonce does not replace the capability/form-scope check.
  • Return an explicit field allowlist. Entries can contain personal data, IP, source URLs, hidden fields, payment fields, and addon-injected values.
  • Bound per_page, validate statuses, and use a fixed sort allowlist.
  • Avoid LIKE searches over the large response JSON column for unbounded public queries. Use detail rows or an addon-owned indexed table for frequent reports.
  • Do not use SubmissionService::find() for a read-only probe without noticing that it can mark unread entries as read by default in 6.2.7.
  • Do not use FluentForm\App\Models\Entry as the primary model; the live model is FluentForm\App\Models\Submission, while FluentForm\App\Api\Entry is the form-scoped API wrapper.

Pro boundary

Pro partial entries live in fluentform_draft_submissions and have a different ownership/hash lifecycle. Do not merge them into completed-submission queries by ID alone. Pro/payment records link through submission_id, but payment access requires fluentform_view_payments or fluentform_manage_payments and must use verified payment status, not merely the presence of a row.

Cross-references

  • Use fluentform-submission-lifecycle for creation-time data and hooks.
  • Use fluentform-custom-fields for field-name and nested-value contracts.
  • Use wp-rest-api when entries are exposed through a custom REST endpoint.

References

Files (wp-agent-skills)
  • agents
    • openai.yaml 299 B
      interface:
        display_name: "Fluent Forms entries and data"
        short_description: "Use Fluent Forms entry APIs and storage safely"
        default_prompt: "Use this skill to implement or review form-scoped Fluent Forms entry access, storage relationships, metadata, authorization, pagination, or mutation."
      
  • references
    • data-contract.md 5.9 KB
      # Fluent Forms entry data contract
      
      This reference describes the 6.2.7 storage model and its practical extension
      semantics.
      
      ## Relationships
      
      ```text
      fluentform_forms.id
        ├─ fluentform_submissions.form_id
        │    ├─ fluentform_entry_details.submission_id
        │    ├─ fluentform_submission_meta.response_id
        │    ├─ fluentform_logs.source_id (source_type = submission_item)
        │    ├─ ff_scheduled_actions.origin_id (submission_action)
        │    └─ payment tables by submission_id when available
        └─ fluentform_form_meta.form_id
      ```
      
      Pro drafts are stored separately in `fluentform_draft_submissions` and are not
      normal `Submission` model records.
      
      ## Three representations of submitted data
      
      ### `fluentform_submissions.response`
      
      JSON snapshot of accepted form data after `fluentform/insert_response_data`. This
      is the main machine-readable submission payload used by entry rendering,
      notifications, asynchronous feeds, printing, and reports.
      
      ### `fluentform_entry_details`
      
      Search/report projection written after the main row. Scalar fields produce one
      row. Arrays/objects produce rows with the same `field_name` and different
      `sub_field_name` values; child values may be serialized.
      
      Do not treat the projection as lossless:
      
      - whitelisted protocol keys are excluded;
      - empty strings and null are skipped;
      - empty nested values are skipped with `empty()`, so nested zero-like values may
        be absent;
      - the update helper uses falsey checks that can omit zero-like values;
      - field definitions can change after the response was stored.
      
      ### `fluentform_submission_meta`
      
      Addon/operational data keyed by `(response_id, meta_key)` in normal usage.
      `Helper::setSubmissionMeta()` serializes values and `getSubmissionMeta()` safely
      unserializes them. Meta is not automatically exposed like a form field, but it is
      also not a secret store.
      
      ## API choices
      
      ### `fluentFormApi('forms')`
      
      - `find($formId)` returns a form or null.
      - `forms($args, $withFields)` lists forms.
      - `entryInstance($formOrId)` returns `FluentForm\App\Api\Entry`.
      
      ### Form-scoped `Api\Entry`
      
      - `entries($args, $includeFormats)` paginates one form.
      - `entry($entryId, $includeFormats)` scopes by form and ID.
      - `entryBySerial($serial, $includeFormats)` scopes by form and display serial.
      - `report($statuses)` generates report data.
      
      The display serial is only unique within its form and is not an authorization or
      anti-enumeration token.
      
      ### `fluentFormApi('submissions')`
      
      - `get($args)` lists submissions across forms.
      - `find($submissionId)` finds globally by ID and assumes a row exists.
      - payment transaction/subscription helpers are present in the Free API class but
        useful only when the relevant payment data/features exist.
      
      The global API does not enforce a current-user capability or form ownership. Add
      the policy at every externally reachable caller.
      
      ### Models and services
      
      Use `FluentForm\App\Models\Submission` for controlled ORM queries and relations.
      Use `SubmissionService` when behavior matters: status updates, delete lifecycle,
      files, notes, user association, parsing, and hooks.
      
      `SubmissionService::find()` parses an entry and, unless
      `fluentform/auto_read_submission` returns false, changes status from `unread` to
      `read`. This method is unsuitable for side-effect-free existence checks.
      
      ## Field definition mapping
      
      `fluentform_forms.form_fields` stores the builder JSON. Use
      `FormFieldsParser::getInputs()` for flattened inputs and
      `getEntryInputs()` for top-level entry inputs. Do not parse the JSON ad hoc unless
      you are writing a migration that intentionally preserves unknown schema.
      
      For a compound key such as `names[first_name]`:
      
      - the flattened input parser knows the bracket child;
      - accepted response data normally contains a top-level `names` array;
      - entry details store `field_name = names`, `sub_field_name = first_name`;
      - display formatting is element-specific.
      
      ## Authorization patterns
      
      For Fluent Forms admin-style operations:
      
      - read forms/dashboard: `fluentform_dashboard_access`
      - manage forms: `fluentform_forms_manager`
      - read entries: `fluentform_entries_viewer`
      - mutate entries: `fluentform_manage_entries`
      - read/manage payments: `fluentform_view_payments` /
        `fluentform_manage_payments`
      
      Prefer `Acl::hasPermission($permission, $formId)` to a bare capability when form
      manager scoping matters. For a customer-facing view, add ownership checks such as
      matching the entry's `user_id` to the authenticated user. Never let knowledge of
      an entry UID/hash replace authorization for sensitive data.
      
      ## Safe list-query checklist
      
      1. Normalize and authorize the form ID before building the query.
      2. Bound `page` and `per_page`; reject pathological offsets where necessary.
      3. Allowlist statuses and sort fields/directions.
      4. Keep search length bounded and avoid unrestricted response JSON scans.
      5. Select only fields the caller needs.
      6. Remove IP, source URL, payment, hidden, and sensitive field values unless
         explicitly authorized.
      7. Return pagination metadata and stable ordering with ID as a tie-breaker.
      8. Cache aggregate reports only with form/user/permission context in the key.
      
      ## Consistent update checklist
      
      There is no one-line public API in 6.2.7 that safely edits arbitrary response
      fields and all projections for every addon. If a requirement truly needs it:
      
      1. Fetch by both entry ID and form ID.
      2. Authorize `fluentform_manage_entries` plus object ownership.
      3. Parse and validate only allowlisted editable field names.
      4. Merge with decoded `response` deliberately; preserve historical keys.
      5. Encode with `wp_json_encode()` and check failure.
      6. Update the response and affected detail rows as one consistency operation.
      7. Set `updated_at`, create an audit log, and emit only documented hooks.
      8. Decide whether notifications/integrations must be re-run; never do so
         implicitly.
      9. Test arrays, empty strings, null, `0`, `"0"`, removed fields, and concurrent
         updates.
      
      For normal derived values, doing the work once during submission is safer.
      
  • SKILL.md 7.8 KB
    ---
    name: fluentform-entries-data
    description: >-
      Reads, relates, updates, and audits Fluent Forms forms, submissions, entry
      details, and submission meta from third-party plugins. Covers fluentFormApi,
      FormFieldsParser, Submission and SubmissionMeta models, form-scoped queries,
      response JSON versus normalized detail rows, pagination, capabilities,
      deletion hooks, and Free versus Pro tables. Use when building entry reports,
      exports, dashboards, REST endpoints, submission metadata, user-facing entry
      views, or code touching fluentform_submissions, fluentform_entry_details,
      fluentform_submission_meta, fluentFormApi('submissions'), or entryInstance().
    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 entries and data model
    
    Use Fluent Forms' API/model layer after enforcing your own authorization. Keep
    the canonical response snapshot, query projection, and addon metadata separate.
    
    Read [data-contract.md](references/data-contract.md) before writing entry data,
    exposing it over REST, joining nested fields, or supporting Pro drafts/payments.
    
    ## Availability contract
    
    | Data/API | Availability in 6.2.7 |
    |---|---|
    | forms, submissions, entry details, form/submission meta | Free |
    | `fluentFormApi()`, `FormFieldsParser`, `Submission`, `SubmissionMeta` | Free |
    | draft/partial submissions | Pro |
    | order items, transactions, payment subscriptions and coupons | Payment/Pro feature dependent |
    
    The PHP helpers perform data access, not request authorization. A successful
    `fluentFormApi()` call does not prove the current user may see the result.
    
    ## Read one form's entries
    
    ```php
    use FluentForm\App\Modules\Acl\Acl;
    
    $formId = absint($requestedFormId);
    
    if (!$formId || !function_exists('fluentFormApi')) {
        return new WP_Error('acme_unavailable', __('Fluent Forms is unavailable.', 'acme-addon'));
    }
    
    if (!Acl::hasPermission('fluentform_entries_viewer', $formId)) {
        return new WP_Error('acme_forbidden', __('You cannot view these entries.', 'acme-addon'), [
            'status' => 403,
        ]);
    }
    
    $form = fluentFormApi('forms')->find($formId);
    if (!$form) {
        return new WP_Error('acme_not_found', __('Form not found.', 'acme-addon'), [
            'status' => 404,
        ]);
    }
    
    $page    = max(1, absint($requestedPage));
    $perPage = min(100, max(1, absint($requestedPerPage)));
    
    $result = fluentFormApi('forms')->entryInstance($form)->entries([
        'page'       => $page,
        'per_page'   => $perPage,
        'entry_type' => 'all',
        'sort_type'  => 'DESC',
        'search'     => sanitize_text_field((string) $requestedSearch),
    ]);
    ```
    
    Use the form-scoped `entryInstance()` for a known form. The global
    `fluentFormApi('submissions')` methods are useful for trusted internal reports,
    but callers must constrain form IDs, user IDs, status, and page size themselves.
    
    ## Read a single form-scoped entry
    
    ```php
    $entryResult = fluentFormApi('forms')
        ->entryInstance($form)
        ->entry(absint($entryId), false);
    
    if (!$entryResult) {
        return new WP_Error('acme_entry_not_found', __('Entry not found.', 'acme-addon'), [
            'status' => 404,
        ]);
    }
    
    $entry    = $entryResult['submission'];
    $response = is_array($entry->response) ? $entry->response : [];
    ```
    
    Do not fetch by entry ID globally and authorize with a different form ID. Scope
    the database lookup and permission decision to the same normalized form ID.
    
    ## Resolve field definitions and labels
    
    ```php
    use FluentForm\App\Modules\Form\FormFieldsParser;
    
    $inputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
    $labels = FormFieldsParser::getAdminLabels($form, $inputs);
    
    foreach ($response as $name => $value) {
        $label = $labels[$name] ?? $name;
        // Escape $label and $value for their actual output context.
    }
    ```
    
    `attributes.name`, not the visible label, connects the field definition to the
    response. Labels and fields can change after old submissions were stored, so
    always provide a fallback for historical/removed keys.
    
    ## Store addon state as submission meta
    
    ```php
    use FluentForm\App\Helpers\Helper;
    
    $entryId = absint($entryId);
    $formId  = absint($formId);
    
    // First verify the entry belongs to $formId and the current operation is allowed.
    Helper::setSubmissionMeta($entryId, '_acme_delivery_state', [
        'status'     => 'queued',
        'updated_at' => current_time('mysql'),
    ], $formId);
    
    $state = Helper::getSubmissionMeta($entryId, '_acme_delivery_state', []);
    ```
    
    Namespace meta keys. Store bounded operational data, not credentials or copied
    entry payloads. `SubmissionMeta` serializes values and is not encrypted.
    
    ## Mutation policy
    
    - Prefer submission-time filters when deriving a stored field value.
    - For status changes and deletion, use `SubmissionService` so Fluent Forms hooks,
      files, logs, details, queued actions, and payment-related cleanup are considered.
    - If an existing response must be edited, treat `response` JSON and affected
      `entry_details` rows as one consistency boundary. Validate against the current
      form, preserve unknown historical keys deliberately, update the timestamp, and
      emit the appropriate audit hook/log.
    - Never update only `fluentform_entry_details`; normal entry rendering and feeds
      read `fluentform_submissions.response`.
    - Never expose generic model `where`/sort/column inputs directly to a request.
    
    ## Security and performance rules
    
    - Use `Acl::hasPermission('fluentform_entries_viewer', $formId)` for Fluent Forms
      admin semantics, plus any domain-specific ownership rule your endpoint needs.
      Use `fluentform_manage_entries` for mutations.
    - Add nonce verification to cookie-authenticated writes; a nonce does not replace
      the capability/form-scope check.
    - Return an explicit field allowlist. Entries can contain personal data, IP,
      source URLs, hidden fields, payment fields, and addon-injected values.
    - Bound `per_page`, validate statuses, and use a fixed sort allowlist.
    - Avoid `LIKE` searches over the large `response` JSON column for unbounded public
      queries. Use detail rows or an addon-owned indexed table for frequent reports.
    - Do not use `SubmissionService::find()` for a read-only probe without noticing
      that it can mark `unread` entries as `read` by default in 6.2.7.
    - Do not use `FluentForm\App\Models\Entry` as the primary model; the live model is
      `FluentForm\App\Models\Submission`, while `FluentForm\App\Api\Entry` is the
      form-scoped API wrapper.
    
    ## Pro boundary
    
    Pro partial entries live in `fluentform_draft_submissions` and have a different
    ownership/hash lifecycle. Do not merge them into completed-submission queries by
    ID alone. Pro/payment records link through `submission_id`, but payment access
    requires `fluentform_view_payments` or `fluentform_manage_payments` and must use
    verified payment status, not merely the presence of a row.
    
    ## Cross-references
    
    - Use `fluentform-submission-lifecycle` for creation-time data and hooks.
    - Use `fluentform-custom-fields` for field-name and nested-value contracts.
    - Use `wp-rest-api` when entries are exposed through a custom REST endpoint.
    
    ## References
    
    - Official database schema: <https://developers.fluentforms.com/database/>
    - Official model guide: <https://developers.fluentforms.com/database/models/>
    - Official query builder guide: <https://developers.fluentforms.com/database/query-builder/>
    - Verified Free source paths:
      - `fluentform/boot/globals.php`
      - `fluentform/app/Api/Form.php`
      - `fluentform/app/Api/Entry.php`
      - `fluentform/app/Api/Submission.php`
      - `fluentform/app/Models/Submission.php`
      - `fluentform/app/Models/EntryDetails.php`
      - `fluentform/app/Models/SubmissionMeta.php`
      - `fluentform/app/Services/Submission/SubmissionService.php`
    - Verified Pro source path:
      - `fluentformpro/src/classes/StepFormEntries.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related