> For the complete documentation index, see [llms.txt](https://kb.craftformswp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kb.craftformswp.com/documentation/for-developers/custom-submit-action-submit-action-context-bus.md).

# Custom Submit Action / Submit-Action Context Bus

Submit actions run in order, but were historically isolated — a later action could not use data produced by an earlier one. The **context bus** is a shared value holder passed through the whole submit-action chain so, for example, a `webhook` action can expose its JSON response to a later `send_email_template` action.

### Overview <a href="#overview" id="overview"></a>

* One `CraftForms\Submit_Action_Context` object is created per submission and passed (by object handle) to every action's `execute()` as a 5th argument.
* Keys are flat **dot-notation** strings (e.g. `webhook.order_id`). Only scalar leaf values are stored; nested arrays/objects are recursively flattened by `add()`.
* After each action runs, the manager applies the `craftforms_submit_action_context` filter so actions/integrations can publish their own data.
* When rendering an email template, the bus is merged into the template variable pool, so `{{webhook.*}}` (or any prefix you choose) tokens resolve.

### API — `CraftForms\Submit_Action_Context` <a href="#api--craftformssubmit_action_context" id="api--craftformssubmit_action_context"></a>

| Method                                       | Description                                                                 |
| -------------------------------------------- | --------------------------------------------------------------------------- |
| `get(string $key, $default = null)`          | Read a single flat value.                                                   |
| `all(): array`                               | The full flat `key => scalar` map.                                          |
| `set(string $key, $value)`                   | Set one flat key (non-scalars ignored).                                     |
| `add(string $prefix, array\|object $values)` | Recursively flatten `$values` under `$prefix`; only scalar leaves are kept. |

Example:

```php
$context->add('webhook', ['order' => ['id' => 42, 'total' => '19.00']]);
$context->get('webhook.order.id'); // 42
```

### Writing to the bus from an action <a href="#writing-to-the-bus-from-an-action" id="writing-to-the-bus-from-an-action"></a>

Any action's `execute()` may accept the optional 5th `$context` parameter and write to it:

```php
public function execute($form_post, $data, $settings, $form_meta, $context = null) {
    // ... do work ...
    if ($context instanceof \CraftForms\Submit_Action_Context) {
        $context->add('mycrm', ['contact_id' => $contact_id]);
    }
    return true;
}
```

Actions that do **not** declare the 5th parameter are unaffected — the surplus argument is silently ignored by PHP.

### Hooks <a href="#hooks" id="hooks"></a>

| Hook                                  | Type   | Signature                                                |
| ------------------------------------- | ------ | -------------------------------------------------------- |
| `craftforms_submit_action_context`    | filter | `($context, $action_config, $result, $form_post, $data)` |
| `craftforms_email_template_context`   | filter | `($flat_context, $template_id, $form_post, $data)`       |
| `craftforms_webhook_response_context` | filter | `($json, $settings, $form_post, $data)`                  |

* `craftforms_submit_action_context` — fired after every action; return the (possibly modified) context object. Use it to publish data without modifying the action.
* `craftforms_email_template_context` — fired right before an email template's subject/recipient/body are parsed; add or override template variables.
* `craftforms_webhook_response_context` — filter the decoded webhook JSON before it is added to the bus.

### Webhook action settings <a href="#webhook-action-settings" id="webhook-action-settings"></a>

The built-in **Send Webhook** action gained two optional settings:

* `context_prefix` (text, default `webhook`) — on a `2xx` JSON response, the decoded body is flattened onto the bus under this prefix, exposing `{{webhook.*}}` tokens to later actions. Empty/non-JSON/oversized (>256 KB) responses are skipped silently; the action still succeeds.
* `required` (checkbox, default off) — if the webhook fails, abort the remaining submit actions and surface the error to the visitor.

On a non-`2xx` response the action now extracts a human-readable message from common JSON error shapes (`{"message"}`, `{"data":{"errorMsg"}}`, `{"error"}`) and attaches the HTTP status to the returned `WP_Error` (`['status' => $code]`).

### Required actions (hard gates) <a href="#required-actions-hard-gates" id="required-actions-hard-gates"></a>

Any action config carrying a truthy `required` or `abort_on_failure` flag (top-level or in `settings`) becomes a hard gate: on failure the chain stops and the error is surfaced. This generalizes the previously hard-coded fatal actions (`create_order`, `wc_add_to_cart`).

### Safety notes <a href="#safety-notes" id="safety-notes"></a>

* Backward compatible: no DB schema changes; existing 4-argument `execute()` implementations keep working.
* Only scalar leaves are stored — objects/resources are dropped during flattening.
* Webhook response bodies larger than 256 KB are not parsed onto the bus.
