← All posts Insights 15 min read

WooCommerce Cart and Checkout Blocks: 5 Customisations That Stop Working

The classic checkout shortcode is not being removed. The five customisations that actually stop working when you migrate to Cart and Checkout Blocks, and the replacement API for each.

WooCommerce Cart and Checkout Blocks migration: five customisations that stop working
Quick Summary

Migrating to Cart and Checkout Blocks — what actually breaks

  • The classic
    Your cart is currently empty.

    Return to shop

    and
    shortcodes are not scheduled for removal. WooCommerce 11.0 shipped on 4 August 2026 and took nothing away from checkout.
  • What forces the move is capability drift, not a deadline: new checkout features are built for the blocks only.
  • The block checkout renders client-side and posts to the Store API (wc/store/v1), so classic PHP checkout hooks and template overrides never execute.
  • Five customisations stop working: the woocommerce_checkout_fields filter family, checkout template overrides, unregistered payment gateways, order-sync code bound to woocommerce_checkout_order_processed, and CSS targeting classic selectors.
  • Custom checkout fields no longer need JavaScript. The Additional Checkout Fields API is PHP-only and has been stable since WooCommerce 8.9.
Not removed
WooCommerce states it has no plans to phase out the classic cart and checkout in core
wc/store/v1
The namespace the block checkout posts to instead of rendering a PHP form
8.9
WooCommerce version where the Additional Checkout Fields API became stable — PHP only
2 hooks
Order-sync code must listen on both the classic and the Store API order-processed actions

The classic checkout shortcode is not being removed. WooCommerce’s developer FAQ states it plainly: “We don’t have any plans to phase out the classic cart and checkout in core at this time.” Version 11.0 shipped on 4 August 2026 and deprecated the Product Editor Beta, not the checkout shortcode. If you postponed this migration because of a removal date, the date was never real.

The migration still matters, for a different reason. New checkout capability ships for the blocks and not for the shortcode, so the gap widens every release. And the move is not a page swap: every customisation bound to the classic checkout’s PHP lifecycle stops executing the moment that page renders a block. This guide names the five that break, gives the replacement API for each, and covers the one that costs the most — order-sync code that silently never fires again.

Version scope: verified against WooCommerce 11.0 (released 4 August 2026). The APIs below apply to WooCommerce 8.9 and later unless a section says otherwise. This is part of the WooCommerce store operations guides.

On this page

The 11.0 deadline was never real

Two facts get conflated into a removal date that does not exist. Cart and Checkout Blocks became the default for new installations in WooCommerce 8.3, released November 2023. Separately, the WooCommerce Blocks package carried its own version numbers in the 8.x–11.x range while core was still on 7.x and 8.x. A Blocks 11.x changelog entry reads exactly like a WooCommerce 11.0 changelog entry if you are skimming.

What WooCommerce actually committed to is the opposite of removal. Existing stores keep whatever checkout they were already using, even after upgrading, and the shortcodes remain in core for backward compatibility with stores that cannot migrate because of extensions or customisations. There is no announced end date.

So the honest framing is a cost curve, not a cliff. Staying on the shortcode costs you access to new checkout features and a shrinking pool of extensions that test against it. Migrating costs you re-implementation work in five specific places. The rest of this guide prices that work.

Why the block checkout ignores your PHP customisations

Every breakage below has one cause, and it is architectural rather than a list of unsupported functions. The classic checkout is a PHP page: WordPress renders

, WooCommerce loads its checkout templates, and your hooks fire inside that render. The block checkout is a client-side React application that fetches and submits through the Store API. The server never renders a checkout form, so the stage where your code used to run does not happen.

Where your PHP customisations used to run

Classic shortcode checkout compared with block checkout request flow In the classic checkout the server renders a PHP form and customisation hooks fire during that render. In the block checkout the browser posts once to the Store API, the PHP form render stage is absent, and classic hooks and template overrides never execute. Classic shortcode checkout — the server renders the form Browser requests /checkout WordPress renders PHP

Templates + hooks your code runs here WC_Order created Block checkout — the browser posts once to the Store API Browser React checkout block POST to Store API wc/store/v1/checkout Stage absent no PHP form render WC_Order created one request — classic hooks and template overrides never execute

This is the same Store API that powers decoupled storefronts, which is why the trade-offs in our assessment of when the Store API is worth it apply here too. One distinction matters before you write any code: the Store API (wc/store/v1) is public and session-scoped for storefront use, while server-to-server integrations belong on the authenticated REST API (wc/v3). Migrating checkout does not change which API your ERP sync should call.

The five customisations that stop working

Work down this list against your own store. Each item names the symptom you will see, because most of these fail silently rather than throwing an error.

