Skip to content

2.6.x to 3.0.0 Developer Migration Notes

Claudio Sanches edited this page Apr 16, 2019 · 19 revisions

Table of Contents

Virtual orders do not have shipping addresses

In 2.6.x, orders containing virtual items only would copy the billing address to the shipping address on save.

In 3.0.x, orders containing virtual items will not have shipping addresses because the order is not shippable. Code that requires a shipping address should fall back to the billing address if not available.

To detect this, see if the order has shipping_address_2 or shipping_address_2 set, or use the ->has_shipping_address() method after 3.0.4.

Accessing $item['item_meta'] directly

When accessing this via an item object, $item['item_meta'] will return an array of values under 2.6, but a single meta value under 3.0.7.

We recommend using $item->get_meta() instead as accessing legacy data is not encouraged.

get_qty_refunded_for_item handling

In 2.6.x this would return a negative count (bug). In 3.0.x this is a positive count representing how many units have been refunded.

set_payment_method does not update meta values

In 3.0.0, using $order->set_payment_method() also requires a call to $order->save() to persist the data.

Data filters differ when extending classes with a different object type

Filters in objects take the format:


For example, a width prop in a product object has the filter: woocommerce_product_get_width

If you extending products and used a custom object type in place of product, your filter name would differ.

See this issue for more details.

Notification email sending

In 2.6.x, email notifications were sent in the same request as the order status change.

In 3.0.0, they are instead queued using WP Cron and send in a separate request.

If using actions to stop emails sending, your code will need to handle the new system.

Order post dates

As pointed out in, orders in 2.6.x would have their post dates updated when moving from pending to some other status.

In 3.0.0, the post date is the date the order was created. This will no longer change unless explicitly set.

Display of variable product and 'free' prices

Prices have been standardized in 3.0.0.

Variable products which show ranges and are on sale no longer show the striked out prices since these are long and confusing.



<del>$20.99 - $25.99</del> <ins>$10 - $12</ins>


$10 - $12

Prices with $0 price no longer display as free. This previously caused inconsistencies with ranges and with localization.

Product visibility is taxonomy based instead of meta based

3.0.0 instroduces a new product visibility taxonomy; catalog, search, hidden, featured, outofstock are terms. These are set on upgrade and help filter products in the catalog during frontend queries.

Removed "items" column in orders panel

This column previously showed a toggle list of items in the order for quick viewing, however, this was a performance bottleneck and has been removed. It was loading all items for all orders on screen; loading time is much improved with it gone.

Removed product downloads update for past orders

In 2.6.x if you edited a product's downloadable files, it would attempt to find all orders of said product and update download permissions.

This was not only a performance bottleneck, but also unexpected behavior as it would grant access to new files for old perchasers without prompting.

This has been removed in 3.0.0. Editing a file will however still update past purchases as it should.

Automatic tax rate sorting

In 2.6.x, the order in which tax rates were displayed and used were sortable with drag and drop.

In 3.0.0, manual sorting is removed. Tax rates will now sort logically based on the zip codes/cities/postcodes/priorities defined.

Product schema markup moved to JSON LD format

2.6.x showed output markup inline. This has implications if a theme changed markup or missed certain elements.

In 3.0.x, this is now output in the footer of the site in JSON LD format instead. For more details, see

Auto-capture of authorised paypal payments

Payments authorized in 2.6.x using PayPal would not be captured at all and would require manual admin intervention to capture funds.

In 3.0.x, these funds will be captured automatically when the order is marked 'complete' using the PayPal API.

Cart percent/Product percent coupon types merged

Coupons had Cart percent and Product percent coupon types in 2.6.x. In 3.0.0 these are merged since they provide the exact same amount of discount regardless.

Array return values being swapped out with objects implementing ArrayAccess

In some places, 2.6.x returned arrays representing data such as taxes. In 3.0.0, proper classes have been added instead. These new classes implement ArrayAccess so old code trying to reference values in the 'array' still functions.

There is one caveat to this; if you check returned data to see if it's an array with is_array this check will return false since it's not an array in 3.0.0, it's an object. This will fail silently so needs to be checked in the extension code.

New classes implementing ArrayAccess as are follows:

  • WC_Product_Download
  • WC_Product_Attribute
  • WC_Order_Item
    • WC_Order_Item_Coupon
    • WC_Order_Item_Fee
    • WC_Order_Item_Product
    • WC_Order_Item_Shipping
    • WC_Order_Item_Tax
  • WC_Customer_Download

