WooCommerce 11.3: What’s coming for developers

Beta

WooCommerce 11.3 is coming soon

This release is now available for testing. Review the highlights below, test the beta, and share feedback before the final release.

Highlights

  • Customize more blocks with new styling controls
  • Display savings in the On-Sale Badge block
  • Analytics Orders now supports payment method filters.
  • Beta available now
  • Release week: Nov 9, 2026
  • Feedback requested

WooCommerce 11.3 is scheduled for the week of November 9, 2026 and the first beta release is ready for testing. This is a big one, with 1,310 commits and 117 contributors so far. Highlights include more styling controls, flexible On-Sale Badges, and smoother mobile swiping in Product Galleries to improve the experience of using Purple, our block theme. You’ll also see improvements Analytics, product queries, and numerous REST API and MCP updates for developers.

More styling options come to WooCommerce blocks

PRs: #69398, #69607, #69016, #69440, #69600, #69603, #69606, #69066

We’ve expanded styling options for many blocks: Product, Featured Product, Featured Category, and Product Categories List blocks and more. New controls include colors, spacing, and borders, with options curated for each block.

In addition, Featured Category block can now be used in Terms Query block, allowing users to display a collection of product categories in unified way and nice custom designs.

We’ve also introduced more customizable Product Result Count and Catalog Sorting:

Based on user feedback we’ve also improved the mobile gesture support in Product Gallery block. It’s smoother and more intuitive. Images now follow user’s finger and respond to quick flicks with natural momentum, thanks to native scrolling and scroll snap.

New options for the On-Sale Badge block

PRs: #69180, #69596

In WooCommerce 11.3, the On-Sale Badge block will support showing the discount amount or percentage, along with custom text. This lets you show how much customers save directly on the badge.

Select the On-Sale Badge block in the Product Catalog or Single Product template. Under Badge content → Show, you can now choose between:

  • Text: show “Sale” or your own text, like “Special offer”.
  • Amount: show the amount saved, like “$5.00”.
  • Percentage: show the discount percentage, like “25%”.

For Amount and Percentage, you can add a prefix and suffix. For example, “Save ” and “ off” will display “Save 25% off”. The default remains “Sale”, so you don’t need to change existing blocks.

For variable products, the badge shows the maximum discount with “Up to”, like “Up to 50%”. On single-product views using Add to Cart + Options, it updates to show the selected variation’s discount. If the selected variation isn’t on sale, the badge is hidden. Clearing the selection restores the “Up to” badge.

Set refund and returns policy pages

PR: #69002

Merchants can select their refund and returns policy page under Settings → Advanced → Page setup. The selected page is also labeled visually in the Pages list.

For discoverability, WooCommerce adds structured data linking to the selected policy page and expose its link to search engines without requiring custom markup.

WooCommerce MCP updates

PRs: #69787, #69678, #69681, #69683, #69684, #69692

WooCommerce 11.3 removes its deprecated /wp-json/woocommerce/mcp endpoint and its REST API key connection path, and stops bundling the MCP Adapter and Abilities API packages. Core product and order abilities are enabled by default. To access them through MCP, clients use the WordPress MCP Adapter plugin with a WordPress application password. Developers using the old endpoint can follow the migration steps in WooCommerce 11.3 MCP Adaptor updates.

The experimental Extensible abilities API is off by default. When enabled, extensions can add their fields to product and order ability reads, support fields in product writes, and register query filters and validators. The API is still changing, and the team is asking extension developers to test it outside production. See Call for testing: Extensible abilities for extension fields for examples, known gaps, and the feedback discussion.

Purple, the official block theme, is broadly available

PR: #69820

Woo’s official block theme, Purple, is now available in the WordPress theme directory, Woo marketplace, and WordPress.com theme directory. For contributors, the theme’s development has moved to the WooCommerce monorepo, so any issues and PRs can be tracked alongside WooCommerce.

WooCommerce Beta Tester 4.00 has been released

WooCommerce Beta Tester 4.0.0 is available for testing upcoming WooCommerce releases. Update the plugin before trying the 11.3 prerelease. This version improves switching away from Live Branch builds and adds a template-loading tool to WCA Test Helper. Use it on a non-production site running WordPress 6.8 or newer. Get WooCommerce Beta Tester.

Developer advisories

Products API endpoints have updated on_sale, include, and exclude behavior

PR: #69432

Several endpoints related to products have been updated so the include and exclude parameters work in a more predictable way when used in combination with the on_sale parameter.

Expand for more details on the affected endpoints

The updated endpoints are:

  • Products Rest API: /wc/v2/products, /wc/v3/products
  • Product Variations Rest API: /wc/v2/products/<id>/variations, /wc/v3/products/<id>/variations, /wc/v3/variations.
  • Products Store API: /wc/store/v1/products