1. The woocommerce_checkout_fields filter family

Anything that adds, removes, reorders or relabels checkout fields through woocommerce_checkout_fields, woocommerce_billing_fields or woocommerce_default_address_fields has no effect on the block checkout. The filter still exists and your callback may still run on other screens, so nothing errors. The field simply does not appear at checkout. Replacement: the Additional Checkout Fields API, covered below.

2. Checkout template overrides

Copies of checkout/form-checkout.php, checkout/review-order.php or checkout/form-billing.php in your theme are dead files under the block checkout. The block does not load the classic template hierarchy at all. Symptom: the theme override stays in place, passes every file-exists check, and renders nothing.

3. Payment gateways with no block registration

A gateway extending WC_Payment_Gateway keeps working for order processing but will not appear as an option in the block checkout unless it also registers a block integration. There is no supports( 'blocks' ) flag — that is a common misreading. The gateway needs a PHP class extending AbstractPaymentMethodType plus a JavaScript module, both covered below. Symptom: the payment method vanishes from checkout while remaining enabled in settings.

4. Order-sync code bound to the classic order hook

This is the expensive one. woocommerce_checkout_order_processed does not fire for orders placed through the block checkout. If your ERP or fulfilment integration queues orders from that hook, it stops queueing — while orders keep being created normally in WooCommerce. Nothing in the admin looks wrong. You find out when someone reconciles.

5. CSS bound to classic checkout selectors

Stylesheets targeting .woocommerce-checkout, .woocommerce-billing-fields or #order_review stop matching. Block markup uses wp-block-woocommerce-* and wc-block-* class names. Symptom: an unstyled or half-styled checkout, which is at least visible immediately — the only failure on this list that announces itself.

Treat the migrated-hooks list as an allowlist

WooCommerce publishes a finite, explicitly enumerated list of legacy hooks that were carried into the Cart and Checkout blocks. It is mostly cart-item, coupon, shipping-package and product-loop filters — woocommerce_add_cart_item_data, woocommerce_get_item_data, woocommerce_shipping_package_name and similar — plus a small set of actions.

The operationally useful way to read that page is as an allowlist. If a classic cart or checkout hook is not on it, assume it does not fire in the blocks and verify before shipping. Reading it as a blocklist — assuming everything works unless documented otherwise — is what produces the silent failures in the previous section.

One caveat is stated on the page itself and is easy to miss: those migrated hooks run server-side, and the client-side block will not necessarily re-render because a server callback changed something. A filter that adjusts cart item data can take effect in the order while the block UI continues showing the pre-filter value until the next Store API response.

Replacement APIs, mapped

What you need to do Classic checkout Block checkout
Add or modify checkout fields woocommerce_checkout_fields filter woocommerce_register_additional_checkout_field() — PHP only
Change checkout markup Template override in theme Block editor, inner blocks, or Slot/Fill components (JS)
Add a payment gateway WC_Payment_Gateway subclass Same subclass plus AbstractPaymentMethodType and registerPaymentMethod()
React to a completed order woocommerce_checkout_order_processed woocommerce_store_api_checkout_order_processed
Send extra server data to the client Inline script or template variable ExtendSchema under the extensions key
Trigger server logic from the client Custom AJAX endpoint extensionCartUpdate()ExtendSchema::register_update_callback

Verdict: only two rows genuinely require JavaScript — markup changes and payment gateways. Field additions and order-side integration work, which is most of what a typical store customises, stay in PHP. Teams that budgeted a front-end rebuild for this migration usually over-scoped it.

Custom checkout fields no longer need JavaScript

The most common piece of outdated migration advice is that custom checkout fields must be rebuilt as a JavaScript block extension. That was true early on. It has not been true since the Additional Checkout Fields API stabilised in WooCommerce 8.9, after shipping as experimental in 8.7. Registration is a single PHP call on woocommerce_init or later.

add_action( 'woocommerce_init', function () {
    woocommerce_register_additional_checkout_field(
        array(
            'id'       => 'softxone/delivery-window',
            'label'    => 'Preferred delivery window',
            'location' => 'order',
            'type'     => 'select',
            'required' => false,
            'options'  => array(
                array( 'value' => 'am', 'label' => 'Morning (08:00-12:00)' ),
                array( 'value' => 'pm', 'label' => 'Afternoon (12:00-17:00)' ),
            ),
        )
    );
} );

Three locations are available: contact renders at the top of the form and saves to the customer account, address renders inside the billing and shipping forms and saves to both customer and order, and order holds everything else. Supported types are text, select and checkbox. The id must be namespaced as your-namespace/field-name.

