$npx -y skills add Lonsdale201/wp-agent-skills --skill fluentform-feed-integrationBuilds and audits configurable third-party Fluent Forms feed integrations with IntegrationManagerController. Covers addon/global settings, per-form feed UI, field mapping, conditional execution, smart-code parsing, synchronous versus asynchronous dispatch, ff_scheduled_actions, A
| 1 | # Fluent Forms feed integrations |
| 2 | |
| 3 | Use the Free-core feed manager when administrators need credentials, reusable |
| 4 | per-form feeds, field mapping, conditions, logs, and background delivery. Use a |
| 5 | plain `submission_inserted` listener only for small, non-configurable local work. |
| 6 | |
| 7 | Read [integration-contract.md](references/integration-contract.md) before |
| 8 | implementing the manager class or deciding retry/idempotency behavior. |
| 9 | |
| 10 | ## Availability contract |
| 11 | |
| 12 | `FluentForm\App\Http\Controllers\IntegrationManagerController`, feed metadata, |
| 13 | the notification manager, `ff_scheduled_actions`, and bundled Action Scheduler |
| 14 | are Free-core surfaces in 6.2.7. Mailchimp is a Free reference implementation. |
| 15 | Many shipped connectors under `fluentformpro/src/Integrations` are Pro-only, but |
| 16 | their existence does not make a third-party integration manager require Pro. |
| 17 | |
| 18 | Use the current controller directly. These aliases are deprecated in 6.2.7: |
| 19 | |
| 20 | - `FluentForm\App\Services\Integrations\IntegrationManager` |
| 21 | - `FluentForm\App\Services\Integrations\BaseIntegration` |
| 22 | |
| 23 | ## Decision point |
| 24 | |
| 25 | Use a feed manager when at least one applies: |
| 26 | |
| 27 | - admins create multiple destinations or mappings per form; |
| 28 | - delivery has form conditions or smart codes; |
| 29 | - credentials need a global connection screen; |
| 30 | - delivery should run asynchronously and appear in integration logs; |
| 31 | - a failed/replayed request needs an idempotency contract. |
| 32 | |
| 33 | Use a direct hook for bounded local metadata/state changes with no settings UI. |
| 34 | Do not build a feed abstraction around a single pure calculation. |
| 35 | |
| 36 | ## Registration workflow |
| 37 | |
| 38 | 1. Bootstrap once on `fluentform/loaded`; require the controller class. |
| 39 | 2. Choose three stable identifiers: |
| 40 | - integration key for addon/global UI; |
| 41 | - namespaced option key for connection settings; |
| 42 | - feed settings key stored in `fluentform_form_meta` and used in the dynamic |
| 43 | notification hook. |
| 44 | 3. Extend `IntegrationManagerController`, call the parent constructor, set the |
| 45 | description/logo/category, then call `registerAdminHooks()`. |
| 46 | 4. Implement global settings and verify credentials server-side before setting |
| 47 | `status => true`. |
| 48 | 5. Implement integration availability, feed defaults, settings fields, and merge |
| 49 | fields. Keep `enabled` and `conditionals` in the feed schema. |
| 50 | 6. Implement `notify($feed, $formData, $entry, $form)` as an idempotent operation. |
| 51 | 7. Report every terminal result through `fluentform/integration_action_result`. |
| 52 | 8. Test disabled, unconfigured, condition-false, sync, async, timeout, retry, and |
| 53 | duplicate delivery paths. |
| 54 | |
| 55 | ## Bootstrap |
| 56 | |
| 57 | ```php |
| 58 | use FluentForm\App\Http\Controllers\IntegrationManagerController; |
| 59 | |
| 60 | add_action('fluentform/loaded', static function ($app): void { |
| 61 | if (!class_exists(IntegrationManagerController::class)) { |
| 62 | return; |
| 63 | } |
| 64 | |
| 65 | new Acme_FluentForm_Integration($app); |
| 66 | }, 20, 1); |
| 67 | ``` |
| 68 | |
| 69 | The constructor should use stable, namespaced keys: |
| 70 | |
| 71 | ```php |
| 72 | parent::__construct( |
| 73 | $app, |
| 74 | __('Acme CRM', 'acme-addon'), |
| 75 | 'acme_crm', |
| 76 | '_acme_ff_crm_settings', |
| 77 | 'acme_crm_feeds', |
| 78 | 20 |
| 79 | ); |
| 80 | |
| 81 | $this->description = __('Send selected entries to Acme CRM.', 'acme-addon'); |
| 82 | $this->category = 'crm'; |
| 83 | $this->logo = plugins_url('assets/acme.svg', ACME_ADDON_FILE); |
| 84 | $this->registerAdminHooks(); |
| 85 | ``` |
| 86 | |
| 87 | Do not change these keys after release without migrating the global option and |
| 88 | all form-meta feed rows. |
| 89 | |
| 90 | ## Notification contract |
| 91 | |
| 92 | ```php |
| 93 | public function notify($feed, $formData, $entry, $form) |
| 94 | { |
| 95 | $entryId = (int) $entry->id; |
| 96 | $values = isset($feed['processedValues']) && is_array($feed['processedValues']) |
| 97 | ? $feed['processedValues'] |
| 98 | : []; |
| 99 | |
| 100 | try { |
| 101 | $result = $this->client()->upsertContact([ |
| 102 | 'external_key' => 'ff-entry-' . $entryId, |
| 103 | 'email' => sanitize_email((string) ($values['fieldEmailAddress'] ?? '')), |
| 104 | ]); |
| 105 | |
| 106 | if (empty($result['ok'])) { |
| 107 | throw new \RuntimeException('Remote service rejected the request.'); |
| 108 | } |
| 109 | |
| 110 | do_action( |
| 111 | 'fluentform/int |