You have a Contact Form 7 form connected to an external API. The form submits. The plugin fires the API request. And then the API rejects it—because the email domain is blocked, the phone number format is wrong for that specific service, or the field value violates a business rule the API enforces.
The user sees a generic success message. The API log shows a failure. And you have no way to tell the user what went wrong before the request was even sent.
This is the validation gap. CF7 validates what it knows: required fields, email format, file size. It does not know your API's rules. Bridging that gap requires custom validation that runs before the API request fires.
Why CF7's Built-in Validation Is Not Enough
Contact Form 7 provides default validation for common field types. A required field must be filled. An email field must contain a valid email address. A file upload must be within size limits .
But API integrations have rules CF7 does not enforce:
- The email must belong to a business domain, not a free provider
- The phone number must match the format expected by the CRM
- The company name must exist in your ERP's customer database
- The budget field must be within a range the sales team can handle
If the API rejects the submission, the user has already seen a success message (or the form hangs, depending on the plugin's response handling). The validation happened too late.
The Solution: Validate Before the API Call Fires
The key insight is that validation must happen at the CF7 submission stage, before the connector plugin sends data to the API. CF7 provides hooks that fire during its own validation process, giving you a chance to inject custom rules.
Approach 1: Custom Validation Filters (Legacy Method)
CF7 implements validation as a filter. The hook name follows the pattern wpcf7_validate_[field_type]. For a text field, it is wpcf7_validate_text. For a required text field, it is wpcf7_validate_text* .
A validation filter receives two parameters: the result object and the form tag. You check the field value and call $result->invalidate() if it fails your custom rule.
add_filter('wpcf7_validate_text*', 'validate_business_email_domain', 20, 2);
function validate_business_email_domain($result, $tag) {
if ($tag->name !== 'your-email') {
return $result;
}
$email = isset($_POST['your-email']) ? sanitize_email($_POST['your-email']) : '';
$free_providers = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com'];
$domain = substr(strrchr($email, "@"), 1);
if (in_array($domain, $free_providers)) {
$result->invalidate($tag, 'Please use your business email address.');
}
return $result;
}
This validation runs during CF7's submission process. If it fails, CF7 marks the submission as invalid and does not proceed to the mail-sent stage—which means the connector plugin never fires the API request.
Approach 2: Schema-Woven Validation (Modern Method)
CF7 5.9 and later use Schema-Woven Validation (SWV), a rule-based system that provides both client-side and server-side validation from a single schema . The wpcf7_swv_create_schema action lets you add custom rules.
The advantage of SWV is that validation rules are defined once and enforced on both the frontend and the backend. Client-side validation gives instant feedback. Server-side validation prevents bypass via direct POST requests.
add_action('wpcf7_swv_create_schema', function($schema, $contact_form) {
$rule = wpcf7_swv_create_rule('required', [
'field' => 'company-name',
'error' => 'Company name is required for business inquiries.',
]);
$schema->add_rule($rule);
}, 10, 2);
For conditional rules, SWV supports all and any composite rules. An all rule requires every sub-rule to pass. An any rule requires at least one . This is useful for validation that depends on multiple field values.
Validating Against the API Before Submitting
Sometimes the validation rule itself lives in the API. For example, you want to check if a phone number is already registered in your CRM before creating a new lead.
You can make a lightweight validation request to the API inside your validation filter. The filter runs synchronously, so the user waits for the result.
add_filter('wpcf7_validate_tel*', 'check_phone_against_api', 20, 2);
function check_phone_against_api($result, $tag) {
if ($tag->name !== 'your-phone') {
return $result;
}
$phone = isset($_POST['your-phone']) ? sanitize_text_field($_POST['your-phone']) : '';
$response = wp_remote_get('https://api.example.com/check-phone?number=' . urlencode($phone), [
'timeout' => 5,
'headers' => ['Authorization' => 'Bearer YOUR_TOKEN'],
]);
if (!is_wp_error($response)) {
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!empty($body['exists'])) {
$result->invalidate($tag, 'This phone number is already registered.');
}
}
return $result;
}
Caution: This adds latency to form submission. If the API is slow or unreachable, the form submission may time out. Use short timeouts and consider caching results where possible.
Conditional Validation: Only Validate When Necessary
Not every field needs validation on every submission. A budget field might only be required when the inquiry type is "Sales." A company name might only be required for business accounts.
CF7's SWV supports conditional logic through composite rules . But for simpler cases, the legacy filter approach with conditional checks works reliably.
add_filter('wpcf7_validate_text', 'conditionally_require_company', 10, 2);
function conditionally_require_company($result, $tag) {
if ($tag->name !== 'company-name') {
return $result;
}
$inquiry_type = isset($_POST['inquiry-type']) ? $_POST['inquiry-type'] : '';
$company = isset($_POST['company-name']) ? trim($_POST['company-name']) : '';
if ($inquiry_type === 'Business' && empty($company)) {
$result->invalidate($tag, 'Company name is required for business inquiries.');
}
return $result;
}
This pattern is more flexible than SWV for complex conditions that reference multiple fields or external data.
The Critical Timing Question: Before or After the API Call?
The connector plugin fires the API request at a specific point in CF7's submission lifecycle. If your validation filter runs before that point, a failed validation prevents the API call. If it runs after, the API call fires regardless.
CF7's validation filters (wpcf7_validate_*) run during the validation phase, before the wpcf7_before_send_mail action. The "Contact Form to Any API" plugin hooks into the submission process at a later stage. This means custom validation filters will block the API call if they fail.
If you need to validate after the API responds (for example, checking if the API accepted the data), you are working with a different problem: surfacing the API's response to the user. That requires a separate solution involving the wpcf7mailsent event or custom response handling.
The Debugging Checklist for Validation Issues
| Step | Action | What It Reveals |
|---|---|---|
| 1 | Submit a test form with invalid data | Does CF7 show your custom error message? |
| 2 | Check if the API request fired | If validation failed, the API call should not fire |
| 3 | Verify the hook name |
wpcf7_validate_text* for required, wpcf7_validate_text for optional |
| 4 | Check field name match | The $tag->name must match your CF7 field name exactly |
| 5 | Test with valid data | Does the form submit and the API call fire? |
| 6 | Check for plugin conflicts | Other CF7 extensions may alter validation behavior |
For a deeper breakdown of what happens when validation fails and no API request appears in the logs, see this guide on Contact Form 7 data not reaching the API.
Key Takeaways
CF7's built-in validation does not know your API's rules. Required, email format, and file size are not enough. Custom validation filters bridge the gap.
Validation must run before the API call fires. CF7's wpcf7_validate_* filters run during the validation phase, blocking submission if they fail.
SWV is the modern approach. Schema-Woven Validation provides client-side and server-side enforcement from a single rule definition.
Conditional validation prevents unnecessary friction. Only require fields when the form data actually demands them.
API-side validation adds latency. If you check against the API inside a validation filter, use short timeouts and cache results where possible.
With custom validation filters that run before the connector plugin fires, your form rejects invalid data at the source. The user gets a clear error message. The API never sees the bad request. And your CRM stays clean.
Top comments (0)