Phone Number Validation for WooCommerce checks the phone numbers your customers enter, formats them consistently, and stores them with the country dial code attached. It works on classic checkout, block checkout, the My Account address pages, and orders you create in WP Admin. This page covers the requirements, every setting, a few common setups, and the developer hooks.
Overview
↑ Back to topPhone Number Validation for WooCommerce adds country-aware validation to the phone fields in your store. Each phone field gets a country flag selector, an optional dial code, and live validation that tells the customer when a number is too short, too long, or not valid for the selected country. Numbers are saved to the order in E.164 format, so a number entered as 1 57 27 65 67 in a French address is stored as +33157276567. You can choose how those stored numbers are shown on your order screens and emails, without changing what is stored.
The plugin also adds phone numbers to WooCommerce order emails, puts Call, WhatsApp, SMS, and Copy buttons on admin order screens, and warns you when the same phone number appears on orders from different customers. It uses the bundled intl-tel-input library (by Twilio) for the country data and formatting, so no external service or API key is involved.
Requirements
↑ Back to topYour site needs to meet the following before the plugin will run:
- WordPress 6.4 or higher
- WooCommerce 9.0 or higher
- PHP 7.4 or higher
- WooCommerce active. WordPress will not activate the plugin without it.
- Your site connected to WooCommerce.com with an active subscription for this product. Without one, a license notice appears in WP Admin and the Settings link on the Plugins screen is replaced with a link to renew.
Block checkout has one extra requirement. If your checkout page uses the WooCommerce Checkout block, the phone field must be enabled in that block. When it is turned off, the plugin shows an admin notice with a link to edit the block, because there is no field for it to validate.
There are no extra installation steps for this plugin. Once it is active, go to WooCommerce > Settings > Shipping > Phone Validation to configure it, or use the Settings link next to the plugin on the Plugins screen. The defaults are usable straight away: validation runs on every phone field and checkout is never blocked.
Usage
↑ Back to topAll settings live on one screen, split into four sections. Everything else the plugin does happens automatically on the storefront and in your order screens.
Where to find the settings
↑ Back to top
Navigate to WooCommerce > Settings > Shipping and choose Phone Validation from the section links at the top of the Shipping tab. The screen has four sections: General, Email, Admin Orders, and Advanced. Changes are saved with the Save Settings button at the bottom of the screen, and a confirmation appears once they are stored.
General
↑ Back to top
This first section controls the phone fields themselves. It is grouped into Country Setup, Shipping phone number, and Miscellaneous.
Country Setup
- Default country code is the flag and dial code the phone field falls back to when it has nothing better to go on. Choose the country most of your customers order from. Default is United States. For logged-in customers with a saved billing country, that saved country wins over this setting.
- Preferred countries are the countries pinned to the top of the country dropdown, above the alphabetical list. Useful when you sell worldwide but most orders come from two or three places. Defaults to United States and United Kingdom.
- Exclude countries removes countries from the dropdown entirely. Customers cannot select them and numbers from them will not validate. Defaults to Russia, so change this if you do sell there.
- Phone number display format sets how saved numbers appear on your order screens, in order emails, and on the order confirmation and My Account order pages. Choose E.164 to show the stored value as it is (
+15551231212), International for the spaced international format (+1 555-123-1212), or National for the local format ((555) 123-1212). This changes presentation only: every number is still stored in E.164, and the value handed to other plugins, SMS gateways, and your carrier is unchanged. Defaults to E.164. - Existing number input format is the same idea for editable fields. When a phone field is prefilled with a number you already have on file — a returning customer at checkout, or an order or user record you open in WP Admin — this decides how that stored number is shown for editing. Auto keeps the behaviour the plugin has always had, where the field follows Show country codes. National shows the local format, for example
(555) 123-1212. International shows the full number with its dial code, for example+1 555-123-1212, unless Show country codes is on, in which case the dial code stays in the flag and the field shows the national part. Defaults to Auto. Like the display format, it never changes what is stored, and it leaves empty fields and numbers that cannot be recognized exactly as they are. - Show country codes displays the dial code next to the flag, outside the input, so the customer types only the national part of their number. Turn it off if you would rather the whole number sit in one field. On by default.
Shipping phone number

