I have a soft spot for the release notes that read like one sentence. WooCommerce 10.9.4 dropped on July 7 with a single fix — “Confirms is_vat_exempt is correctly applied during block checkout for logged-in users” — and it is exactly the kind of quiet dot release that hides a very loud story if you sell to businesses.
Because if your store has B2B customers in the EU, the UK, or Turkey, and any of them have a VAT-exempt profile, they may have been charged VAT they should not have been charged for about six weeks. Silently. Only on Block Checkout. Only when logged in. That is the sort of bug that does not fire a support ticket for a while — the customer either eats it, files a chargeback, or moves to a competitor without telling you.
This is also the fourth dot release WooCommerce has shipped in fifteen days — 10.9.2, 10.9.3, 10.9.4 — and the fourth one in a row where the root cause was a recent, well-intentioned improvement that met production reality. That is worth talking about on its own.
What is actually new
WooCommerce 10.9.4 was announced on July 7, 2026 by Brian Coords on the WooCommerce Developer Blog. It is not a security release and does not require a database update. There is exactly one changelog entry: the VAT-exemption fix for Block Checkout with logged-in customers.
The fix ships as PR #66342 by kalessil, merged on July 7. The commit reverts a piece of PR #64392 — a Store API checkout performance optimization by the same author that landed back in April 2026 and shipped in WooCommerce 10.9.0 on June 23. That optimization did real work: median POST /checkout response time improved by about 20 percent, P90 latency dropped by 27 percent. It got there by adding an early return inside WC_Customer::save() that skipped the database write when nothing had changed on the customer object.
The problem is that not everything that changes on a customer goes through set_prop(). Three properties bypass it: password, calculated_shipping, and is_vat_exempt. When a Block Checkout request set VAT exemption on a logged-in customer, the change-detection check did not see it, the early return fired, and the flag was silently discarded before the order calculated tax. The classic (shortcode) checkout was not affected. Guest checkout was not affected. Only the intersection — Block Checkout plus a logged-in customer — was.
10.9.4 does two things. First, it drops the early-return short-circuit on WC_Customer::save() so the save always persists. Second, it adds unit tests for WC_Customer covering the null-return and property-change edge cases, and casts is_paying_customer to boolean for consistency with the other flag setters. The file changed is plugins/woocommerce/includes/class-wc-customer.php, with a matching test file. That is the whole release.
For context on the cadence: this is the fourth 10.9.x dot release in fifteen days. 10.9.2 (July 3) fixed a settings SDK fatal on upgrade and deprecated the push notifications feature flag. 10.9.3 (also July 3) fixed a fatal error in WC_Email::send_notification() when a filtered mail callback returned a non-bool value. 10.9.4 (July 7) is this VAT fix. WooCommerce 11.0 is scheduled for July 28.
Why it matters for WordPress / WooCommerce people
If you run a B2B store on Block Cart and Block Checkout — which has been the default in fresh installs since WooCommerce 10.9 — this is a data-integrity bug, not a UX bug. Between June 23 and July 7, any logged-in customer whose VAT-exempt status was set or refreshed during a Block Checkout submission may have been charged tax that legally should not have been on their invoice. The order record shows the wrong total. The tax report shows revenue you did not actually earn. If you file quarterly VAT in the EU, you have almost certainly filed an incorrect return for at least one week.
The narrower question is what triggered the exemption in your stack. If you use the built-in “Enable tax exemption” toggle on the customer profile in wp-admin, that path goes through the normal admin save and is not affected. The path that breaks is the one where set_is_vat_exempt() is called during the checkout request itself — most commonly by an EU VAT number validation plugin, a B2B customer plugin that reads a VAT ID from a checkout field and flips the flag, or your own custom code hooked into woocommerce_store_api_checkout_update_customer_from_request. If that is your setup, you were exposed.
The broader pattern is the one worth naming. Four dot releases in fifteen days, each one reverting or hardening a piece of good engineering that shipped in 10.9.0: the settings SDK autoloader, the mail callback return contract, and now the customer-save short-circuit. Every one of them looked reasonable in a PR review. Every one of them failed against the shape of real production data. This is what happens when a major release is genuinely ambitious — 464 PRs from 65 contributors, per the 10.9.0 announcement — and it is the argument for staging environments that mirror production customer profiles, not just production code.
What I would do (or not do) about it
Update to 10.9.4 today. There is no reason to wait. It is a one-line functional change plus tests, no database migration, no schema change, and the risk of leaving it is that you continue to invoice VAT-exempt customers incorrectly. If you are on 10.9.0, 10.9.1, 10.9.2, or 10.9.3, 10.9.4 supersedes all of them.
Before you push the update on production, do the audit. Pull the list of orders placed between June 23 and July 7 with a customer who is currently VAT-exempt, or whose email domain matches a business VAT registration. Filter to orders placed via Block Checkout. Compare the tax charged to what the tax rules say should have been charged. Any order where the numbers disagree is a candidate for a partial refund and a corrected invoice — and yes, in most EU jurisdictions you are legally obliged to issue the corrected invoice, not just quietly refund the difference. Turkey follows the same principle under KDV rules.
On the code side, if you maintain a plugin or custom code that calls set_is_vat_exempt(), set_password(), or set_calculated_shipping(), add a defensive save() call and do not assume that setter+later save will persist. That was always the correct pattern, but 10.9.0 briefly rewarded the shortcut. Do not build against that behaviour going forward — the fix intentionally makes save() always persist regardless of change detection.
And on the release-management side, this is the fourth week in a row I would tell a client on Woo 10.9 to stay one dot release behind trunk on production and let staging absorb the churn. WooCommerce 11.0 arrives July 28 with product object caching on by default and Action Scheduler 4.0.0 — both are meaningful changes. Do the staging pass on an 11.0 release candidate before it lands, not the day after.
The good news is that this one is fixed, tested, and small. The less-good news is that if you were running a B2B store on Block Checkout the last two weeks, you have some invoices to look at.
Last modified: July 11, 2026
United States / English
Slovensko / Slovenčina
Canada / Français
Türkiye / Türkçe