Developer hooks

SubtleForms uses WordPress filters and actions, all namespaced with subtleforms/. These are the ones most add-ons need.

subtleforms/payments/quote

Filter the computed order before it is shown or charged. The quote contains currency, items, subtotal, discount, coupon, tax_rate, tax and total.

add_filter( 'subtleforms/payments/quote', function ( array $quote, array $schema, array $payload ) {
	// Add a handling fee to every order over 100.
	if ( $quote['subtotal'] > 100 ) {
		$quote['items'][] = array( 'key' => 'handling', 'label' => 'Handling', 'quantity' => 1, 'amount' => 5.0 );
		$quote['total']  += 5.0;
	}
	return $quote;
}, 10, 3 );

If you change line items, keep total consistent with them, because the total is what gets charged.

subtleforms/payments/validate_coupon

The last word on whether a coupon applies. Receives the coupon array or a WP_Error, the form ID and the order subtotal. Return the coupon to accept it or a WP_Error to refuse it.

add_filter( 'subtleforms/payments/validate_coupon', function ( $coupon, int $form_id, float $subtotal ) {
	if ( is_wp_error( $coupon ) ) {
		return $coupon;
	}
	if ( 'MEMBERS' === $coupon['code'] && ! is_user_logged_in() ) {
		return new WP_Error( 'members_only', __( 'Log in to use this coupon.', 'my-plugin' ) );
	}
	return $coupon;
}, 10, 3 );

subtleforms/payment/verify

Gateways use this filter to confirm the visitor’s payment. Return an array with status (authorized, paid or pending), gateway and transaction_id, or a WP_Error to reject the submission. Returning null means no gateway handled it, and the entry is stored as payment pending.

add_filter( 'subtleforms/payment/verify', function ( $result, array $quote, array $payload, int $form_id, $schema ) {
	if ( null !== $result || empty( $payload['_payment']['my_gateway_token'] ) ) {
		return $result;
	}
	$auth = my_gateway_authorize( $payload['_payment']['my_gateway_token'], $quote['total'], $quote['currency'] );
	if ( ! $auth['ok'] ) {
		return new WP_Error( 'payment_declined', $auth['message'] );
	}
	return array( 'status' => 'authorized', 'gateway' => 'my_gateway', 'transaction_id' => $auth['id'] );
}, 10, 5 );

subtleforms/payment/capture

Runs after the entry has been stored. Capture an authorised payment here and return the updated payment array, or a WP_Error if the capture failed.

add_filter( 'subtleforms/payment/capture', function ( array $payment, int $submission_id ) {
	if ( 'my_gateway' !== ( $payment['gateway'] ?? '' ) || 'authorized' !== $payment['status'] ) {
		return $payment;
	}
	$capture = my_gateway_capture( $payment['transaction_id'] );
	if ( ! $capture['ok'] ) {
		return new WP_Error( 'capture_failed', $capture['message'] );
	}
	$payment['status'] = 'paid';
	return $payment;
}, 10, 2 );

subtleforms/payment/void

Fires when the submission failed after the gateway authorised the payment. Release the hold here so the visitor is not charged.

add_action( 'subtleforms/payment/void', function ( array $payment ) {
	if ( 'my_gateway' === ( $payment['gateway'] ?? '' ) ) {
		my_gateway_release( $payment['transaction_id'] );
	}
} );

subtleforms/frontend/enqueued

Fires after the public form assets are enqueued, so add-ons such as a payment gateway can load their own scripts alongside. Receives the plugin settings object, or null.

add_action( 'subtleforms/frontend/enqueued', function ( $settings ) {
	wp_enqueue_script( 'my-gateway', plugins_url( 'gateway.js', __FILE__ ), array( 'subtleforms-frontend' ), '1.0.0', true );
} );

subtleforms/fields/register

Register a custom field type. Receives the field registry. Each type is described by a FieldDefinition, and a type can only be registered once.

use SubtleForms\Fields\FieldDefinition;

add_action( 'subtleforms/fields/register', function ( $registry ) {
	$registry->register(
		new FieldDefinition(
			type: 'vat_number',
			label: __( 'VAT number', 'my-plugin' ),
			category: 'basic',
			icon: 'dashicons-id',
			kind: 'input',
			baseAttributes: array(),
			fieldSpecificAttributes: array( 'placeholder' => '' ),
			inspectorControls: array(
				array( 'type' => 'text', 'name' => 'label', 'label' => __( 'Label', 'my-plugin' ) ),
			)
		)
	);
} );

Hooks marked with a version in the source are stable from that version on. If you need a hook that does not exist yet, email support@subtleforms.com.

Something unclear or out of date? Email support@subtleforms.com and we will fix it.