Using on_sale, include or exclude alone is unchanged. The only change is when the parameters are used in combination.

This table shows how these parameters behaved before 11.3, and how they behave in 11.3:

RequestBefore 11.311.3
on_sale=true + includeEvery included product, including ones not on sale, plus some other on-sale products when the on-sale list was longer than includeProducts that are both included and on sale
on_sale=false + includeThe include list. on_sale was ignoredIncluded products that are not on sale
on_sale=true + excludeexclude was ignoredOn-sale products, minus exclude
on_sale=false + excludeSome on-sale products could still be returnedProducts that are not on sale, minus exclude

For example, in a store with product 10 not on sale and products 13-18 on sale, on_sale=true&include=10,13 previously returned 10, 13, 15, 16, 17, 18. In WC 11.3 it will only return 13.

The new behavior matches what developers would expect and makes on_sale aligned with other parameters such as featured or type.

If your implementation is making a request to the endpoints listed above using on_sale in combination with include or exclude, we recommend re-testing it to make sure it matches your expectations.

Additional validation for refunds made via REST API

PR: #69302

Starting with WooCommerce 11.3, POST /wp-json/wc/v3/orders/{order_id}/refunds validates explicitly supplied line-item refund amounts before creating a refund. This change prevents the REST API from creating refunds whose line-item amounts contradict the overall refund amount or exceed what remains refundable. Previously, those requests could leave order records and Analytics reporting inconsistent.

Expand for more details and testing instructions

Integrations supplying line_items should check that:

  • Each nonzero allocation refers to a product, fee, or shipping line on the same order.
  • Each line’s refund_total plus its refund_tax values does not exceed that line’s original or remaining refundable amount.
  • The sum of the line allocations, including tax, does not exceed the refund’s top-level amount. If amount is omitted, supply numeric line amounts from which it can be calculated.
  • Refund amounts have the appropriate sign for their order line. Negative refunds remain supported for negative lines, such as discount fees.

Invalid requests can now return HTTP 400 or 422, with a woocommerce_rest_* error code, rather than creating a refund. For example, amount: “3” with a line-item refund_total: 4 returns 400 woocommerce_rest_invalid_refund_amount; an allocation above a line’s original or remaining balance returns a 422 error. Check the response before treating a refund as created, and correct the allocation rather than retrying the same payload.

Developers are affected if their integration creates refunds through POST /wp-json/wc/v3/orders/{order_id}/refunds with explicit line_items amounts that exceed the refund’s amount or a line’s remaining refundable balance. After updating to WooCommerce 11.3, those requests return HTTP 400 or 422 instead of creating a refund; check for woocommerce_rest_* error codes in the response.

Ensure refunds are within the bounds of the remaining refundable balance, and ensure the combined line amounts do not exceed the refund’s top-level amount. Handle HTTP 400 and 422 responses as validation failures: show or log the error, correct the request, and don’t assume a refund was created.

Changes to Product Collections and Product Filters

PRs: #68935, #69195, #69630

Product Collection now uses the same filtering engine as the Product Filters blocks for URL filters. This applies to collections with filtering enabled that don’t inherit the template query.

Extensions modifying generated filter clauses through query_loop_block_query_vars. For price, stock, attribute and taxonomy filters, modify query arguments such as max_price instead of the generated meta_query/tax_query clauses. Custom conditions and rating filters are unchanged.

The newly added woocommerce_product_filter_params hook lets developers localize URL filter parameters or rename them to avoid conflicts. It receives the full parameter map, but only taxonomy changes are supported in 11.3.

As an example, you can replace existing parameter names with Spanish parameter names (categorias, etiquetas, and marcas) by registering the filter before the parameter map is first read:

add_filter( 'woocommerce_product_filter_params', function ( $params ) {
    $params['taxonomy']['product_cat']   = 'categorias';
    $params['taxonomy']['product_tag']   = 'etiquetas';
    $params['taxonomy']['product_brand'] = 'marcas';
    return $params;
} );

Context helpers in product blocks

PR: #68533

WooCommerce 11.3 changes how the Single Product block passes product context to nested blocks. The previous implementation tracked descendant block names and could leak context into nested Product Collection, Query, or Single Product blocks. #68533 now uses WordPress block context, fixing #45374 and #45342.

This cleanup removes the protected extract_single_product_inner_block_names() method and the $product_id and $single_product_inner_blocks_names properties. Normal use of the block is unchanged.

Search extension or theme code for extract_single_product_inner_block_names. Only custom PHP subclasses that use them are affected. Before upgrading to WooCommerce 11.3, remove dependencies on the removed method and use WordPress block context instead.

Shop pages now associate directly with the Product Catalog template