This group only appears when your checkout page uses the classic shortcode, because the Checkout block manages its own address fields.
- Add shipping phone field adds a separate phone number field to the shipping address, on both checkout and the My Account address form. WooCommerce only gives you a billing phone by default, so turn this on when your carrier needs a number for the delivery address rather than the payer. Off by default. It is ignored when WooCommerce > Settings > Shipping > Shipping options is set to force shipping to the billing address only.
- Make shipping phone required turns the shipping phone into a required field instead of an optional one. It stays hidden until you turn on Add shipping phone field, and has no effect while the shipping phone field is off. Off by default.
Miscellaneous
- Allow dropdown lets the customer open the country list and pick a country. With it off, the flag still shows and still updates as they type, but they cannot choose from the list. On by default.
- Custom placeholder replaces the example number the plugin generates for each country with your own text. On its own it changes nothing until you fill in the field below it. Off by default.
- Phone input placeholder is that text. It only appears once Custom placeholder is turned on. Leave it empty to show no placeholder at all, which is the usual reason to reach for this pair of settings. Fill it in to show the same hint to every customer, for example
555-201-1111. - Always allow checkout decides what an invalid number does. Left on, the customer sees the error but can still place the order, and the error is written to the log. Turned off, the error blocks the order until the number is corrected, and on block checkout the Place order button is disabled once the customer leaves the field. On by default, which is the safer choice for conversion. Turn it off when a reachable phone number matters more than a completed order, for example when a courier needs to call before delivery. If your billing phone field is currently set to optional, the setting shows a note saying so: an empty phone number is not an invalid one, so customers can still check out without entering anything until you make the field required in your checkout field settings.

Show phone in emails adds a Phone Numbers block to WooCommerce order emails, after the customer details. It lists the billing phone, and the shipping phone when there is one and it differs from billing. Every number is a tel: link. Admin emails also get WhatsApp and SMS links under each number, so you can reach the customer straight from your inbox. Customer emails get the number and the tel: link only. On by default.
Admin Orders
↑ Back to top
- Phone action buttons adds Call, WhatsApp, SMS, and Copy buttons under the billing address on the order edit screen, and under the shipping address when a shipping phone is present. The same Call, WhatsApp, and SMS buttons appear in the order preview modal on the Orders list. Copy puts the number on your clipboard and confirms it. On by default.
- Duplicate phone warning adds a warning box to the order edit screen when the same billing phone appears on orders belonging to a different customer. Orders from the same customer are ignored, since repeat orders are normal. The box lists up to ten of the matching orders with their status and date, each linked to its edit screen, and counts the rest. Treat it as a signal to look closer, not as proof of anything. On by default.
Advanced
↑ Back to top
Enable debug writes validation activity to the WooCommerce logs, under the phone-number-validation source at WooCommerce > Status > Logs. Leave it off for daily use and turn it on while you are investigating something. Off by default.
Classic checkout
↑ Back to topOn a checkout page using the [woocommerce_checkout] shortcode, the plugin enhances the billing phone field with the flag selector, the country dropdown, and live validation. If you enabled the shipping phone field, that gets the same treatment.
Validation runs as the customer types: the flag follows the number, and the field is marked valid or invalid as they go. The message itself appears when they place the order, as a WooCommerce error notice at the top of the checkout — for example Phone validation error: Billing phone number is too short. The validated value and any error are written to hidden fields that the server checks on submit, so the check still holds if someone bypasses the JavaScript. Whether that error stops the order depends on Always allow checkout: left on, the order goes through and the error is only logged; turned off, the notice blocks the order until the number is corrected.
When the order is created, the number is normalized to E.164 and saved as the order’s billing or shipping phone. If the customer typed a national number, the plugin uses the address country to work out the dial code.

Block checkout
↑ Back to topOn a checkout page using the WooCommerce Checkout block, the plugin attaches to the block’s own phone fields once they render, and re-attaches when the block re-renders during checkout.
Errors are specific: invalid country code, number too short, number too long, or invalid number. They appear under the field as the customer types. Once the customer leaves the field with an error still showing, and Always allow checkout is off, a notice appears at the top of the checkout and the Place order button is disabled until the number is fixed.
The separate shipping phone setting does not apply here. The Checkout block controls which address fields it renders, so use the block’s own settings to add or require a shipping phone, and the plugin will validate whatever the block shows.

