October 17, 2026

WooCommerce Phone Validation Plugin: A Small Job, Engineered Like a Big One

woocommerce wordpress plugin checkout testing playwright github actions

We just shipped SHIFT64 Phone Validation for WooCommerce to WordPress.org. It’s free, it’s open source, and on paper it’s boring: it checks that the phone number typed at checkout can actually exist, and optionally rewrites it into one consistent format before it lands on the order.

That’s it. No SMS codes, no flag picker, no “AI-powered” anything.

So why write an article about it? Because a plugin this small is the best way to show what we mean by well-coded. When the feature fits in one sentence, everything else you see in the repository is engineering discipline — and that’s exactly the part you can’t judge from a plugin’s screenshot or a freelancer’s portfolio.

The Problem It Solves

If you ship physical goods, the phone number on the order is not decoration. The courier calls it. Your SMS gateway texts it. Your ERP exports it. And customers type it in every possible way:

  • 600 123 456
  • +48 600-123-456
  • 0048 (600) 123 456
  • 600 123 456 pasted from a PDF, with non-breaking spaces you can’t see

WooCommerce’s built-in check only rejects characters it doesn’t like. It happily accepts 123 as a phone number. The plugin reads the number in the context of the address country (so 600 123 456 next to a Polish address becomes +48 600 123 456), validates it with Google’s libphonenumber rules bundled locally, and blocks the order until the number is fixed.

Everything runs on your server. No external HTTP requests, no API keys, no data leaving the store.

One “Simple” Plugin, Two Checkouts

Here’s the first place where “simple plugin” gets expensive. A WooCommerce store in 2026 can run one of two completely different checkouts:

Classic (shortcode) checkoutBlock checkout
How the order is submittedForm POST, PHP hooksStore API (REST), React on the front end
Where validation hooks inwoocommerce_after_checkout_validationThe Store API checkout request, before the order is placed
How errors reach the customerWooCommerce notices in the pageJSON error, rendered by the blocks
How you highlight the right fieldField IDs in the classic markupError codes mapped to block field paths

A plugin that “supports WooCommerce checkout” but only tested one of these is a plugin that breaks for half its users. We support both, and every behavior is tested on both.

The Details That Took the Most Time

Validation itself is a library call. The real work was everything around it — and the changelog is honest about it. A few examples from the last weeks:

  • Invisible whitespace. A number pasted from a document often contains non-breaking spaces. WooCommerce’s own phone check rejects them before our validator ever runs, with a confusing message. Fix: collapse Unicode whitespace before WooCommerce looks at the field.
  • Errors mapped by code, not by text. The first version matched error messages to fields by their text. Then a translation changed the text and the highlight disappeared. Now every error carries a code, and the field is found by code — in English, in Polish, and when a developer rewords the message with a filter.
  • The collapsed address. On the block checkout, a returning customer’s address is shown collapsed. If the phone in that hidden address was invalid, the customer saw an error about a field they couldn’t see. Fix: open the address section and highlight the field.
  • Shipping phone copied from billing. When “ship to a different address” is off, WooCommerce copies the billing data. Validating that copy would show the same error twice. It’s now skipped.
  • One checkout script per page. Load the classic script on the classic checkout, the block script on the block checkout, and nothing on pages that aren’t the checkout.

None of these show up in a feature list. All of them show up in your support inbox if nobody thought about them.

How It’s Tested

This is the part we’re proudest of, so here are the actual numbers from the repository:

  • 93 PHPUnit test methods (more cases through data providers) covering the normalizer, validator, formatter, both checkout validators, error messages, hooks and dependency checks. They run on PHP 8.3, 8.4 and 8.5 on every pull request.
  • 38 Playwright end-to-end tests in 21 spec files, running against a real WordPress + WooCommerce in wp-env. They place real orders: valid and invalid numbers, Polish messages, pasted Unicode spaces, saved customer addresses, collapsed addresses, classic and block themes, every admin setting.
  • PHPStan at level 6 — static analysis that catches type errors before they become fatals in production.
  • PHPCS with the WordPress-Extra and PHPCompatibilityWP rulesets, including the WordPress security sniffs for escaping, sanitizing and nonces.
  • The official WordPress Plugin Check on the built ZIP, on every pull request — the same checks the WordPress.org review team uses.

Every Monday, Against Whatever Is Newest

The end-to-end suite doesn’t only run when we change something. A scheduled job runs it every Monday at 05:17 UTC against the latest stable WordPress and the latest stable WooCommerce.

Why? Because most plugins don’t break when their code changes. They break when WooCommerce ships a new checkout release and nobody noticed until a customer couldn’t place an order. A weekly run turns “a merchant reported it” into “we knew on Monday morning”.

How It’s Released

Nobody bumps version numbers by hand:

  1. Every change goes through a pull request with a Conventional Commits title, enforced by a lint check.
  2. Merging to master triggers semantic-release: it reads the commits, decides the version, writes the changelog and updates readme.txt.
  3. The same pipeline runs PHPStan again, validates the package before anything is tagged, builds the ZIP, attaches it to a GitHub release and deploys it to WordPress.org SVN.
  4. The third-party actions that touch the WordPress.org deploy and Plugin Check are pinned to commit SHAs, not tags — a moved tag can’t slip code into the release.

We wrote about the same approach for themes in Automatic WordPress Theme Versioning with Semantic Release.

A Written Backward-Compatibility Contract

The plugin exposes four filters and a format_phone() helper for themes and integrations. The repository has a BACKWARD_COMPATIBILITY.md that lists those surfaces and what counts as breaking them. Changing one without a deprecation path is a review blocker, not a nit. If your integration hooks into us today, it keeps working tomorrow — or you get a major version and a migration note.

Why We Built It This Way

Honestly? Because this is how we build code for clients, and we wanted something public to point at.

When a store asks us for a custom checkout field, an ERP integration or a shipping rule, the question isn’t “can you write the PHP”. Anyone can. The questions are:

  • Will it work on the block checkout and the classic one?
  • What happens on the next WooCommerce update?
  • Who notices when it breaks — us, or your customers?
  • Can the next developer change it without fear?

This plugin is our answer in code you can read. The full repository is public: the tests, the CI, the release pipeline, the review checklist.

Try It — and Tell Us What You Think

If it saves you a few bounced deliveries, a review on WordPress.org helps more than you’d think — a new plugin with zero reviews is invisible. If something doesn’t work, open an issue on GitHub; it’ll get a test before it gets a fix.

And if your store needs something more specific than phone validation — engineered to the same standard — that’s what our Custom WooCommerce Coding service is for.

M
Written by

Mateusz Zadorozny

SHIFT64 Founder. WooCommerce performance specialist helping store owners achieve faster load times and better conversions.