Skip to main content

Custom Actions

Create your own form actions by extending the Action base class. Custom actions appear in the form editor's Actions dropdown and can be configured per form.

Action Base Class

Every action extends Core_Forms\Actions\Action and implements two methods:

namespace Core_Forms\Actions;

use Core_Forms\Form;
use Core_Forms\Submission;

abstract class Action {
    public $type  = '';   // Unique identifier
    public $label = '';   // Display name in admin

    abstract function page_settings( $settings, $index );
    abstract function process( array $settings, Submission $submission, Form $form );
}

Registration

The hook() method registers three WordPress hooks:

  1. cf_available_form_actions -- Adds the action to the dropdown
  2. cf_output_form_action_{type}_settings -- Renders the settings UI
  3. cf_process_form_action_{type} -- Processes the action on submission
$action = new MyAction();
$action->hook();

Complete Example: CRM Integration

<?php
namespace My_Plugin\Actions;

use Core_Forms\Actions\Action;
use Core_Forms\Form;
use Core_Forms\Submission;

class CrmAction extends Action {

    public $type  = 'my_crm';
    public $label = 'Send to CRM';

    public function __construct() {
        $this->label = __( 'Send to CRM', 'my-plugin' );
    }

    /**
     * Render the action settings in the form editor.
     *
     * @param array      $settings Current saved settings.
     * @param string|int $index    Action index (for field names).
     */
    public function page_settings( $settings, $index ) {
        $defaults = [
            'api_url'    => '',
            'api_key'    => '',
            'list_id'    => '',
            'email_field'=> 'email',
            'name_field' => 'name',
        ];
        $settings = array_merge( $defaults, $settings );
        ?>
        <span class="cf-action-summary">
            <?php printf( 'CRM: %s', esc_html( $settings['api_url'] ?: 'Not configured' ) ); ?>
        </span>
        <input type="hidden"
               name="form[settings][actions][<?php echo $index; ?>][type]"
               value="<?php echo $this->type; ?>" />

        <table class="form-table">
            <tr>
                <th><label><?php _e( 'API URL', 'my-plugin' ); ?></label></th>
                <td>
                    <input name="form[settings][actions][<?php echo $index; ?>][api_url]"
                           value="<?php echo esc_attr( $settings['api_url'] ); ?>"
                           type="url" class="regular-text" required />
                </td>
            </tr>
            <tr>
                <th><label><?php _e( 'API Key', 'my-plugin' ); ?></label></th>
                <td>
                    <input name="form[settings][actions][<?php echo $index; ?>][api_key]"
                           value="<?php echo esc_attr( $settings['api_key'] ); ?>"
                           type="text" class="regular-text" required />
                </td>
            </tr>
            <tr>
                <th><label><?php _e( 'List ID', 'my-plugin' ); ?></label></th>
                <td>
                    <input name="form[settings][actions][<?php echo $index; ?>][list_id]"
                           value="<?php echo esc_attr( $settings['list_id'] ); ?>"
                           type="text" class="regular-text" />
                </td>
            </tr>
            <tr>
                <th><label><?php _e( 'Email Field', 'my-plugin' ); ?></label></th>
                <td>
                    <input name="form[settings][actions][<?php echo $index; ?>][email_field]"
                           value="<?php echo esc_attr( $settings['email_field'] ); ?>"
                           type="text" class="regular-text" placeholder="email" />
                </td>
            </tr>
            <tr>
                <th><label><?php _e( 'Name Field', 'my-plugin' ); ?></label></th>
                <td>
                    <input name="form[settings][actions][<?php echo $index; ?>][name_field]"
                           value="<?php echo esc_attr( $settings['name_field'] ); ?>"
                           type="text" class="regular-text" placeholder="name" />
                </td>
            </tr>
        </table>
        <?php
    }

    /**
     * Process the action when the form is submitted.
     *
     * @param array      $settings Action settings.
     * @param Submission $submission The submission.
     * @param Form       $form The form.
     */
    public function process( array $settings, Submission $submission, Form $form ) {
        if ( empty( $settings['api_url'] ) || empty( $settings['api_key'] ) ) {
            return false;
        }

        $data = $submission->data;

        $body = [
            'email'   => $data[ $settings['email_field'] ] ?? '',
            'name'    => $data[ $settings['name_field'] ] ?? '',
            'list_id' => $settings['list_id'],
            'source'  => home_url(),
            'form'    => $form->title,
        ];

        $response = wp_remote_post( $settings['api_url'], [
            'headers' => [
                'Authorization' => 'Bearer ' . $settings['api_key'],
                'Content-Type'  => 'application/json',
            ],
            'body'    => wp_json_encode( $body ),
            'timeout' => 15,
        ] );

        if ( is_wp_error( $response ) ) {
            throw new \Exception( $response->get_error_message() );
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( $code >= 400 ) {
            throw new \Exception( "CRM API returned HTTP {$code}" );
        }

        return true;
    }
}

Registering the Action

Register your action in your plugin's initialization:

add_action( 'init', function() {
    if ( class_exists( 'Core_Forms\Actions\Action' ) ) {
        $crm = new \My_Plugin\Actions\CrmAction();
        $crm->hook();
    }
} );

Settings Field Names

Action settings fields must follow this naming convention:

name="form[settings][actions][{$index}][{setting_key}]"

The $index parameter passed to page_settings() is the action's position in the actions array. Always include a hidden type field:

<input type="hidden"
       name="form[settings][actions][<?php echo $index; ?>][type]"
       value="my_crm" />

Error Handling

Throw exceptions in process() to signal failure. When running in the background queue, the ActionQueue catches exceptions and handles retry logic:

public function process( array $settings, Submission $submission, Form $form ) {
    // Throwing triggers retry with exponential backoff
    throw new \Exception( 'API connection failed' );
}

Testing Actions

Use the REST API test endpoint to run an action with sample data:

POST /wp-json/cf/v1/actions/test
{
    "action_type": "my_crm",
    "form_id": 123,
    "settings": {
        "api_url": "https://api.example.com/contacts",
        "api_key": "test-key",
        "email_field": "email"
    },
    "test_data": {
        "name": "Test User",
        "email": "test@example.com"
    }
}

Related