This is a list of methods which returned arrays in 2.6.x, but objects in 3.0.0:

  • WC_Product::get_file()
  • WC_Product::get_files() (returns an array of objects)
  • WC_Product::get_attributes() (returns an array of objects)
  • WC_Abstract_Order::get_items(); (returns an array of objects)
  • WC_Abstract_Order::get_fees() (returns an array of objects)
  • WC_Abstract_Order::get_taxes() (returns an array of objects)
  • WC_Abstract_Order::get_shipping_methods() (returns an array of objects)


Variation actions/filters prefixes

woocommerce_get_regular_price/sale_price filters were in the abstract in 2.6.x. For products, these hooks are replaced with woocommerce_get_product_regular_price and woocommerce_get_product_sale_price and mapped so existing filters continue to function.

The exception is with product variations - these have their own hook prefix making them woocommerce_get_product_variation_regular_price and woocommerce_get_product_variation_sale_price. There is no mapping in place for these filters.

Objects are passed by reference to actions/filters

If you're being passed a CRUD object via an action or filter hook, changes you make to said object will persist. If you need to change object properties without doing this, clone the variable.

// Create a clone to ensure item totals will not be modified permanently.
$item = clone $item;

Variation IDs

In 2.6.x, $variation->id was the ID of the parent product, and $variation->variation_id was the ID of the variation. Backwards compatibility is maintained, but when moving to CRUD ensure you use the correct getters and setters.

$variation->get_id() is the variation ID. $variation->get_parent_id() is the parent product ID.

Select2 Version 4

From WC Beta 2, we've migrated to Select2 V4.

Select2 V4 is mostly compatible with Select2 V3 with a few exceptions, the main one being how AJAX search inputs work. WooCommerce has two instances of these which are affected and need some HTML markup changes to function.

wc-product-search inputs

These inputs were 'hidden' text inputs in v3. In v4 these needs to be changed to regular multiselect elements.


<input type="hidden" id="grant_access_id" name="grant_access_id" data-multiple="true" class="wc-product-search" style="width: 400px;" data-placeholder="<?php esc_attr_e( 'Search for a downloadable product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_downloadable_products_and_variations" />

Would be changed to a regular multiselect field:

<select id="grant_access_id" class="wc-product-search" name="grant_access_id[]" multiple="multiple" style="width: 400px;" data-placeholder="<?php esc_attr_e( 'Search for a downloadable product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_downloadable_products_and_variations"></select>

Field name

Note that the field name in this case was changed from grant_access_id to grant_access_id[].

Multiple attribute

data-multiple is no longer needed. The multiple prop is added instead. This supports multiple selections.

Handling the POSTed data

In the previous example, the hidden input would have posted a comma delimited string of IDs. With the switch to multi selects, this code needs to handle an array of values instead.

wc-customer-search inputs

Handled in the same way as wc-product-search.

Checking POSTed data

Unlike hidden inputs, if no selections are made multi-selects do not POST. Your code needs to check if the field isset() to deal with posted values and handles scenarios where no selections are made.

Order version property

With 2.3.7-2.6.x:

changing order line items via the admin interface would update them to the WC > 2.3.7 structure (in wc_save_order_items()), because of this, it would update the order version. This is the only time the version data is actually different between WC < 2.3.7 and 2.3.7-2.6.14 so it's the only time the order version if updated. changing order line items via any other method, be it the REST API, CLI or another API function (e.g. WC_Abstract_Order::add_tax()) would not definitively update the structure to the WC > 2.3.7 structure, so it would not update the order version. The calling code could update them to the newer structure, and if it chose to, it would be responsible for also updating the order version. changing any other data via any other method won't update the order version, because nothing has changed in terms of how the data is stored. tl;dr - the order version on orders with WC < 3.0.0 almost never changed, because the data structure almost never changed.

With 3.0.0:

changing order line items via the admin interface would update them to the WC > 2.3.7 structure so it would update the order version (even if there are no changes to the structure). changing order line items via any other method, be it the REST API, CLI or another API function would update the structure to the WC > 3.0.0 structure, so it would update the order version (even if there are no changes to the structure). changing any other data via any other method would update the order version, even if nothing has changed in terms of how the order is being stored, but if something has changed, it will be updated. tl;dr - the order version on orders with WC 3.0.0+ will always change, even if the data structure hasn't changed, but the order version will also always reflect the version of the structure.

Order address indexes