PR: #68506

WooCommerce now associates the Shop page with the Product Catalog template when a block theme is active, allowing the page editor to preview that template.

This changes the value returned when reading the Shop page’s _wp_page_template metadata. Reads specifying this key return archive-product, including when the page has a custom template assignment saved in the database.

Expand for more details

For example, if the saved value is my-custom-template:

$shop_id = wc_get_page_id( 'shop' );
get_post_meta( $shop_id, '_wp_page_template', true );
// Before this change: 'my-custom-template'
// After this change:  'archive-product'

The saved metadata remains unchanged. However, the override applies wherever the WooCommerce filter is registered, including frontend, admin and REST requests. This metadata override does not affect other pages or stores using classic themes.

Developers should review extensions, themes, and integrations that use the Shop page’s template assignment. Look for:

  • Reads of _wp_page_template using get_post_meta().
  • Logic that reads or synchronizes the Shop page’s REST API template field.

You are affected if this code expects a previously saved custom template value while a block theme is active. To verify, activate a block theme, assign a page as the WooCommerce Shop page, and compare the values returned before and after the update. If your integration needs a different effective template value, use the get_post_metadata filter to override the logic.

VAT exemption and calculated shipping now survive customer logins

PR: #69615

In WooCommerce 11.3, a logged-in customer’s VAT exemption (is_vat_exempt) and calculated shipping (calculated_shipping) are kept in the session when the customer account is saved. Previously, any save to the account (including checkout saving the billing address) made the session out of date, so WooCommerce threw it away and both values reset to false. If a VAT-exempt customer had their payment declined, tax would be calculated after retrying payment. Addresses and other saved fields still reload from the account as before.

You’re affected if your code calls WC()->customer->set_is_vat_exempt( true ) and relies on an account save to clear it again. For example, exempting a customer with a valid VAT number and expecting that to drop when they change country. The flag now stays set until your code clears it, whether the account was saved by checkout, My Account, or an admin. Set the flag both ways, based on the customer’s current details, rather than only ever setting true:

function myplugin_update_vat_exempt() {
	WC()->customer->set_is_vat_exempt( myplugin_customer_is_exempt() );
}
add_action( 'woocommerce_store_api_cart_update_customer_from_request', 'myplugin_update_vat_exempt' ); // Checkout block.
add_action( 'woocommerce_checkout_update_order_review', 'myplugin_update_vat_exempt' ); // Classic checkout.

Payment methods now appear in Analytics Orders report

PRs: #68970, #69651

WooCommerce 11.3 adds the payment method to the Analytics Orders report. It shows as a table column, a CSV export column and a new advanced filter. To make this work, the update changes the wc_order_stats table.

Expand for a deeper dive into what changed:
  • wc_order_stats has a new nullable payment_method column. It holds the gateway ID, the same value as WC_Order::get_payment_method(). A refund takes the gateway of the order it refunds. Orders with no gateway stay NULL.
  • wc_order_stats has a new index, idx_payment_method_date_paid_status on (payment_method, date_paid, status).
  • The database update wc_update_1130_backfill_order_stats_payment_method() fills the column for existing orders. It runs in the background through Action Scheduler, 500 orders per batch.
  • Items from reports/orders have a new payment_method property.
  • reports/orders and reports/orders/stats accept two new optional arguments, payment_method_is and payment_method_is_not. Both take a list of gateway IDs.

No existing hook, field or query argument changes.

Custom SQL that joins wc_orders

Before 11.3, only wc_orders had a payment_method column, so custom Analytics SQL could use it without a table name. Now both tables have it.

If your code joins wc_orders into an Analytics query through the woocommerce_analytics_clauses_* filters and uses the bare payment_method, the query fails with: Column 'payment_method' in field list is ambiguous

On the Orders report, this shows an empty table. Search your woocommerce_analytics_clauses_* callbacks for payment_method. Code that reads _payment_method from post meta is not affected.

Large stores

WooCommerce adds the column and the index to wc_order_stats during the update. On MySQL 8.0 and later with InnoDB, adding the column is instant. The index builds without blocking writes, but MySQL still reads every row of the table to build it. On a large store, this takes time and disk I/O. The index needs about 47 MB per 1M orders.

On older database servers, for example MySQL 5.7, adding the column also rebuilds the whole table.

  • Add the table name to the column. For example, change payment_method = 'stripe' to {$wpdb->prefix}wc_orders.payment_method = 'stripe', or use the alias from your join. This works on earlier versions too.
  • If you join wc_orders only to get the payment method, on 11.3 you can drop the join and use wc_order_stats.payment_method or the new payment_method_is argument. Existing rows are empty until the background update fills them.
  • On large stores, plan the update for a quiet time.

Legacy Reports menu hidden on new stores

