How to Avoid Excess Meta with WooCommerce Subscriptions

Orders in WooCommerce create a lot data in the postmeta table. There’s the standard WooCommerce fields (like _order_key, _cart_hash, _billing_first_name, etc.), but payment gateway plugins, marketing integrations, and other WooCommerce plugins also generate their own metadata. It’s not unusual to see more than 60 rows of metadata for each order.

I recommend the Post Meta Inspector to easily view all the metadata on any order or post. You may be surprised how much there is!

If you run subscriptions on your site and generate a lot of renewals, WooCommerce Subscriptions may be creating a lot of unneeded metadata due to how subscriptions and renewal orders are generated. This can cause your database to grow really quickly.

WooCommerce Subscriptions copies all custom fields (i.e. metadata) set on an order during checkout to the subscription/s created for that order during checkout. When the subscription renews, Subscriptions will then copy that data to the renewal order too.

If you haven’t audited your metadata, there’s likely quite a few fields (metadata) being saved to parent orders that do not need to be copied into the subscription and then into each renewal.

A good example is “_customer_user_agent” and “_customer_ip_address”. These are pieces of metadata that really only make sense on the original parent order, and in my opinion do not need to get copied to the subscription or any renewal orders.

Many sites also have custom checkout fields like “gift_message”, “where did you hear about us” or marketing fields that should only apply to the first order. I’d suggest looking through your shop_subscription post types and shop_order renewals to see what data you’re storing but may not need.

WooCommerce Subscriptions offers two filters prevent metadata from copying: ‘wcs_subscription_meta’ and ‘wcs_renewal_order_meta’.

Here’s an example file showing how we prevent meta from copying at Universal Yums. In additional to removing meta from subscriptions and renewals, we also remove some metadata when an order completes and it is no longer needed.

<?php
/**
* Some meta data should not be copied to subscriptions or renewal orders.
*/
namespace UniversalYums\Subscriptions;
class PreventMetaCopy {
/**
* The single instance of the class.
*/
protected static $instance;
/**
* Prevents these meta keys from copying to subscriptions.
* Ordinarily these would copy over from the parent order.
*
* The Metorik meta does not need to copy to the subscription,
* it should only apply to the parent order.
*/
protected static $subscription_meta = array(
'_metorik_source_type',
'_metorik_referer',
'_metorik_utm_source',
'_metorik_utm_medium',
'_metorik_session_entry',
'_metorik_session_start_time',
'_metorik_session_pages',
'_metorik_session_count',
'_metorik_cart_token',
'_metorik_utm_campaign',
'_metorik_utm_content',
'_metorik_utm_term',
);
/**
* Prevents these meta keys from copying to renewal orders.
* Ordinarily these would copy over from the subscription order.
*
* User agent and IP address are just artifacts from the original order.
* We're keeping them on the subscription order for now, but may remove later.
*/
protected static $order_meta = array(
'_customer_user_agent',
'_customer_ip_address',
'gift_message',
'gift_message_to',
'gift_message_from',
'shareasale-wc-tracker-triggered',
);
/**
* Meta to remove when order gets status competed.
*/
protected static $completed_order_meta = array(
'shareasale-wc-tracker-triggered',
);
/**
* Main PreventMetaCopy Instance.
*
* Ensures only one instance of the PreventMetaCopy is loaded or can be loaded.
* To load the instance: UniversalYums\Subscriptions\PreventMetaCopy::instance();
*
* @return PreventMetaCopy - Main instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
public function __construct() {
// This removes the meta from the subscription.
add_filter( 'wcs_subscription_meta', array( $this, 'prevent_subscription_meta_copy' ) );
// This removes the meta from renewal orders.
add_filter( 'wcs_renewal_order_meta', array( $this, 'prevent_renewal_meta_copy' ) );
// When order status is completed, remove some meta.
add_action( 'woocommerce_order_status_completed', array( $this, 'remove_meta_on_order_status_completed' ) );
}
/**
* Delete post meta from the order once the order is completed.
*
* @param $order_id
*/
public function remove_meta_on_order_status_completed( $order_id ) {
foreach ( self::$completed_order_meta as $meta_key ) {
delete_post_meta( $order_id, $meta_key );
}
}
/**
* Prevent meta data from copying to subscriptions.
*
* @param $meta
*
* @return mixed
*/
public function prevent_subscription_meta_copy( $meta ) {
return self::filter_meta( $meta, self::$subscription_meta );
}
/**
* Prevent meta data from copying to renewals.
*
* @param $meta
*
* @return mixed
*/
public function prevent_renewal_meta_copy( $meta ) {
return self::filter_meta( $meta, self::$order_meta );
}
/**
* Remove meta with given meta keys from the array.
*
* @param $meta
* @param $meta_to_remove
*
* @return mixed
*/
public static function filter_meta( $meta, $meta_to_remove ) {
foreach ( $meta_to_remove as $meta_key_to_remove ) {
$key = array_search( $meta_key_to_remove, wp_list_pluck( $meta, 'meta_key' ), true );
if ( false !== $key ) {
unset( $meta[ $key ] );
}
}
return $meta;
}
}

If you’re interested in working on WooCommerce at scale, we’re hiring on my team at Universal Yums. Check out the job description.

Leave a Reply