Order address indexes are introduced in WooCommerce 3.0 to improve search results in the wp-admin, those indexes should be created automatically when an order is created or have changes on the address, although orders placed before 3.0 shouldn't contain any indexes, so in order to generate missing you can use the "Order address indexes" tool that can be found in "WooCommerce" > "Tools", this tool is also avaliable on WP-CLI with the follow command:

wp wc tool run add_order_indexes --user=<USER_ID>

Note: Since WooCommerce 3.6 this tool has been converted into a plugin:

Template changes

File Description
cart/cart.php CRUD compatibility.
cart/cross-sells.php Removes WP_Query for performance reasons.
checkout/thankyou.php Mark
emails/email-order-items.php CRUD compatibility.
emails/plain/email-order-items.php CRUD compatibility.
myaccount/downloads.php Improved download table layout.
order/order-details-customer.php CRUD compatibility.
order/order-details-item.php CRUD compatibility.
order/order-details.php CRUD compatibility.
single-product/add-to-cart/grouped.php Checks grouped products exist correct, and CRUD compatibility.
single-product/meta.php CRUD compatibility.
single-product/photoswipe.php Handles the new photoswipe gallery.
single-product/price.php CRUD compatibility.
single-product/product-attributes.php CRUD compatibility.
single-product/product-image.php New image gallery.
single-product/product-thumbnails.php New image gallery.
single-product/related.php Removes WP_Query for performance reasons.
single-product/up-sells.php Removes WP_Query for performance reasons.
single-product/review-meta.php CRUD compatibility.
single-product/review-rating.php CRUD compatibility.
single-product/stock.php New template to output stock HTML.
single-product/tabs/additional-information.php CRUD compatibility.
checkout/form-billing.php Extra wrappers.

Deprecated functions

  • woocommerce_get_product_schema
  • _wc_save_product_price
  • All methods in the legacy (backwards compatibility) classes:
    • WC_Legacy_Shipping_Zone
    • WC_Abstract_Legacy_Product
    • WC_Legacy_Payment_Token
    • WC_Abstract_Legacy_Order
    • WC_Legacy_Coupon
    • WC_Legacy_Customer

Deprecated filters

Filter Replacement
woocommerce_email_order_schema_markup woocommerce_structured_data_order
add_to_cart_fragments woocommerce_add_to_cart_fragments
add_to_cart_redirect woocommerce_add_to_cart_redirect
woocommerce_product_width woocommerce_product_get_width
woocommerce_product_height woocommerce_product_get_height
woocommerce_product_length woocommerce_product_get_length
woocommerce_product_weight woocommerce_product_get_weight
woocommerce_get_sku woocommerce_product_get_sku
woocommerce_get_price woocommerce_product_get_price
woocommerce_get_regular_price woocommerce_product_get_regular_price
woocommerce_get_sale_price woocommerce_product_get_sale_price
woocommerce_product_tax_class woocommerce_product_get_tax_class
woocommerce_get_stock_quantity woocommerce_product_get_stock_quantity
woocommerce_get_product_attributes woocommerce_product_get_attributes
woocommerce_product_gallery_attachment_ids woocommerce_product_get_gallery_image_ids
woocommerce_product_review_count woocommerce_product_get_review_count
woocommerce_product_files woocommerce_product_get_downloads
woocommerce_get_currency woocommerce_order_get_currency
woocommerce_order_amount_discount_total woocommerce_order_get_discount_total
woocommerce_order_amount_discount_tax woocommerce_order_get_discount_tax
woocommerce_order_amount_shipping_total woocommerce_order_get_shipping_total
woocommerce_order_amount_shipping_tax woocommerce_order_get_shipping_tax
woocommerce_order_amount_cart_tax woocommerce_order_get_cart_tax
woocommerce_order_amount_total woocommerce_order_get_total
woocommerce_order_amount_total_tax woocommerce_order_get_total_tax
woocommerce_order_amount_total_discount woocommerce_order_get_total_discount
woocommerce_order_amount_subtotal woocommerce_order_get_subtotal
woocommerce_order_tax_totals woocommerce_order_get_tax_totals
woocommerce_refund_amount woocommerce_get_order_refund_get_amount
woocommerce_refund_reason woocommerce_get_order_refund_get_reason
default_checkout_country default_checkout_billing_country
default_checkout_state default_checkout_billing_state
default_checkout_postcode default_checkout_billing_postcode
woocommerce_single_product_image_html Removed - new gallery has not 'single' image. All images are ran through woocommerce_single_product_image_thumbnail_html
Clone this wiki locally