PRs: #69247, #69367

Analytics replaced the legacy WooCommerce > Reports screen years ago, but every new store still lists Reports in its menu. From 11.3, stores first installed on WooCommerce 11.3.0 or later no longer show it.

Expand to see more details for stores using legacy reports

What changed:

  • On a new store, WooCommerce hides the WooCommerce > Reports menu item. It also hides the top-level Sales reports item that users with view_woocommerce_reports see when they can’t access the WooCommerce menu.
  • Stores installed before 11.3.0 see no change. WooCommerce decides by the woocommerce_initial_installed_version option. A missing or malformed value counts as an existing store.
  • The page stays registered. admin.php?page=wc-reports, its tabs, the screen IDs (woocommerce_page_wc-reports, toplevel_page_wc-reports) and the view_woocommerce_reports capability all work as before.
  • WooCommerce hides the item with the hide-if-js class. The entry stays in the global $menu and $submenu arrays, so code that reads, reorders or removes it keeps working.
  • New filter: woocommerce_show_legacy_reports_menu. It takes one boolean, $show, and lets a plugin force the item on or off.

The menu stays visible on a new store when:

  • Analytics is disabled, or
  • an extension adds legacy reports: it hooks wc_reports_tabs or wc_admin_reports_path, or its woocommerce_admin_reports callback adds a report group or report that core doesn’t define, or replaces a core report’s callback.

Analytics-only entries added through woocommerce_admin_reports don’t count, so extensions that use that filter only for Analytics don’t bring the menu back.

You may be affected if your extension:

  • Adds a legacy report and registers its filters late. WooCommerce makes the decision at the end of admin_menu (priority PHP_INT_MAX). Report filters added after that, for example on admin_init or current_screen, aren’t seen, so the menu stays hidden on new stores even though your report exists. Your report still loads at admin.php?page=wc-reports; merchants just can’t find it in the menu.
  • Adds a legacy report only on certain screens. If your woocommerce_admin_reports callback checks the current screen before adding its report, it adds nothing when the menu is built, and the menu stays hidden.
  • Links merchants to WooCommerce > Reports by menu position, or tells them in docs or onboarding to “go to WooCommerce > Reports”. On a new store, that item isn’t there.
  • Checks for the Reports item in the menu, for example in end-to-end tests that click WooCommerce > Reports on a fresh install.

To check, install WooCommerce 11.3 on a fresh site, activate your extension and open the WordPress dashboard. To test against an existing store, run:

wp option update woocommerce_initial_installed_version 11.2.0

  • Register your woocommerce_admin_reports, wc_reports_tabs or wc_admin_reports_path callbacks on plugins_loaded or init, before admin_menu. Add the report on every request, not only on the Reports screen.
  • If you need the menu regardless, return true from the filter, and add the callback before admin_menu:

add_filter( 'woocommerce_show_legacy_reports_menu', '__return_true' );

  • Link straight to admin.php?page=wc-reports&tab=your-tab rather than describing the menu path.
  • Better still, move your reports to Analytics. WooCommerce is retiring the legacy Reports screen, and this change is one step.

Customer results on order edit screens are now keyed by user ID

PR: #36437

The woocommerce_json_search_found_customers filter runs both during AJAX customer searches and when WooCommerce renders the selected customer on an order edit screen. The purpose of this change is to bring these two filter call sites into parity, so extensions receive the same array shape whether filtering search results or the customer already selected on an order.

// Before WooCommerce 11.3, the order edit screen passed the selected registered customer with numeric key 0:

array(
	0 => 'Customer Name (#123 – customer@example.com)',
);
	
// Starting in 11.3, it uses the customer’s user ID as the key, matching the AJAX payload:
array(
	123 => 'Customer Name (#123 – customer@example.com)',
)

Guest orders continue to pass array( '' ). The displayed customer label and the filter’s return value are otherwise unchanged.

Search extension, theme, and custom code for callbacks attached to woocommerce_json_search_found_customers. Code is affected if the callback runs while rendering an order edit screen and directly accesses numeric key 0, such as $found_customers[0]. Callbacks using current(), reset(), or value iteration continue to work.

If the callback only needs the selected label, retrieve it without assuming a key: $label = current( $found_customers );. If it needs the customer ID, use the array key while accounting for the unchanged guest value.

Variation-price hashing enabled for new stores

PR: #68910

We are graduating the optimized hashing algorithm announced in WooCommerce 10.5 and enabling it by default for new stores to align with the original graduation timeline. Read the developer advisory for more information.

Test WooCommerce 11.3

Install the WooCommerce Beta Tester plugin, switch to the Beta channel, and update to 11.3.0-beta.1. Please read our Beta Testing documentation for more info.


Leave a Reply

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