Hooks Reference
Core Forms provides filters and actions throughout the form lifecycle for customization.
Filters
cf_validate_form
Validate form data before processing. Return an error code string to block submission.
add_filter( 'cf_validate_form', function( $error_code, $form, $data ) {
// Pass through if another validator set an error
if ( '' !== $error_code ) {
return $error_code;
}
// Custom validation
if ( empty( $data['agree_terms'] ) ) {
return 'terms_required';
}
return ''; // Empty string = valid
}, 10, 3 );
Parameters: $error_code (string), $form (Form), $data (array)
cf_form_html
Modify the complete form HTML output (outer wrapper included).
add_filter( 'cf_form_html', function( $html, $form ) {
return '<div class="custom-wrapper">' . $html . '</div>';
}, 10, 2 );
Parameters: $html (string), $form (Form)
cf_form_markup
Modify the inner form markup (the HTML between <form> tags).
add_filter( 'cf_form_markup', function( $markup, $form ) {
// Append a disclaimer before the submit button
$disclaimer = '<p class="disclaimer">By submitting, you agree to our terms.</p>';
$markup = str_replace( '</form>', $disclaimer . '</form>', $markup );
return $markup;
}, 10, 2 );
Parameters: $markup (string), $form (Form)
cf_schema_markup
Modify the HTML generated from the form schema before it is used as form markup.
add_filter( 'cf_schema_markup', function( $markup, $schema, $form ) {
return $markup;
}, 10, 3 );
Parameters: $markup (string), $schema (FormSchema), $form (Form)
cf_form_default_settings
Set default values for form settings.
add_filter( 'cf_form_default_settings', function( $settings ) {
$settings['save_submissions'] = true;
$settings['redirect_url'] = '';
$settings['display_mode'] = 'normal';
return $settings;
} );
cf_form_default_messages
Set default form messages (success, error, validation).
add_filter( 'cf_form_default_messages', function( $messages ) {
$messages['success'] = 'Thank you! We will be in touch.';
$messages['error'] = 'Something went wrong. Please try again.';
return $messages;
} );
cf_form_field_length_limits
Filter server-enforced field character limits. Email fields remain capped at 254 characters after this filter runs.
add_filter( 'cf_form_field_length_limits', function ( $limits, $form ) {
$limits['message'] = 5000;
return $limits;
}, 10, 2 );
cf_max_submission_bytes
Lower the maximum encoded submission size. The default and hard ceiling are
60 KiB so stored JSON remains below the submission table's TEXT limit.
add_filter( 'cf_max_submission_bytes', function ( $bytes, $form, $data ) {
return 32 * 1024;
}, 10, 3 );
cf_process_form_action_{type}
Process a custom action type. This is an action hook (despite the filter-like name) fired via do_action.
add_action( 'cf_process_form_action_my_crm', function( $settings, $submission, $form ) {
// Send data to your CRM
wp_remote_post( $settings['api_url'], [
'body' => wp_json_encode( $submission->data ),
] );
}, 10, 3 );
cf_webhook_request_args
Modify the wp_remote_post arguments for webhook requests.
add_filter( 'cf_webhook_request_args', function( $args, $url, $submission ) {
$args['headers']['Authorization'] = 'Bearer ' . MY_API_KEY;
$args['timeout'] = 30;
return $args;
}, 10, 3 );
cf_upload_max_filesize
Change the maximum upload file size (default: 8,000,000 bytes / 8 MB).
add_filter( 'cf_upload_max_filesize', function() {
return 25 * 1024 * 1024; // 25 MB
} );
cf_create_user_allowed_roles
Restrict which roles the Create User action can assign.
add_filter( 'cf_create_user_allowed_roles', function( $roles ) {
return [ 'subscriber', 'contributor' ];
} );
cf_template_tags
Add custom template tags for {{tag.field}} syntax.
add_filter( 'cf_template_tags', function( $tags ) {
$tags['company'] = function( $field ) {
return get_option( "company_{$field}", '' );
};
return $tags;
} );
// Usage: {{company.name}}, {{company.phone}}
cf_duplicate_window_seconds
Since 4.9.0. How long the Prevent Duplicates setting remembers a submitted value-set. Defaults to one hour; return 0 to disable the check.
add_filter( 'cf_duplicate_window_seconds', function( $seconds, $form ) {
return 'support-request' === $form->slug ? 5 * MINUTE_IN_SECONDS : $seconds;
}, 10, 2 );
cf_fullscreen_is_supported
Since 4.9.0. Fullscreen mode rebuilds the rendered form as a one-question-at-a-time flow and has no awareness of payment fields or multi-step panels, so it refuses those forms rather than breaking them. Use this to refuse others.
add_filter( 'cf_fullscreen_is_supported', function( $supported, $form ) {
return $supported && empty( $form->settings['my_custom_widget'] );
}, 10, 2 );
cf_template_user_fields / cf_template_post_fields
Since 4.9.0. Template tags such as {{user.user_email}} and {{post.title}} resolve against an allowlist. Anything not on the list returns an empty string.
This is a security boundary, not a convenience: cf_template() also runs over submitted values, so before 4.9.0 a logged-in visitor who typed {{user.user_pass}} into any field had their password hash written into the submission, the CSV export and the notification email. Add fields with care.
add_filter( 'cf_template_user_fields', function( $fields ) {
$fields[] = 'billing_company';
return $fields;
} );
cf_available_form_actions
Register custom action types in the Actions dropdown.
add_filter( 'cf_available_form_actions', function( $actions ) {
$actions['my_action'] = 'My Custom Action';
return $actions;
} );
Actions
cf_form_success
Fires after a successful, non-spam form submission. This is the primary hook for post-submission processing.
add_action( 'cf_form_success', function( $submission, $form ) {
// Log, notify, integrate, etc.
error_log( "Form {$form->ID} submitted by {$submission->data['email']}" );
}, 10, 2 );
Parameters: $submission (Submission), $form (Form)
cf_submission_inserted
Fires immediately after a submission is inserted into the database, before actions run.
add_action( 'cf_submission_inserted', function( $submission, $form ) {
// $submission->id is now available
}, 10, 2 );
cf_submission_after_write
Since 4.9.0. Fires after any write to wp_cf_submissions, from every path that touches the table — the submit pipeline, admin edits, draft autosave, spam toggles, GDPR erasure, REST, payment status changes, WP-CLI, the importers and the data-management tools.
cf_submission_inserted only covers new rows from the front-end submit. Use this one when you need to stay consistent with the table itself — a cache, a counter, or a secondary index.
add_action( 'cf_submission_after_write', function( $submission_id, $context, $submission, $form ) {
// $context: submit | admin_edit | draft | delete | status | spam
// payment | gdpr | rest | cli | import | data_management | license_commerce
my_reindex_submission( $submission_id );
}, 10, 4 );
Parameters: $submission_id (int), $context (string), $submission (Submission|null), $form (Form|null)
Deleted rows fire this too — check whether the row still exists before reading it.
cf_submission_blocked
Since 4.9.0. Fires when a submission attempt is refused before it becomes a row. Carries a reason code only — never field values.
add_action( 'cf_submission_blocked', function( $form, $reason ) {
// $reason: honeypot_missing | honeypot_filled
// honeypot_missing usually means a page cache is stripping hidden
// fields from real visitors' forms, not that spam was stopped.
}, 10, 2 );
cf_submission_insert_failed
Since 4.9.0. Fires when the database write for a submission fails. Before 4.9.0 the failure was swallowed and the visitor still saw a success message.
add_action( 'cf_submission_insert_failed', function( $submission, $form, $error ) {
error_log( 'Core Forms lost a submission: ' . $error );
}, 10, 3 );
cf_action_skipped
Since 4.9.0. Fires when a configured action is deliberately not run.
add_action( 'cf_action_skipped', function( $form, $action_settings, $submission, $reason ) {
// $reason: conditions_not_met
}, 10, 4 );
cf_form_response
Fires when the form response is being prepared, including for spam.
add_action( 'cf_form_response', function( $form, $submission, $error_code ) {
if ( $error_code === 'spam' ) {
// Track spam attempt
}
}, 10, 3 );
cf_process_form
Fires during form processing, before validation.
add_action( 'cf_process_form', function( $form, $submission ) {
// Modify submission data before processing
}, 10, 2 );
cf_fullscreen_head
Output content in the <head> of fullscreen form pages.
cf_fullscreen_footer
Output content before </body> of fullscreen form pages.
4.1 additions
cf_can_view_form_analytics
Gate per-form analytics REST endpoints on something stricter than the
edit_forms capability — for example, post-author ownership.
add_filter( 'cf_can_view_form_analytics', function ( $allowed, $form_id ) {
$post = get_post( $form_id );
return $post && (int) $post->post_author === get_current_user_id();
}, 10, 2 );
Parameters: $allowed (bool), $form_id (int), $form (Form|null)
cf_submit_rate_limit
Override the per-IP-per-hour submission cap for a form. Returning 0
disables the limit; the default is 0 (unlimited) unless the form's
rate_limit setting is non-zero.
add_filter( 'cf_submit_rate_limit', function ( $limit, $form ) {
return $form->slug === 'contact' ? 5 : $limit;
}, 10, 2 );
cf_draft_ttl_days
Days before an unfinished draft is purged by the daily
cf_cleanup_drafts cron. Default 30. Return 0 to disable cleanup.
cf_analytics_retention_days
Days to keep tracker rows before the daily cf_cleanup_analytics cron
deletes them. Default 365. Return 0 to disable retention.
cf_action_queue_stale_minutes
Minutes a row may sit in the workflow queue's processing status before
it's considered stuck and requeued by ActionQueue::recover_stale().
Default 10.
cf_payments_orchestrator
Returns the singleton Payments orchestrator so you can reach the
registered gateways from elsewhere in the codebase. Returns null if
called before init.
$payments = apply_filters( 'cf_payments_orchestrator', null );
$stripe = $payments?->get_gateway( 'stripe' );
cf_payment_completed
Fires once a webhook confirms a payment for a submission. Use it for
behaviour that must only happen on paid submissions (free submissions
fire cf_form_success directly, without going through this hook).
add_action( 'cf_payment_completed', function ( $submission, $form ) {
// ... your code here ...
}, 10, 2 );