Contact Form 7 has a hook system that is more capable than most developers realise. The documentation is thin, but the hooks cover custom validation, pre-send manipulation, post-send processing, and custom form tags. This is a working reference for the most useful ones.
Custom Field Validation
The wpcf7_validate hook fires for each field type. To add custom validation to a text field:
add_filter('wpcf7_validate_text', 'validate_uk_postcode', 10, 2);
add_filter('wpcf7_validate_text*', 'validate_uk_postcode', 10, 2);
function validate_uk_postcode($result, $tag) {{
if ($tag->name !== 'postcode') {{
return $result;
}}
$value = isset($_POST[$tag->name]) ? trim($_POST[$tag->name]) : '';
$pattern = '/^[A-Z]{{1,2}}[0-9][0-9A-Z]?s?[0-9][A-Z]{{2}}$/i';
if ($value && !preg_match($pattern, $value)) {{
$result->invalidate($tag, 'Please enter a valid UK postcode.');
}}
return $result;
}}
The hook name follows the pattern wpcf7_validate_[field_type]. The asterisk version (wpcf7_validate_text*) covers required text fields. Always hook both to cover optional and required variants of the same field type.
Conditional Required Fields
CF7 does not have conditional logic built in, but you can make a field required based on another field’s value using validation hooks:
add_filter('wpcf7_validate_text', 'conditionally_require_company', 10, 2);
function conditionally_require_company($result, $tag) {{
if ($tag->name !== 'company_name') {{
return $result;
}}
$account_type = isset($_POST['account_type']) ? $_POST['account_type'] : '';
$company_name = isset($_POST['company_name']) ? trim($_POST['company_name']) : '';
if ($account_type === 'business' && empty($company_name)) {{
$result->invalidate($tag, 'Company name is required for business accounts.');
}}
return $result;
}}
Need this built properly? Describe the project and get a free estimate.
Modifying the Email Before It Sends
The wpcf7_before_send_mail hook fires after validation passes but before the email is sent. Use it to add data, modify recipients, or add CC/BCC based on form values:
add_action('wpcf7_before_send_mail', 'route_cf7_by_department');
function route_cf7_by_department($contact_form) {{
// Only affect a specific form by ID
if ($contact_form->id() !== 42) {{
return;
}}
$submission = WPCF7_Submission::get_instance();
if (!$submission) return;
$posted = $submission->get_posted_data();
$department = isset($posted['department']) ? $posted['department'] : '';
$mail = $contact_form->prop('mail');
switch ($department) {{
case 'sales':
$mail['recipient'] = 'sales@example.com';
break;
case 'support':
$mail['recipient'] = 'support@example.com';
break;
default:
$mail['recipient'] = 'info@example.com';
}}
$contact_form->set_properties(array('mail' => $mail));
}}
Saving Submissions to the Database
CF7 does not store submissions by default. To save to a custom table on successful submission:
add_action('wpcf7_mail_sent', 'save_cf7_submission_to_db');
function save_cf7_submission_to_db($contact_form) {{
if ($contact_form->id() !== 42) return;
$submission = WPCF7_Submission::get_instance();
if (!$submission) return;
$data = $submission->get_posted_data();
global $wpdb;
$wpdb->insert(
$wpdb->prefix . 'contact_submissions',
array(
'form_id' => $contact_form->id(),
'name' => sanitize_text_field($data['your-name'] ?? ''),
'email' => sanitize_email($data['your-email'] ?? ''),
'message' => sanitize_textarea_field($data['your-message'] ?? ''),
'created_at' => current_time('mysql'),
),
array('%d', '%s', '%s', '%s', '%s')
);
}}
You need to create the table first, typically in a plugin activation hook or a Code Snippets “run once” snippet. Alternatively, use Flamingo (from the CF7 author) which handles submission storage without custom code.
Adding a Custom Form Tag
CF7 lets you register custom shortcode-style tags for form fields. A simple custom tag that renders a hidden field with a server-generated value:
add_action('wpcf7_init', 'register_cf7_session_token_tag');
function register_cf7_session_token_tag() {{
wpcf7_add_form_tag('session_token', 'render_cf7_session_token');
}}
function render_cf7_session_token($tag) {{
$token = wp_create_nonce('cf7_submission_' . session_id());
return '';
}}