Custom Actions
Create custom actions that execute when automation recipes fire.
Creating Custom Actions
An action defines what happens when a recipe triggers and all conditions pass.
Complete Example
This example creates an action that sends a Slack notification:
<?php
// File: actions/slack-notify-action.php
if ( ! defined( 'ABSPATH' ) ) exit;
class IW_SlackNotify_Action extends IW_Automation_Action {
/**
* Display name in the action dropdown.
*/
function get_title() {
return 'Send Slack Notification';
}
/**
* Restrict which triggers this action works with.
* Omit to allow all triggers.
*/
function allowed_triggers() {
return array(
'IW_Purchase_Trigger',
'IW_OrderStatusChange_Trigger',
'IW_OrderCreation_Trigger',
);
}
/**
* Define the admin form fields.
*/
function get_field_schema() {
return array(
array(
'type' => 'text',
'name' => 'webhook_url',
'label' => 'Slack Webhook URL',
'placeholder' => 'https://hooks.slack.com/services/...',
),
array(
'type' => 'select',
'name' => 'channel',
'label' => 'Channel',
'options' => array(
array('value' => '#orders', 'label' => '#orders'),
array('value' => '#general', 'label' => '#general'),
array('value' => 'custom', 'label' => 'Custom channel...'),
),
'default_value' => '#orders',
),
array(
'type' => 'text',
'name' => 'custom_channel',
'label' => 'Custom Channel',
'placeholder' => '#my-channel',
'visible_when' => array('channel' => 'custom'),
),
array(
'type' => 'textarea',
'name' => 'message',
'label' => 'Message',
'merge_field' => true,
'placeholder' => 'New order {{WCOrder:OrderId}} from {{WPUser:user_email}}',
),
);
}
/**
* Validate configuration.
*/
function validate_entry($config) {
if (empty($config['webhook_url'])) {
return 'Please enter the Slack webhook URL.';
}
if (!preg_match('#^https://hooks\.slack\.com/#', $config['webhook_url'])) {
return 'Please enter a valid Slack webhook URL.';
}
if (empty($config['message'])) {
return 'Please enter a message.';
}
}
/**
* Execute the action at runtime.
*
* @param array $config The saved configuration
* @param object $trigger The trigger instance
*/
function process($config, $trigger) {
// IMPORTANT: Resolve merge fields before using text values
$message = $trigger->merger->merge_text($config['message']);
$channel = $config['channel'];
if ($channel === 'custom') {
$channel = $config['custom_channel'];
}
// Send to Slack
wp_remote_post($config['webhook_url'], array(
'headers' => array('Content-Type' => 'application/json'),
'body' => json_encode(array(
'channel' => $channel,
'text' => $message,
)),
'timeout' => 10,
));
}
}
// Register with InfusedWoo
iw_add_action_class('IW_SlackNotify_Action');
Required Methods
| Method | Description |
|---|---|
get_title() | Name shown in the admin |
process($config, $trigger) | Execute the action at runtime |
Required for Admin Support
| Method | Description |
|---|---|
get_field_schema() | Define form fields. Without this, the action shows as “legacy” (non-editable). |
validate_entry($config) | Validate config. Return error string if invalid. |
Optional Methods
| Method | Description |
|---|---|
allowed_triggers() | Array of trigger class names this action works with |
on_class_load() | Called once when the class loads. Use for hooking into frontend actions (cart modifications, etc.) |
Resolving Merge Fields
Always resolve merge fields in text values before using them:
function process($config, $trigger) {
// WRONG -- contains raw {{WPUser:first_name}}
$name = $config['name'];
// RIGHT -- resolved to actual value like "John"
$name = $trigger->merger->merge_text($config['name']);
}
The merge_text() method replaces all {{Group:Key}} tokens with their actual values.
The Trigger Object
In process($config, $trigger), you can access:
| Property/Method | Description |
|---|---|
$trigger->user_email | Contact email |
$trigger->wp_user_id | WordPress user ID |
$trigger->infusion_contact_id | Keap contact ID |
$trigger->pass_vars | Event data array from the trigger |
$trigger->merger->merge_text($text) | Resolve merge fields in a string |
$trigger->search_infusion_contact_id($create) | Find (or create) Keap contact |
Default Values
Pre-populate fields when the action is first added to a recipe:
array('type' => 'text', 'name' => 'timeout', 'label' => 'Timeout (seconds)', 'default_value' => '30')
For dynamic defaults (e.g., from the database):
function get_field_schema() {
$default_url = get_option('my_plugin_webhook_url', '');
return array(
array('type' => 'text', 'name' => 'url', 'label' => 'URL', 'default_value' => $default_url),
);
}
Actions with Frontend Side Effects
If your action needs to modify the WooCommerce frontend (cart, checkout), use on_class_load():
function on_class_load() {
add_action('woocommerce_before_checkout_form', array($this, 'show_banner'));
}
function process($config, $trigger) {
// Store data in session for the frontend hook to read
WC()->session->set('my_banner_message', $trigger->merger->merge_text($config['message']));
}
function show_banner() {
$msg = WC()->session->get('my_banner_message');
if ($msg) {
echo '<div class="woocommerce-info">' . esc_html($msg) . '</div>';
}
}
Tips
- Keep
process()fast — it runs synchronously during the WordPress request - For heavy processing (API calls, file operations), consider using
wp_schedule_single_event()to defer - Use
wp_remote_post()/wp_remote_get()for external API calls (notcurldirectly) - Test your action using the “Manually Trigger” feature in the recipe editor