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
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/fluentform/fluentform-entries-data
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 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
SubmissionServiceso Fluent Forms hooks, files, logs, details, queued actions, and payment-related cleanup are considered. - If an existing response must be edited, treat
responseJSON and affectedentry_detailsrows 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 readfluentform_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. Usefluentform_manage_entriesfor 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
LIKEsearches over the largeresponseJSON 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 markunreadentries asreadby default in 6.2.7. - Do not use
FluentForm\App\Models\Entryas the primary model; the live model isFluentForm\App\Models\Submission, whileFluentForm\App\Api\Entryis 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-lifecyclefor creation-time data and hooks. - Use
fluentform-custom-fieldsfor field-name and nested-value contracts. - Use
wp-rest-apiwhen 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.phpfluentform/app/Api/Form.phpfluentform/app/Api/Entry.phpfluentform/app/Api/Submission.phpfluentform/app/Models/Submission.phpfluentform/app/Models/EntryDetails.phpfluentform/app/Models/SubmissionMeta.phpfluentform/app/Services/Submission/SubmissionService.php
- Verified Pro source path:
fluentformpro/src/classes/StepFormEntries.php
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.
Reviews (0)
No reviews yet.
No comments yet.