fluentform-feed-integration
Builds 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
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/fluentform/fluentform-feed-integration
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 feed integrations
Use the Free-core feed manager when administrators need credentials, reusable
per-form feeds, field mapping, conditions, logs, and background delivery. Use a
plain submission_inserted listener only for small, non-configurable local work.
Read integration-contract.md before implementing the manager class or deciding retry/idempotency behavior.
Availability contract
FluentForm\App\Http\Controllers\IntegrationManagerController, feed metadata,
the notification manager, ff_scheduled_actions, and bundled Action Scheduler
are Free-core surfaces in 6.2.7. Mailchimp is a Free reference implementation.
Many shipped connectors under fluentformpro/src/Integrations are Pro-only, but
their existence does not make a third-party integration manager require Pro.
Use the current controller directly. These aliases are deprecated in 6.2.7:
FluentForm\App\Services\Integrations\IntegrationManagerFluentForm\App\Services\Integrations\BaseIntegration
Decision point
Use a feed manager when at least one applies:
- admins create multiple destinations or mappings per form;
- delivery has form conditions or smart codes;
- credentials need a global connection screen;
- delivery should run asynchronously and appear in integration logs;
- a failed/replayed request needs an idempotency contract.
Use a direct hook for bounded local metadata/state changes with no settings UI. Do not build a feed abstraction around a single pure calculation.
Registration workflow
- Bootstrap once on
fluentform/loaded; require the controller class. - Choose three stable identifiers:
- integration key for addon/global UI;
- namespaced option key for connection settings;
- feed settings key stored in
fluentform_form_metaand used in the dynamic notification hook.
- Extend
IntegrationManagerController, call the parent constructor, set the description/logo/category, then callregisterAdminHooks(). - Implement global settings and verify credentials server-side before setting
status => true. - Implement integration availability, feed defaults, settings fields, and merge
fields. Keep
enabledandconditionalsin the feed schema. - Implement
notify($feed, $formData, $entry, $form)as an idempotent operation. - Report every terminal result through
fluentform/integration_action_result. - Test disabled, unconfigured, condition-false, sync, async, timeout, retry, and duplicate delivery paths.
Bootstrap
use FluentForm\App\Http\Controllers\IntegrationManagerController;
add_action('fluentform/loaded', static function ($app): void {
if (!class_exists(IntegrationManagerController::class)) {
return;
}
new Acme_FluentForm_Integration($app);
}, 20, 1);
The constructor should use stable, namespaced keys:
parent::__construct(
$app,
__('Acme CRM', 'acme-addon'),
'acme_crm',
'_acme_ff_crm_settings',
'acme_crm_feeds',
20
);
$this->description = __('Send selected entries to Acme CRM.', 'acme-addon');
$this->category = 'crm';
$this->logo = plugins_url('assets/acme.svg', ACME_ADDON_FILE);
$this->registerAdminHooks();
Do not change these keys after release without migrating the global option and all form-meta feed rows.
Notification contract
public function notify($feed, $formData, $entry, $form)
{
$entryId = (int) $entry->id;
$values = isset($feed['processedValues']) && is_array($feed['processedValues'])
? $feed['processedValues']
: [];
try {
$result = $this->client()->upsertContact([
'external_key' => 'ff-entry-' . $entryId,
'email' => sanitize_email((string) ($values['fieldEmailAddress'] ?? '')),
]);
if (empty($result['ok'])) {
throw new \RuntimeException('Remote service rejected the request.');
}
do_action(
'fluentform/integration_action_result',
$feed,
'success',
__('Delivered to Acme CRM.', 'acme-addon')
);
} catch (\Throwable $error) {
do_action(
'fluentform/integration_action_result',
$feed,
'failed',
__('Acme CRM delivery failed.', 'acme-addon')
);
// Log bounded, redacted diagnostics; never expose credentials or payloads.
}
}
processedValues contains the feed settings after Fluent Forms smart-code
parsing. $formData is the stored response data, and $entry is its parsed entry
view. Map explicit keys; do not forward the complete arrays by default.
Async and failure semantics
Feeds default to asynchronous delivery. Fluent Forms writes a row to
ff_scheduled_actions, queues fluentform/schedule_feed through Action
Scheduler, marks the row processing, then dispatches
fluentform/integration_notify_{settingsKey}.
add_filter(
'fluentform/notifying_async_acme_crm',
static fn($async, $formId) => true,
10,
2
);
The filter suffix is the integration key, while the notify action suffix is the feed settings key. Do not interchange them.
Do not advertise automatic delivery guarantees that the implementation does not
provide. In 6.2.7 the queue increments retry_count and marks processing, but
the integration callback must still record a terminal result, and exception or
process-death recovery needs explicit testing. Use a stable remote idempotency key
derived from the entry/feed, bounded timeouts, and a documented retry policy.
Security and data rules
- Store credentials only in the global option, with autoload disabled. Never copy API keys into per-form feed values, localized JavaScript, submission meta, or logs. Mask secrets when returning global settings to the UI.
- Verify credentials with a bounded server-side request before marking the connection configured. Use TLS verification and an allowlisted service origin.
- Sanitize global/feed settings on save. Escape labels/help HTML for its exact admin rendering context.
- If adding custom AJAX/REST routes, implement nonce/authentication, Fluent Forms capabilities, form-level authorization, and object-level ownership yourself.
- Never send password fields, payment tokens, file-system paths, hidden control keys, IP addresses, or the entire entry unless explicitly required and lawful.
- Validate mapped email/URL/ID types after smart-code expansion.
- Treat provider error bodies as untrusted and redact before logs or UI output.
- Keep
notify()idempotent; Action Scheduler/manual retry or network ambiguity can deliver the same entry more than once.
Pro boundary
Custom feed infrastructure is Free. A connector is Pro-dependent only when it
uses a Pro class, field, payment object, user-registration feed, post feed, or
other Pro-only capability. Guard that exact dependency with class_exists() or
method_exists() and provide a clear disabled state in the integration UI.
Cross-references
- Use
fluentform-submission-lifecyclefor feed dispatch timing. - Use
fluentform-entries-datafor entry fields, meta, and permissions.
References
- Official Integration Manager Controller documentation: https://developers.fluentforms.com/api/classes/integration-manager-controller/
- Official integration hooks: https://developers.fluentforms.com/hooks/actions/integration/
- Verified Free source paths:
fluentform/app/Http/Controllers/IntegrationManagerController.phpfluentform/app/Services/Integrations/FormIntegrationService.phpfluentform/app/Hooks/Handlers/GlobalNotificationHandler.phpfluentform/app/Services/Integrations/GlobalNotificationService.phpfluentform/app/Services/WPAsync/FluentFormAsyncRequest.phpfluentform/app/Services/Integrations/MailChimp/MailChimpIntegration.php
- Verified Pro examples, required only for their features:
fluentformpro/src/Integrations/ActiveCampaign/Bootstrap.phpfluentformpro/src/Integrations/WebHook/Bootstrap.php
Files (wp-agent-skills)
-
agents
-
openai.yaml 322 B
interface: display_name: "Fluent Forms feed integrations" short_description: "Build configurable, reliable Fluent Forms connectors" default_prompt: "Use this skill to design or audit a Fluent Forms IntegrationManagerController feed with secure settings, mappings, conditions, async dispatch, logs, and idempotency."
-
-
references
-
integration-contract.md 7.4 KB
# Fluent Forms integration-manager contract Read this before implementing the manager class. It records the Free 6.2.7 controller and dispatch behavior; provider-specific code remains the addon's responsibility. ## Identifier map Given: ```php parent::__construct( $app, 'Acme CRM', 'acme_crm', '_acme_ff_crm_settings', 'acme_crm_feeds', 20 ); ``` | Value | Role | |---|---| | `acme_crm` | addon/global-settings identifier and async-filter suffix | | `_acme_ff_crm_settings` | WordPress option holding connection state | | `acme_crm_feeds` | `fluentform_form_meta.meta_key` and notify-action suffix | | `20` | registration priority | The controller's `isEnabled()` checks Fluent Forms' global addon-module state. When disabled, registration exposes the addon card but does not attach its feed or notify hooks. Test the UI enable/disable transition. ## Required methods The controller requires: ```php public function getIntegrationDefaults($settings, $formId); public function pushIntegration($integrations, $formId); public function getSettingsFields($settings, $formId); public function getMergeFields($list, $listId, $formId); ``` Most remote integrations also override: ```php public function getGlobalFields($fields); public function getGlobalSettings($settings); public function saveGlobalSettings($settings); public function notify($feed, $formData, $entry, $form); ``` `registerAdminHooks()` attaches the UI/feed hooks. It also attaches the dynamic notify callback only when `isConfigured()` is true. ## Feed defaults At minimum preserve the framework's expected control values: ```php public function getIntegrationDefaults($settings, $formId) { return [ 'name' => '', 'enabled' => true, 'fieldEmailAddress' => '', 'merge_fields' => [], 'remote_tag' => '', 'conditionals' => [ 'status' => false, 'type' => 'all', 'conditions' => [], ], ]; } ``` Feed settings are JSON in `fluentform_form_meta`. Avoid resources, closures, objects, secrets, and unbounded blobs. Keep migrations for renamed keys. `prepareIntegrationFeed()` converts string booleans and merges defaults. Do not assume every historical feed already has current keys. ## Settings-field structure The manager generates the form-feed UI from arrays. Verify component names against the current Integration Feed Fields API and a bundled integration. A typical start is: ```php public function getSettingsFields($settings, $formId) { return [ 'fields' => [ [ 'key' => 'name', 'label' => __('Feed name', 'acme-addon'), 'required' => true, 'component' => 'text', 'placeholder' => __('Acme CRM feed', 'acme-addon'), ], [ 'key' => 'merge_fields', 'label' => __('Map fields', 'acme-addon'), 'component' => 'map_fields', 'field_label_remote' => __('Acme field', 'acme-addon'), 'field_label_local' => __('Form field', 'acme-addon'), // This misspelling is the Fluent Forms 6.2.7 schema key. 'primary_fileds' => [ [ 'key' => 'fieldEmailAddress', 'label' => __('Email address', 'acme-addon'), 'required' => true, 'input_options' => 'emails', ], ], ], [ 'key' => 'conditionals', 'label' => __('Conditional logic', 'acme-addon'), 'component' => 'conditional_block', ], [ 'key' => 'enabled', 'label' => __('Status', 'acme-addon'), 'component' => 'checkbox-single', 'checkbox_label' => __('Enable this feed', 'acme-addon'), ], ], 'integration_title' => __('Acme CRM feed', 'acme-addon'), 'button_require_list' => false, ]; } ``` Do not assume this simplified schema fits list/tag providers. Inspect the current Mailchimp source and official field-component reference for dynamic option calls, route handling, and merge-field shapes. ## Global settings Return a masked value to the browser while retaining the real secret server-side. On save: 1. distinguish “unchanged masked value” from a new credential; 2. sanitize identifier fields without corrupting secrets; 3. verify the credential against an allowlisted HTTPS endpoint; 4. handle timeout, authentication error, provider rate limit, and malformed JSON; 5. store with `update_option($key, $value, false)`; 6. never include raw provider error bodies in `wp_send_json_error()`. Core's manager routes protect its own settings operations. Any additional custom route or AJAX action is your security boundary and must repeat the relevant nonce, capability, form-scope, and parameter validation. ## Dispatch order On `fluentform/submission_inserted`, the global notification handler: 1. obtains enabled feed meta keys from `fluentform/global_notification_active_types`; 2. loads matching form-meta rows; 3. keeps `enabled` feeds whose `conditionals` pass; 4. filters each feed with `fluentform/integration_feed_before_parse`; 5. loads a parsed entry; 6. expands smart codes into `feed['processedValues']`; 7. chooses async/sync with `fluentform/notifying_async_{integrationKey}`; 8. queues or calls `fluentform/integration_notify_{settingsKey}`. Async execution reloads the submission's response JSON and parsed entry. It is a new request/process; do not depend on globals, current user, request cookies, or in-memory state from form submission. ## Result and observability contract Always call: ```php do_action( 'fluentform/integration_action_result', $feed, $status, // success or failed $boundedRedactedNote ); ``` When `scheduled_action_id` exists, core updates the queue row status/note. Keep the note under 255 characters and useful to an administrator without including personal data or provider secrets. For deeper diagnostics, log a correlation ID, form ID, entry ID, feed ID, provider status class, and attempt count. Put full sanitized diagnostics behind a debug setting with retention limits. ## Reliability checklist - External key/idempotency key is stable per entry and feed. - Remote create operations become upsert or safely handle duplicate-key errors. - Connect/read timeout is bounded; no request can hold PHP indefinitely. - 2xx with malformed/negative business result is not marked success. - 4xx permanent failures and 429/5xx transient failures are classified. - Retry has a maximum attempt count and backoff; manual replay is safe. - A PHP exception/process death cannot leave an invisible permanent “processing” row without monitoring/recovery. - Deleting a form/feed/entry does not leave unsafe orphan work. - Logs and queue payloads contain no credentials. ## Free and Pro examples Use Free Mailchimp as the baseline for the controller/UI contract. Pro connectors can illustrate provider-specific mapping, but importing their namespaces or assuming their supporting routes/assets makes the addon Pro-dependent. Mark any such dependency in code, readme, activation checks, and tests.
-
-
SKILL.md 8.9 KB
--- name: fluentform-feed-integration description: >- Builds 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, Action Scheduler, result logging, credential handling, retries, and idempotency. Use when adding a CRM, webhook, messaging, storage, or external API connector; extending fluentform/get_available_form_integrations; handling fluentform/integration_notify_*; or reviewing an integration that currently sends remote requests directly from fluentform/submission_inserted. 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 feed integrations Use the Free-core feed manager when administrators need credentials, reusable per-form feeds, field mapping, conditions, logs, and background delivery. Use a plain `submission_inserted` listener only for small, non-configurable local work. Read [integration-contract.md](references/integration-contract.md) before implementing the manager class or deciding retry/idempotency behavior. ## Availability contract `FluentForm\App\Http\Controllers\IntegrationManagerController`, feed metadata, the notification manager, `ff_scheduled_actions`, and bundled Action Scheduler are Free-core surfaces in 6.2.7. Mailchimp is a Free reference implementation. Many shipped connectors under `fluentformpro/src/Integrations` are Pro-only, but their existence does not make a third-party integration manager require Pro. Use the current controller directly. These aliases are deprecated in 6.2.7: - `FluentForm\App\Services\Integrations\IntegrationManager` - `FluentForm\App\Services\Integrations\BaseIntegration` ## Decision point Use a feed manager when at least one applies: - admins create multiple destinations or mappings per form; - delivery has form conditions or smart codes; - credentials need a global connection screen; - delivery should run asynchronously and appear in integration logs; - a failed/replayed request needs an idempotency contract. Use a direct hook for bounded local metadata/state changes with no settings UI. Do not build a feed abstraction around a single pure calculation. ## Registration workflow 1. Bootstrap once on `fluentform/loaded`; require the controller class. 2. Choose three stable identifiers: - integration key for addon/global UI; - namespaced option key for connection settings; - feed settings key stored in `fluentform_form_meta` and used in the dynamic notification hook. 3. Extend `IntegrationManagerController`, call the parent constructor, set the description/logo/category, then call `registerAdminHooks()`. 4. Implement global settings and verify credentials server-side before setting `status => true`. 5. Implement integration availability, feed defaults, settings fields, and merge fields. Keep `enabled` and `conditionals` in the feed schema. 6. Implement `notify($feed, $formData, $entry, $form)` as an idempotent operation. 7. Report every terminal result through `fluentform/integration_action_result`. 8. Test disabled, unconfigured, condition-false, sync, async, timeout, retry, and duplicate delivery paths. ## Bootstrap ```php use FluentForm\App\Http\Controllers\IntegrationManagerController; add_action('fluentform/loaded', static function ($app): void { if (!class_exists(IntegrationManagerController::class)) { return; } new Acme_FluentForm_Integration($app); }, 20, 1); ``` The constructor should use stable, namespaced keys: ```php parent::__construct( $app, __('Acme CRM', 'acme-addon'), 'acme_crm', '_acme_ff_crm_settings', 'acme_crm_feeds', 20 ); $this->description = __('Send selected entries to Acme CRM.', 'acme-addon'); $this->category = 'crm'; $this->logo = plugins_url('assets/acme.svg', ACME_ADDON_FILE); $this->registerAdminHooks(); ``` Do not change these keys after release without migrating the global option and all form-meta feed rows. ## Notification contract ```php public function notify($feed, $formData, $entry, $form) { $entryId = (int) $entry->id; $values = isset($feed['processedValues']) && is_array($feed['processedValues']) ? $feed['processedValues'] : []; try { $result = $this->client()->upsertContact([ 'external_key' => 'ff-entry-' . $entryId, 'email' => sanitize_email((string) ($values['fieldEmailAddress'] ?? '')), ]); if (empty($result['ok'])) { throw new \RuntimeException('Remote service rejected the request.'); } do_action( 'fluentform/integration_action_result', $feed, 'success', __('Delivered to Acme CRM.', 'acme-addon') ); } catch (\Throwable $error) { do_action( 'fluentform/integration_action_result', $feed, 'failed', __('Acme CRM delivery failed.', 'acme-addon') ); // Log bounded, redacted diagnostics; never expose credentials or payloads. } } ``` `processedValues` contains the feed settings after Fluent Forms smart-code parsing. `$formData` is the stored response data, and `$entry` is its parsed entry view. Map explicit keys; do not forward the complete arrays by default. ## Async and failure semantics Feeds default to asynchronous delivery. Fluent Forms writes a row to `ff_scheduled_actions`, queues `fluentform/schedule_feed` through Action Scheduler, marks the row `processing`, then dispatches `fluentform/integration_notify_{settingsKey}`. ```php add_filter( 'fluentform/notifying_async_acme_crm', static fn($async, $formId) => true, 10, 2 ); ``` The filter suffix is the integration key, while the notify action suffix is the feed settings key. Do not interchange them. Do not advertise automatic delivery guarantees that the implementation does not provide. In 6.2.7 the queue increments `retry_count` and marks `processing`, but the integration callback must still record a terminal result, and exception or process-death recovery needs explicit testing. Use a stable remote idempotency key derived from the entry/feed, bounded timeouts, and a documented retry policy. ## Security and data rules - Store credentials only in the global option, with autoload disabled. Never copy API keys into per-form feed values, localized JavaScript, submission meta, or logs. Mask secrets when returning global settings to the UI. - Verify credentials with a bounded server-side request before marking the connection configured. Use TLS verification and an allowlisted service origin. - Sanitize global/feed settings on save. Escape labels/help HTML for its exact admin rendering context. - If adding custom AJAX/REST routes, implement nonce/authentication, Fluent Forms capabilities, form-level authorization, and object-level ownership yourself. - Never send password fields, payment tokens, file-system paths, hidden control keys, IP addresses, or the entire entry unless explicitly required and lawful. - Validate mapped email/URL/ID types after smart-code expansion. - Treat provider error bodies as untrusted and redact before logs or UI output. - Keep `notify()` idempotent; Action Scheduler/manual retry or network ambiguity can deliver the same entry more than once. ## Pro boundary Custom feed infrastructure is Free. A connector is Pro-dependent only when it uses a Pro class, field, payment object, user-registration feed, post feed, or other Pro-only capability. Guard that exact dependency with `class_exists()` or `method_exists()` and provide a clear disabled state in the integration UI. ## Cross-references - Use `fluentform-submission-lifecycle` for feed dispatch timing. - Use `fluentform-entries-data` for entry fields, meta, and permissions. ## References - Official Integration Manager Controller documentation: <https://developers.fluentforms.com/api/classes/integration-manager-controller/> - Official integration hooks: <https://developers.fluentforms.com/hooks/actions/integration/> - Verified Free source paths: - `fluentform/app/Http/Controllers/IntegrationManagerController.php` - `fluentform/app/Services/Integrations/FormIntegrationService.php` - `fluentform/app/Hooks/Handlers/GlobalNotificationHandler.php` - `fluentform/app/Services/Integrations/GlobalNotificationService.php` - `fluentform/app/Services/WPAsync/FluentFormAsyncRequest.php` - `fluentform/app/Services/Integrations/MailChimp/MailChimpIntegration.php` - Verified Pro examples, required only for their features: - `fluentformpro/src/Integrations/ActiveCampaign/Bootstrap.php` - `fluentformpro/src/Integrations/WebHook/Bootstrap.php`
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.