My Account address pages
↑ Back to topThe same field enhancements apply to the billing and shipping address forms under My Account > Addresses. When the customer saves an address, the number is normalized to E.164 and stored on their account, so it comes back correctly formatted at their next checkout. If Always allow checkout is off, a validation error blocks the address save.
Orders you create in WP Admin
↑ Back to topWhen you add or edit an order in WP Admin, the billing and shipping phone fields get the same country selector and validation. Save the order and the validated international number is what gets stored, including the dial code. If a number does not validate and Always allow checkout is off, a notice appears after the save telling you which field is wrong.
Numbers already stored without a dial code are shown with one added, worked out from the order’s address country, in the admin address block, the phone action buttons, and order emails. The stored value itself is left alone.
Editing a customer’s phone under Users → All Users
↑ Back to topWhen you edit a user under Users > All Users, the WooCommerce Billing Phone and Shipping Phone fields get the same country selector and validation as the checkout. The field is pre-filled with the customer’s stored number and its country flag, and when you save the profile the number is stored in E.164, dial code included. As with the rest of the plugin, a number that cannot be validated is still saved rather than blocked, so you are never stopped from saving a user record.
How numbers are stored and displayed
↑ Back to topEvery phone number is stored in E.164: a plus sign, the country code, and the national number with no spaces, for example +15551231212. That is the format order exports, SMS and courier integrations, and the plugin’s own Call, WhatsApp, and SMS links depend on, so it never changes.
How the number is shown is a separate choice. The Phone number display format setting controls the number you see on the order edit screen, in order emails, on the duplicate phone warning, and on the customer’s order confirmation and My Account order pages. Set it to International or National to show +1 555-123-1212 or (555) 123-1212 in place of the raw +15551231212, while the stored value stays exactly as it was. On the order screens and the customer order pages this formatting is applied in the browser, so those pages fall back to the stored E.164 value if JavaScript is turned off; the tel: links there always keep the dialable E.164 number.
Phone number display format covers numbers you are reading. Existing number input format covers numbers you are editing: the value a phone input is prefilled with on the classic and block checkout for a returning customer, on the order edit screen in WP Admin, and on the Users profile screen. The two are independent, so you can read numbers in International and still edit them in National. The reformatting happens in the browser once the country data has loaded, and only for a number the plugin recognizes as valid for its country; anything else is left as stored. Leave it on Auto to keep the behaviour from before the setting existed, where the field simply follows Show country codes.
Numbers you already have saved
↑ Back to topThe display format covers your existing orders, not just new ones, and switching it never rewrites what is stored. Nothing changes until you choose a format, and your saved numbers are safe whichever way you go.
Numbers already in E.164, which is everything the plugin has saved on your behalf, are formatted right away in your chosen style. Older numbers saved without a country code, whether from before the plugin or imported from elsewhere, have the code added first, worked out from the order’s address country, and are then shown in the same format, on the customer’s pages and on your admin order screen alike. A number that cannot be recognized at all is left exactly as it is stored rather than being changed or blanked, so nothing is ever lost. In every case the stored value is untouched; only the way it is displayed changes.
Use cases
↑ Back to topA domestic store that wants clean data without losing orders. Set Default country code to your country, add it to Preferred countries, and leave Always allow checkout on. Customers get the formatting help and the inline errors, but a customer with an unusual number can still buy from you. Turn on Enable debug for a week and check the logs to see how often numbers actually fail before deciding to tighten anything.
A store shipping with couriers that call ahead. Turn Always allow checkout off so an invalid number stops the order. On classic checkout, also turn on Add shipping phone field and Make shipping phone required so the delivery address carries its own number. Leave Show phone in emails on so the number reaches your fulfilment team’s inbox.
A store that only sells to a handful of countries. Add those countries to Preferred countries so they sit at the top of the list, and add the countries you do not ship to under Exclude countries. Set Default country code to your largest market. Customers from an excluded country cannot pick it, so orders with numbers you cannot service do not get created in the first place.
A support team that works from the Orders screen. Leave Phone action buttons on. Your team can call, open WhatsApp, or text a customer directly from the order without copying numbers between apps, and the Copy button handles the cases where they need the number somewhere else.
A store watching for card testing or reshipping fraud. Leave Duplicate phone warning on. When a phone number turns up on orders from several different customer accounts or email addresses, the warning box on the order lists the related orders so you can review them together before you fulfil.
FAQ
↑ Back to topQ: How do I add a phone number field to the shipping address?
Go to WooCommerce > Settings > Shipping > Phone Validation and turn on Add shipping phone field under Shipping phone number, then save. To make it mandatory, turn on Make shipping phone required, which appears once the first toggle is on. If you do not see these settings, your checkout page is using the Checkout block, which manages its own address fields.
Q: Can I limit which countries customers can choose?
Yes, using Exclude countries. Select the countries you want removed and they disappear from the dropdown. Earlier versions had a separate supported countries setting; that was removed, and Exclude countries replaces it.
Q: In what format are phone numbers stored?
E.164, which is a plus sign followed by the country code and the number with no spaces or punctuation, for example +33157276567. Numbers are stored this way on orders, on customer accounts, and on orders you create in WP Admin.
Q: An order shows +15551231212 instead of a nicely formatted number. Can I change that?
Yes. Numbers are stored in E.164 (+15551231212) so they stay reliable for order exports, SMS, and courier integrations, but you can choose how they are displayed. Go to WooCommerce > Settings > Shipping > Phone Validation and set Phone number display format to International (+1 555-123-1212) or National ((555) 123-1212). It changes what you and your customers see on order screens, in emails, and on order confirmation pages, without changing the stored value or what other plugins receive.
Q: Will changing the display format affect the numbers I have already saved?
Only how they look, never what is stored. The format is applied when a number is shown, so your existing orders are displayed in your chosen style just like new ones, while the saved value stays exactly as it is. Numbers saved without a country code have one added first, worked out from the order’s address country, before they are formatted, and any number the plugin cannot recognize is shown as it is rather than changed. You can switch back to E.164 at any time and every number returns to how it looked before.
Q: What is the difference between the two format settings?
Phone number display format changes numbers you read: order screens, emails, and the customer’s order pages. Existing number input format changes numbers you edit: what a phone field is prefilled with when a number is already on file, at checkout for a returning customer and on the order and user screens in WP Admin. Neither changes what is stored, and you can set them independently.
Q: What happens when a customer enters an invalid number?
They see an inline error naming the problem. Whether they can still place the order depends on Always allow checkout. Left on, the order goes through and the error is logged. Turned off, the order is blocked, and on block checkout the Place order button is disabled until the number is corrected.
Q: Does it work with High Performance Order Storage (HPOS)?
Yes. The plugin declares compatibility with HPOS and with the Cart and Checkout blocks, and writes phone data through the WooCommerce order APIs, so both storage modes work.
Q: Why does the duplicate phone warning not appear for a customer who orders often?
By design. The warning only counts orders that belong to a different customer account or a different billing email. Repeat orders from the same person never trigger it.
Q: Can I turn off the WhatsApp and SMS links?
Yes. Turn off Phone action buttons to remove them from the order screens and the order preview, and turn off Show phone in emails to remove the phone block, including those links, from order emails.
Q: Where do the logs go?
WooCommerce > Status > Logs, under the phone-number-validation source. You need Enable debug turned on first.
Q: The flags or the international formatting are not showing. What should I check?
Usually another plugin loading its own copy of the intl-tel-input library. Disable other checkout-related plugins one at a time to find the conflict. If you are on block checkout, also confirm the phone field is enabled in the Checkout block. Turning on Enable debug and reading the log will often point you at the cause.
Is the plugin translated?
It ships with translations for Arabic, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Indonesian, Italian, Japanese, Korean, Brazilian Portuguese, Russian, Simplified Chinese, Spanish, Swedish, and Turkish.
Developer documentation
↑ Back to topThe plugin registers under the PNV\Phone_Number_Validation namespace and uses the pnv_ prefix for options, hooks, and POST fields. All snippets below belong in a site-specific plugin or an mu-plugin rather than a theme.
Action hooks
↑ Back to toppnv_init
Fires while the main plugin class registers its hooks, after WooCommerce has been confirmed active and the plugin’s classes have been instantiated. No arguments.
Filter hooks
↑ Back to toppnv_exclude_countries
Filters the array of uppercase ISO 3166-1 alpha-2 codes excluded from the country selector. Applied in Admin\Settings::get_exclude_countries(), after the saved setting is read, and passed to the checkout, account, and admin order scripts. Use it for codes the library offers but WooCommerce does not list, such as Kosovo (XK) and Ascension Island (AC).
add_filter(
'pnv_exclude_countries',
function ( $countries ) {
$countries = is_array( $countries ) ? $countries : array();
foreach ( array( 'XK', 'AC' ) as $code ) {
if ( ! in_array( $code, $countries, true ) ) {
$countries[] = $code;
}
}
return $countries;
}
);
pnv_preferred_dial_country
Filters the map of dial code to lowercase ISO2 country code used to decide which flag to show when several countries share a dial code. Defaults to array( '44' => 'gb', '1' => 'us', '7' => 'ru' ). Keys and values are normalized to strings and lowercase after the filter runs, and the result is exposed to the frontend as preferredDialCountry.
add_filter(
'pnv_preferred_dial_country',
function ( $mappings ) {
$mappings = is_array( $mappings ) ? $mappings : array();
$mappings['44'] = 'gg'; // Prefer Guernsey over GB, JE, and IM.
$mappings['1'] = 'us';
return $mappings;
}
);
pnv_phone_placeholder
Filters the placeholder text passed to the phone inputs on checkout, the account address form, and the admin order editor. The unfiltered value is the saved pnv_phone_placeholder option when pnv_enable_custom_placeholder is yes, and null otherwise. Returning null restores the library’s per-country example number; returning an empty string shows no placeholder.
add_filter(
'pnv_phone_placeholder',
function ( $placeholder ) {
return '1 49 52 70 00';
}
);
pnv_country_dial_code
Filters the dial code returned for a country. Arguments: string $dial (digits, no plus sign, empty when unknown) and string $country_code (as supplied by the caller). Applied in Country_Dial_Codes::get_dial_code(), which backs both the E.164 normalization and the get-country-dial-code ability.
pnv_order_phone_e164
Filters the E.164 phone value immediately before it is written to an order. Arguments: string $phone, WC_Order $order, and string $type (billing or shipping). It runs at order placement on classic and block checkout, and again when the Store API updates an order from the request, so a filter that rewrites the value must be idempotent.
add_filter(
'pnv_order_phone_e164',
function ( $phone, $order, $type ) {
return $phone;
},
10,
3
);
pnv_display_phone
Filters a phone number after it has been formatted for display. Arguments: string $formatted (the display value), string $phone (the raw stored E.164 value), and string $style (e164, international, or national). It runs wherever the plugin presents a number for reading, including order emails, the duplicate phone warning, and the data handed to the order and customer order screens. Return your own string to control the presentation.
add_filter(
'pnv_display_phone',
function ( $formatted, $phone, $style ) {
return $formatted;
},
10,
3
);
Template functions
↑ Back to toppnv_format_phone_for_display( string $phone ): string
Formats a stored E.164 number using the site’s Phone number display format setting and returns it. Unlike the other snippets on this page, this one is meant for use directly in a template, for example a custom order details layout or a PDF invoice template, so a third-party surface can match the rest of the store. It is fail-safe: when the number cannot be formatted it returns the input unchanged, so it never throws or blanks a number and is always safe to echo.
echo esc_html( pnv_format_phone_for_display( $order->get_billing_phone() ) );
Abilities API tools (MCP)
↑ Back to topSince 1.9.0 the plugin registers tools with the WordPress Abilities API, so any MCP connector plugin, or the WooCommerce core MCP adapter, can discover them through wp_get_abilities(). The plugin does not run an MCP server of its own. Registration is skipped silently when the Abilities API is not available. Every tool requires the manage_woocommerce capability, and all of them are read-only except update-settings.
| Ability | Input | Returns |
|---|---|---|
phone-number-validation/validate-phone | phone_number (required), country (optional ISO2, defaults to pnv_initial_country) | valid, e164, national, international, rfc3966, country, dial_code, number_type, errors |
phone-number-validation/find-orders-by-phone | phone_number (required), limit (1 to 50, default 20) | count, distinct_customers, distinct_emails, possible_fraud_signal, orders |
phone-number-validation/get-settings | none | The current configuration plus the plugin version |
phone-number-validation/update-settings | Any of the fields listed below | updated (changed field names) and the resulting settings |
phone-number-validation/get-country-dial-code | country (required ISO2) | country, dial_code, found |
find-orders-by-phone matches on digits only, so it finds an order whether or not the stored value carries a leading plus sign. It is HPOS-aware. Billing emails in the response are masked to their first character. possible_fraud_signal is true when the matching orders span more than one customer ID.
update-settings accepts partial updates and rejects the whole call if any supplied value is invalid, so settings are never left half written. Its fields map to the plugin options as follows: default_country, preferred_countries, excluded_countries, shipping_phone_enabled, shipping_phone_required, always_allow_checkout, separate_dial_code, allow_dropdown, show_phone_in_emails, phone_actions_enabled, duplicate_warning_enabled, debug_enabled, custom_placeholder_enabled, and phone_placeholder. The two formatting settings, Phone number display format and Existing number input format, are not exposed through the abilities; set those on the settings screen or through the REST route below. Country values must be ISO 3166-1 alpha-2 codes present in the plugin’s dial code table. It is annotated as a write, non-idempotent, non-destructive tool, so MCP clients should confirm before running it.
validate-phone is backed by MCP\Phone_Validator, which wraps libphonenumber and loads its autoloader from vendor/autoload.php on first use. When the library is not present, the ability returns valid: false with an explanatory entry in errors rather than failing.
Settings REST route
↑ Back to topThe settings screen is a React app that reads and writes every option through one route, phone-number-validation/v1/settings, rather than through WooCommerce’s settings form. GET returns each option in its stored shape (yes/no strings, arrays of uppercase ISO2 codes); POST accepts a partial payload keyed by option name and updates only the keys you send, returning the fresh values. Both methods require the manage_woocommerce capability and a valid X-WP-Nonce. Unknown keys are ignored and invalid values fall back to the option’s default, so the route is safe to script against.
Options
↑ Back to topAll options are stored in wp_options. Checkbox options hold yes or no; country lists hold arrays of uppercase ISO2 codes.
| Option | Default |
|---|---|
pnv_add_shipping_phone | no |
pnv_make_shipping_phone_required | no |
pnv_initial_country | US |
pnv_preferred_countries | array( 'US', 'GB' ) |
pnv_separate_dial_code | yes |
pnv_allow_dropdown | yes |
pnv_enable_custom_placeholder | no |
pnv_phone_placeholder | empty string |
pnv_exclude_countries | array( 'RU' ) |
pnv_always_allow_checkout | yes |
pnv_display_format | e164 |
pnv_input_display_format | auto |
pnv_show_phone_in_emails | yes |
pnv_enable_phone_actions | yes |
pnv_enable_duplicate_warning | yes |
pnv_enable_debug | no |
Order meta and POST fields
↑ Back to topValidated numbers are written to _billing_phone_valid and _shipping_phone_valid on the order through the CRUD API, alongside the order’s own billing and shipping phone properties.
The client-side validator posts four fields that the server reads: pnv_phone_valid_billing and pnv_phone_valid_shipping carry the validated international value, and pnv_phone_error_billing and pnv_phone_error_shipping carry the error message when validation failed. The older _pnv_phone_validator and _pnv_phone_validator_err field names are still read as a fallback on the account address form.
Normalization helpers
↑ Back to topCountry_Dial_Codes::normalize_to_e164( string $phone, string $country_code ) converts a national number to E.164 for the given country. It uses libphonenumber when the number is valid for that country, so per-country rules are respected — including countries such as Italy where a leading zero is part of the national number and must be kept. If libphonenumber is unavailable or the number is not valid, it falls back to prepending the country’s dial code. It returns the input unchanged when the number already starts with a plus sign or the country is unknown.
Country_Dial_Codes::to_e164_strict( string $phone ) strips every non-digit character and returns a plus sign followed by the digits, or an empty string when there are no digits.
Logging
↑ Back to topThe plugin logs through wc_get_logger() with the context array( 'source' => 'phone-number-validation' ). Nothing is written unless the pnv_enable_debug option is yes. Logs appear at WooCommerce > Status > Logs.