Values are stored against prefixed meta keys — _wc_billing/ and _wc_shipping/ for address fields, _wc_other/ for contact and order fields. Read them back through the CheckoutFields helper rather than reaching for the raw meta key, because the prefix scheme is internal and the helper is the documented accessor. That is the same discipline HPOS forced on order code, which we covered in the HPOS migration guide for NetSuite sync code.

Keeping order sync alive across both checkouts

The Store API fires its own action when an order is ready: woocommerce_store_api_checkout_order_processed, introduced in WooCommerce Blocks 7.2.0 to replace the deprecated woocommerce_blocks_checkout_order_processed. WooCommerce’s own source comment describes it as “similar to existing core hook woocommerce_checkout_order_processed” — similar, but separate. The classic hook does not fire on Store API requests.

During a migration you will have orders arriving through both paths, so bind both hooks. There is a trap in doing that: the two actions have different signatures. The classic hook passes $order_id, $posted_data, $order; the Store API hook passes only $order. Register with one accepted argument and normalise inside the callback.

add_action( 'woocommerce_checkout_order_processed', 'sx_queue_order_for_erp', 10, 1 );
add_action( 'woocommerce_store_api_checkout_order_processed', 'sx_queue_order_for_erp', 10, 1 );

/**
 * Queue an order for ERP sync from either checkout.
 *
 * @param int|WC_Order $order_or_id Order ID (classic) or order object (Store API).
 */
function sx_queue_order_for_erp( $order_or_id ) {
    $order = $order_or_id instanceof WC_Order ? $order_or_id : wc_get_order( $order_or_id );

    if ( ! $order ) {
        return;
    }

    // Idempotency guard: both hooks are registered, and retries happen.
    if ( $order->get_meta( '_sx_erp_queued' ) ) {
        return;
    }

    $order->update_meta_data( '_sx_erp_queued', current_time( 'mysql', true ) );
    $order->save();

    sx_erp_queue_push( $order->get_id() );
}

The guard is not optional. Once both hooks are registered, any future code path that routes a classic order through the Store API — or a retry that re-enters the callback — writes a duplicate to your queue, and duplicate Sales Orders are the most expensive failure mode in this stack. One idempotency key, checked before the write, costs four lines.

Note also that the callback takes an ID and calls wc_get_order() rather than assuming a WP_Post, and reads meta through the order object rather than get_post_meta(). Both are required for HPOS correctness independently of this migration.

Correction to earlier guidanceOrder sync is not automatically unaffected by this migration:

The WC_Order object produced by both checkouts is the same, so code that reads an order later — a scheduled job, a REST poller, an admin action — needs no change. Code that is triggered by checkout does. If your integration is hook-driven, and most are, it needs the second action registered.

Registering a payment gateway with the block checkout

Gateway support has two halves. Server-side, add a class extending AutomatticWooCommerceBlocksPaymentsIntegrationsAbstractPaymentMethodType that implements initialize(), is_active(), get_payment_method_script_handles() and get_payment_method_data(), then register it on the woocommerce_blocks_payment_method_type_registration action from within woocommerce_blocks_loaded.

add_action( 'woocommerce_blocks_loaded', function () {
    add_action(
        'woocommerce_blocks_payment_method_type_registration',
        function ( AutomatticWooCommerceBlocksPaymentsPaymentMethodRegistry $registry ) {
            $registry->register( new SX_Gateway_Blocks_Support() );
        }
    );
} );

Client-side, the script handle returned by get_payment_method_script_handles() must call registerPaymentMethod() from @woocommerce/blocks-registry, available at runtime as window.wc.wcBlocksRegistry. This half cannot be done in PHP — the block checkout needs a React component for the payment method’s label and content.

Budget accordingly. If you run a gateway maintained by someone else, this work is theirs and your only decision is whether their release schedule fits your cutover. If the gateway is bespoke, it is the largest single item in the migration.

Declaring compatibility is a claim, not a test

WooCommerce asks extensions to declare whether they work with the blocks, using a third boolean argument on the features API.

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( AutomatticWooCommerceUtilitiesFeaturesUtil::class ) ) {
        AutomatticWooCommerceUtilitiesFeaturesUtil::declare_compatibility(
            'cart_checkout_blocks', __FILE__, true
        );
    }
} );

When an extension declares itself incompatible, the block editor shows a warning and offers a one-click switch to the classic checkout. That is genuinely useful. What it is not is a capability test — nothing verifies the declaration. The value is whatever the extension author passed as the third argument, so the admin compatibility screen tells you what plugins say, not what they do.

Two consequences follow. An extension that never declares anything is not necessarily broken; it may simply predate the API. And an extension declaring true can still ship a partly broken checkout. Neither direction is trustworthy enough to replace a test order. Use the screen to build your test list, not to shorten it.

