$npx -y skills add Lonsdale201/wp-agent-skills --skill fluentform-entries-dataReads, 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
| 1 | # Fluent Forms entries and data model |
| 2 | |
| 3 | Use Fluent Forms' API/model layer after enforcing your own authorization. Keep |
| 4 | the canonical response snapshot, query projection, and addon metadata separate. |
| 5 | |
| 6 | Read [data-contract.md](references/data-contract.md) before writing entry data, |
| 7 | exposing it over REST, joining nested fields, or supporting Pro drafts/payments. |
| 8 | |
| 9 | ## Availability contract |
| 10 | |
| 11 | | Data/API | Availability in 6.2.7 | |
| 12 | |---|---| |
| 13 | | forms, submissions, entry details, form/submission meta | Free | |
| 14 | | `fluentFormApi()`, `FormFieldsParser`, `Submission`, `SubmissionMeta` | Free | |
| 15 | | draft/partial submissions | Pro | |
| 16 | | order items, transactions, payment subscriptions and coupons | Payment/Pro feature dependent | |
| 17 | |
| 18 | The PHP helpers perform data access, not request authorization. A successful |
| 19 | `fluentFormApi()` call does not prove the current user may see the result. |
| 20 | |
| 21 | ## Read one form's entries |
| 22 | |
| 23 | ```php |
| 24 | use FluentForm\App\Modules\Acl\Acl; |
| 25 | |
| 26 | $formId = absint($requestedFormId); |
| 27 | |
| 28 | if (!$formId || !function_exists('fluentFormApi')) { |
| 29 | return new WP_Error('acme_unavailable', __('Fluent Forms is unavailable.', 'acme-addon')); |
| 30 | } |
| 31 | |
| 32 | if (!Acl::hasPermission('fluentform_entries_viewer', $formId)) { |
| 33 | return new WP_Error('acme_forbidden', __('You cannot view these entries.', 'acme-addon'), [ |
| 34 | 'status' => 403, |
| 35 | ]); |
| 36 | } |
| 37 | |
| 38 | $form = fluentFormApi('forms')->find($formId); |
| 39 | if (!$form) { |
| 40 | return new WP_Error('acme_not_found', __('Form not found.', 'acme-addon'), [ |
| 41 | 'status' => 404, |
| 42 | ]); |
| 43 | } |
| 44 | |
| 45 | $page = max(1, absint($requestedPage)); |
| 46 | $perPage = min(100, max(1, absint($requestedPerPage))); |
| 47 | |
| 48 | $result = fluentFormApi('forms')->entryInstance($form)->entries([ |
| 49 | 'page' => $page, |
| 50 | 'per_page' => $perPage, |
| 51 | 'entry_type' => 'all', |
| 52 | 'sort_type' => 'DESC', |
| 53 | 'search' => sanitize_text_field((string) $requestedSearch), |
| 54 | ]); |
| 55 | ``` |
| 56 | |
| 57 | Use the form-scoped `entryInstance()` for a known form. The global |
| 58 | `fluentFormApi('submissions')` methods are useful for trusted internal reports, |
| 59 | but callers must constrain form IDs, user IDs, status, and page size themselves. |
| 60 | |
| 61 | ## Read a single form-scoped entry |
| 62 | |
| 63 | ```php |
| 64 | $entryResult = fluentFormApi('forms') |
| 65 | ->entryInstance($form) |
| 66 | ->entry(absint($entryId), false); |
| 67 | |
| 68 | if (!$entryResult) { |
| 69 | return new WP_Error('acme_entry_not_found', __('Entry not found.', 'acme-addon'), [ |
| 70 | 'status' => 404, |
| 71 | ]); |
| 72 | } |
| 73 | |
| 74 | $entry = $entryResult['submission']; |
| 75 | $response = is_array($entry->response) ? $entry->response : []; |
| 76 | ``` |
| 77 | |
| 78 | Do not fetch by entry ID globally and authorize with a different form ID. Scope |
| 79 | the database lookup and permission decision to the same normalized form ID. |
| 80 | |
| 81 | ## Resolve field definitions and labels |
| 82 | |
| 83 | ```php |
| 84 | use FluentForm\App\Modules\Form\FormFieldsParser; |
| 85 | |
| 86 | $inputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']); |
| 87 | $labels = FormFieldsParser::getAdminLabels($form, $inputs); |
| 88 | |
| 89 | foreach ($response as $name => $value) { |
| 90 | $label = $labels[$name] ?? $name; |
| 91 | // Escape $label and $value for their actual output context. |
| 92 | } |
| 93 | ``` |
| 94 | |
| 95 | `attributes.name`, not the visible label, connects the field definition to the |
| 96 | response. Labels and fields can change after old submissions were stored, so |
| 97 | always provide a fallback for historical/removed keys. |
| 98 | |
| 99 | ## Store addon state as submission meta |
| 100 | |
| 101 | ```php |
| 102 | use FluentForm\App\Helpers\Helper; |
| 103 | |
| 104 | $entryId = absint($entryId); |
| 105 | $formId = absint($formId); |
| 106 | |
| 107 | // First verify the entry belongs to $formId and the current operation is allowed. |
| 108 | Helper::setSubmissionMeta($entryId, '_acme_delivery_state', [ |
| 109 | 'status' => 'queued', |
| 110 | 'updated_at' => current_time('mysql'), |
| 111 | ], $formId); |
| 112 | |
| 113 | $state = Helper::getSubmissionMeta($entryId, '_acme_delivery_state', []); |
| 114 | ``` |
| 115 | |
| 116 | Namespace meta keys. Store bounded operational data, not credentials or copied |
| 117 | entry payloads. `SubmissionMeta` serializes values and is not encrypted. |
| 118 | |
| 119 | ## Mutation policy |
| 120 | |
| 121 | - Prefer submission-time filters when deriving a stored field value. |
| 122 | - For status changes and deletion, use `SubmissionService` so Fluent Forms hooks, |
| 123 | files, logs, details |