Pre-migration audit

Run this against a staging copy with production data before you touch the live cart and checkout pages. The same staging discipline applied to the WooCommerce 10.8 changes that break production applies here, and for the same reason: the failures are silent.

  • Grep the codebase for woocommerce_checkout_fields, woocommerce_billing_fields and woocommerce_default_address_fields; list every field each one adds.
  • List every file under woocommerce/checkout/ in the active theme and child theme.
  • Grep for woocommerce_checkout_order_processed and every woocommerce_before_checkout_ or woocommerce_review_order_ hook in use.
  • Inventory active payment gateways and confirm each registers an AbstractPaymentMethodType integration.
  • Grep stylesheets for .woocommerce-checkout, .woocommerce-billing-fields and #order_review.
  • Place a test order on staging through the block checkout and confirm it reaches the downstream system.
  • Place a second test order and confirm exactly one queue entry exists — this catches a missing idempotency guard.
  • Diff the rendered checkout field-by-field against production before signing off.

The last three items catch the failures that do not surface visually. Everything above them you would eventually notice on your own.

How to revert if the migration goes wrong

The rollback is documented and takes under a minute, which is the main argument for attempting the migration on a scheduled window rather than deferring it indefinitely. Edit the Cart or Checkout page, select the block, click Transform in the block toolbar and choose Classic Shortcode.

Revert both pages together. WooCommerce notes that the cart and checkout blocks work as a pair, so reverting only one leaves you with a mixed flow. Keep in mind that orders placed during the block window were created through the Store API — if you reverted because order sync went quiet, those orders exist in WooCommerce and need a manual reconciliation pass against your ERP before you close the incident.

Get the working checklists

The runbooks and decision checklists from these guides, as printable PDFs — free in the SoftXone guide library.

Browse the guide library →

If the audit turns up a bespoke gateway plus hook-driven ERP sync, that combination is where checkout migrations overrun. It is scoped as an integration project rather than a content change: gateway block support, field re-registration, and keeping order sync intact across the cutover.

Sources & Further Reading

References

  1. FAQ: Cart and Checkout Blocks by DefaultWooCommerce Developer Blog — the statement that there are no plans to phase out the classic cart and checkout.
  2. Cart and Checkout Blocks: Becoming the Default ExperienceWooCommerce Developer Blog — 8.3 default for new installs, existing stores unchanged, compatibility declaration snippet.
  3. WooCommerce 11.0 release notesWooCommerce Developer Blog — what 11.0 actually shipped and deprecated on 4 August 2026.
  4. Additional Checkout Fields APIWooCommerce developer docs — locations, field types, registration keys and meta key prefixes.
  5. Payment method integrationWooCommerce developer docs — AbstractPaymentMethodType, the registration action, and registerPaymentMethod().
  6. Migrated legacy hooksWooCommerce developer docs — the enumerated list to read as an allowlist, plus the server-side-only caveat.
  7. Customizing the Cart and Checkout pagesWooCommerce docs — the Transform control for reverting to the classic shortcode.
  8. Store API referenceWooCommerce developer docs — the wc/store/v1 namespace and its storefront-only scope.

Frequently asked questions

Will WooCommerce remove the classic checkout shortcode?

There is no announced removal date. WooCommerce states it has no plans to phase out the classic cart and checkout in core, and version 11.0 (August 2026) changed nothing about it. Plan the migration around feature access and extension support, not a deadline.

Do I need a developer to migrate to Cart and Checkout Blocks?

Only if you have custom checkout fields, a bespoke payment gateway, or hook-driven order sync. A store with no checkout customisation can swap the pages in the block editor and test. The cost scales with the length of your audit list, not with store size.

Will my orders still sync to NetSuite after switching to the block checkout?

Only if the integration listens on woocommerce_store_api_checkout_order_processed. Code triggered by the classic woocommerce_checkout_order_processed action stops firing silently while orders continue to be created normally. Scheduled jobs and REST pollers that read orders after the fact are unaffected.

Can I run the block checkout and the classic shortcode at the same time?

Not on the same page, and WooCommerce advises keeping cart and checkout on the same version because the two blocks work as a pair. During a phased migration, keep both order-processed hooks registered so orders from either route reach downstream systems.

Do custom checkout fields still require JavaScript?

No. The Additional Checkout Fields API has been stable since WooCommerce 8.9 and registers text, select and checkbox fields from PHP alone. JavaScript is only unavoidable for changing block markup and for adding a payment gateway to the checkout.

Related guides

Discussion

Leave a Reply

Your email address will not be published. Required fields are marked *


Ship it

Need this in your stack?

We build, integrate, and ship — no calls, just delivery.

Start a project →