Compatibility Module¶
The Compatibility module provides a stable, version-agnostic API for working with WooCommerce features that change across major releases. The primary focus is HPOS (High-Performance Order Storage) compatibility.
Overview¶
The Compatibility module handles:
- HPOS (Custom Order Tables) compatibility
- WooCommerce version detection
- Order meta operations
- Admin screen detection
Key Classes¶
| Class | File | Purpose |
|---|---|---|
Woodev_Order_Compatibility |
compatibility/class-order-compatibility.php |
HPOS-safe order operations |
Woodev_Plugin_Compatibility |
compatibility/class-plugin-compatibility.php |
WC version checks |
Declaring HPOS Support¶
Plugin Declaration¶
Declare HPOS support in your plugin constructor:
<?php
parent::__construct(
'my-plugin',
'1.0.0',
[
'text_domain' => 'my-plugin',
'supported_features' => [
'hpos' => true, // Declare HPOS compatibility
],
]
);
Woodev_Plugin automatically registers the before_woocommerce_init hook based on this flag.
Full Example¶
<?php
class My_Plugin extends Woodev_Plugin {
private static $instance;
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public function __construct() {
parent::__construct(
'my-plugin',
'1.0.0',
[
'text_domain' => 'my-plugin',
'supported_features' => [
'hpos' => true,
],
]
);
}
public function get_file(): string {
return __FILE__;
}
public function get_plugin_name(): string {
return __( 'My Plugin', 'my-plugin' );
}
public function get_download_id(): int {
return 0;
}
}
Order Meta Operations¶
Woodev_Order_Compatibility provides static methods that work with both HPOS and classic order storage.
Reading Order Meta¶
<?php
// Woodev_Order_Compatibility is a global class (no namespace required)
$order = wc_get_order( $order_id );
// Get single meta value
$tracking = Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_number' );
// Get all values for a key
$all_values = Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_numbers', false );
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
$order |
WC_Order\|int |
required | Order object or ID |
$meta_key |
string |
required | Meta key to retrieve |
$single |
bool |
true |
Return single value or array |
Writing Order Meta¶
<?php
$order = wc_get_order( $order_id );
// Add meta (allows duplicates)
Woodev_Order_Compatibility::add_order_meta( $order, '_shipment_id', '12345' );
// Update meta (single value)
Woodev_Order_Compatibility::update_order_meta( $order, '_tracking_number', 'ABC123' );
// Update sync status
Woodev_Order_Compatibility::update_order_meta( $order, '_sync_status', 'completed' );
Deleting Order Meta¶
<?php
// Delete all meta with key
Woodev_Order_Compatibility::delete_order_meta( $order, '_temporary_data' );
// Delete specific value
Woodev_Order_Compatibility::delete_order_meta( $order, '_shipment_id', '12345' );
Checking Meta Existence¶
<?php
if ( Woodev_Order_Compatibility::order_meta_exists( $order, '_tracking_number' ) ) {
$tracking = Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_number' );
echo "Tracking: {$tracking}";
} else {
echo "No tracking number";
}
Complete Example¶
<?php
class Order_Meta_Manager {
/**
* Add tracking to order
*/
public function add_tracking( WC_Order $order, string $tracking, string $carrier ): void {
Woodev_Order_Compatibility::update_order_meta(
$order,
'_tracking_number',
sanitize_text_field( $tracking )
);
Woodev_Order_Compatibility::update_order_meta(
$order,
'_carrier',
sanitize_text_field( $carrier )
);
Woodev_Order_Compatibility::update_order_meta(
$order,
'_tracking_added',
current_time( 'mysql' )
);
$order->add_order_note( sprintf(
__( 'Tracking %1$s added via %2$s', 'my-plugin' ),
$tracking,
$carrier
) );
}
/**
* Get tracking from order
*/
public function get_tracking( WC_Order $order ): array {
return [
'number' => Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_number' ),
'carrier' => Woodev_Order_Compatibility::get_order_meta( $order, '_carrier' ),
'added' => Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_added' ),
];
}
/**
* Remove tracking
*/
public function remove_tracking( WC_Order $order ): void {
Woodev_Order_Compatibility::delete_order_meta( $order, '_tracking_number' );
Woodev_Order_Compatibility::delete_order_meta( $order, '_carrier' );
Woodev_Order_Compatibility::delete_order_meta( $order, '_tracking_added' );
$order->add_order_note( __( 'Tracking removed', 'my-plugin' ) );
}
}
Admin Screen Detection¶
Orders Screen¶
<?php
// Woodev_Order_Compatibility is a global class (no namespace required)
// Check if on orders list
if ( Woodev_Order_Compatibility::is_orders_screen() ) {
// Enqueue orders-specific assets
wp_enqueue_style( 'my-plugin-orders' );
}
// Check for specific status
if ( Woodev_Order_Compatibility::is_orders_screen_for_status( 'processing' ) ) {
// Show processing orders notice
}
Order Edit Screen¶
<?php
// Check if on order edit screen
if ( Woodev_Order_Compatibility::is_order_edit_screen() ) {
// Show order meta fields
add_action( 'admin_footer', [ $this, 'render_order_meta_fields' ] );
}
// Get order ID from screen
$order_id = Woodev_Order_Compatibility::get_order_id_for_order_edit_screen();
if ( $order_id ) {
$order = wc_get_order( $order_id );
// Work with order
}
Order Screen URLs¶
<?php
// Get orders screen URL
$orders_url = Woodev_Order_Compatibility::get_orders_screen_url();
// Get edit URL for order
$edit_url = Woodev_Order_Compatibility::get_edit_order_url( $order_id );
// Get order screen ID
$screen_id = Woodev_Order_Compatibility::get_order_screen_id();
// Use in add_meta_box
add_meta_box(
'my-plugin-order-data',
__( 'Shipping Info', 'my-plugin' ),
[ $this, 'render_shipping_meta_box' ],
Woodev_Order_Compatibility::get_order_screen_id(),
'side',
'default'
);
Database Table Names¶
For raw SQL queries:
<?php
global $wpdb;
// Get correct table names
$orders_table = Woodev_Order_Compatibility::get_orders_table();
$meta_table = Woodev_Order_Compatibility::get_orders_meta_table();
// Query orders with custom meta
$results = $wpdb->get_results( $wpdb->prepare(
"SELECT o.id, m.meta_value as tracking
FROM {$orders_table} o
INNER JOIN {$meta_table} m ON o.id = m.order_id
WHERE m.meta_key = %s AND m.meta_value != ''",
'_tracking_number'
) );
// Get order post types
$post_types = Woodev_Order_Compatibility::get_order_post_types();
Order Type Detection¶
<?php
$order = wc_get_order( $order_id );
// Check if object is an order
if ( Woodev_Order_Compatibility::is_order( $order ) ) {
// Work with order
}
// Check if object is a refund
if ( Woodev_Order_Compatibility::is_refund( $order ) ) {
// Handle refund
}
Formatted Meta Data¶
<?php
$order = wc_get_order( $order_id );
// Get formatted meta data for display
$meta_data = Woodev_Order_Compatibility::get_item_formatted_meta_data( $order_item );
foreach ( $meta_data as $meta ) {
echo esc_html( $meta->display_key ) . ': ' . esc_html( $meta->value );
}
WooCommerce Version Checks¶
Woodev_Plugin_Compatibility provides version comparison helpers.
Version Comparison¶
<?php
// Woodev_Plugin_Compatibility is a global class (no namespace required)
// Check if WC >= version
if ( Woodev_Plugin_Compatibility::is_wc_version_gte( '8.0' ) ) {
// Use WC 8.0+ API
}
// Check if WC < version
if ( Woodev_Plugin_Compatibility::is_wc_version_lt( '7.0' ) ) {
// Fall back to older API
}
// Check if WC > version
if ( Woodev_Plugin_Compatibility::is_wc_version_gt( '8.5' ) ) {
// Use new feature
}
// Check exact version
if ( Woodev_Plugin_Compatibility::is_wc_version( '8.0' ) ) {
// Exact match
}
Getting Version Information¶
<?php
// Get current WC version
$version = Woodev_Plugin_Compatibility::get_wc_version();
echo "WooCommerce {$version}"; // e.g., "9.4.1"
// Get latest WC versions
$versions = Woodev_Plugin_Compatibility::get_latest_wc_versions();
// Returns: ['9.4.1', '9.4.0', '9.3.2', ...]
// Check if HPOS is enabled
if ( Woodev_Plugin_Compatibility::is_hpos_enabled() ) {
// HPOS is active
}
Version-Specific Features¶
<?php
class WC_Version_Handler {
public function init() {
// Use new HPOS CRUD API (WC 7.6+)
if ( Woodev_Plugin_Compatibility::is_wc_version_gte( '7.6' ) ) {
add_action( 'woocommerce_order_updated', [ $this, 'handle_update_new' ] );
} else {
add_action( 'woocommerce_update_order', [ $this, 'handle_update_old' ] );
}
// Use new email API (WC 8.0+)
if ( Woodev_Plugin_Compatibility::is_wc_version_gte( '8.0' ) ) {
add_filter( 'woocommerce_email_headers', [ $this, 'add_headers' ], 10, 4 );
} else {
add_filter( 'woocommerce_email_headers', [ $this, 'add_headers_old' ], 10, 3 );
}
}
}
Practical Examples¶
Example 1: Custom Order Meta Box¶
<?php
class Order_Meta_Box {
public function __construct() {
add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] );
add_action( 'woocommerce_process_shop_order_meta', [ $this, 'save_meta' ] );
}
public function add_meta_box() {
add_meta_box(
'my-plugin-delivery-date',
__( 'Delivery Date', 'my-plugin' ),
[ $this, 'render_meta_box' ],
Woodev_Order_Compatibility::get_order_screen_id(),
'side',
'default'
);
}
public function render_meta_box( $post_or_order ) {
$order = ( $post_or_order instanceof WP_Post )
? wc_get_order( $post_or_order->ID )
: $post_or_order;
if ( ! $order ) {
return;
}
$delivery_date = Woodev_Order_Compatibility::get_order_meta(
$order,
'_delivery_date',
true
);
wp_nonce_field( 'my-plugin-save-order-meta', 'my_plugin_order_meta_nonce' );
?>
<p>
<label for="delivery_date"><?php esc_html_e( 'Delivery Date:', 'my-plugin' ); ?></label>
<input type="date"
id="delivery_date"
name="delivery_date"
value="<?php echo esc_attr( $delivery_date ); ?>"
min="<?php echo esc_attr( date( 'Y-m-d' ) ); ?>"
class="regular-text" />
</p>
<?php
}
public function save_meta( int $order_id ): void {
if ( ! isset( $_POST['my_plugin_order_meta_nonce'] ) ||
! wp_verify_nonce( $_POST['my_plugin_order_meta_nonce'], 'my-plugin-save-order-meta' ) ) {
return;
}
if ( ! current_user_can( 'edit_shop_order', $order_id ) ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
if ( isset( $_POST['delivery_date'] ) ) {
$date = sanitize_text_field( $_POST['delivery_date'] );
if ( ! empty( $date ) ) {
Woodev_Order_Compatibility::update_order_meta(
$order,
'_delivery_date',
$date
);
} else {
Woodev_Order_Compatibility::delete_order_meta(
$order,
'_delivery_date'
);
}
}
}
}
Example 2: Orders List Column¶
<?php
class Orders_List_Columns {
public function __construct() {
// HPOS
add_filter( 'woocommerce_shop_order_list_table_columns', [ $this, 'add_column' ] );
add_action( 'woocommerce_shop_order_list_table_custom_column', [ $this, 'render_column' ], 10, 2 );
// Classic (fallback)
add_filter( 'manage_edit-shop_order_columns', [ $this, 'add_column_classic' ] );
add_action( 'manage_shop_order_posts_custom_column', [ $this, 'render_column_classic' ] );
}
public function add_column( $columns ) {
$new_columns = [];
foreach ( $columns as $key => $label ) {
$new_columns[ $key ] = $label;
if ( 'order_status' === $key ) {
$new_columns['tracking_number'] = __( 'Tracking', 'my-plugin' );
}
}
return $new_columns;
}
public function render_column( $column, $order ) {
if ( 'tracking_number' !== $column ) {
return;
}
$tracking = Woodev_Order_Compatibility::get_order_meta(
$order,
'_tracking_number',
true
);
if ( $tracking ) {
echo esc_html( $tracking );
} else {
echo '<span class="na">—</span>';
}
}
// Classic compatibility
public function add_column_classic( $columns ) {
return $this->add_column( $columns );
}
public function render_column_classic( $column ) {
global $post;
$order = wc_get_order( $post->ID );
$this->render_column( $column, $order );
}
}
Example 3: Order Search¶
<?php
class Order_Search {
public function __construct() {
add_filter( 'woocommerce_shop_order_search_fields', [ $this, 'add_search_fields' ] );
add_filter( 'woocommerce_shop_order_search_results', [ $this, 'search_by_tracking' ], 10, 3 );
}
public function add_search_fields( $search_fields ) {
$search_fields[] = '_tracking_number';
return $search_fields;
}
public function search_by_tracking( $order_ids, $term, $search_fields ) {
global $wpdb;
$meta_table = Woodev_Order_Compatibility::get_orders_meta_table();
$tracking_orders = $wpdb->get_col( $wpdb->prepare(
"SELECT DISTINCT order_id FROM {$meta_table}
WHERE meta_key = '_tracking_number'
AND meta_value LIKE %s",
'%' . $wpdb->esc_like( $term ) . '%'
) );
return array_merge( $order_ids, $tracking_orders );
}
}
Example 4: Bulk Actions¶
<?php
class Order_Bulk_Actions {
public function __construct() {
// HPOS
add_filter( 'bulk_actions-woocommerce_page_wc-orders', [ $this, 'add_actions' ] );
add_filter( 'handle_bulk_actions-woocommerce_page_wc-orders', [ $this, 'handle' ], 10, 3 );
// Classic
add_filter( 'bulk-actions-edit-shop_order', [ $this, 'add_actions' ] );
add_filter( 'handle_bulk_actions-edit-shop_order', [ $this, 'handle' ], 10, 3 );
}
public function add_actions( $actions ) {
$actions['mark_shipped'] = __( 'Mark as Shipped', 'my-plugin' );
return $actions;
}
public function handle( $redirect, $action, $order_ids ) {
if ( 'mark_shipped' !== $action ) {
return $redirect;
}
$count = 0;
foreach ( $order_ids as $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
continue;
}
Woodev_Order_Compatibility::update_order_meta(
$order,
'_shipped_date',
current_time( 'mysql' )
);
$order->add_order_note( __( 'Marked as shipped', 'my-plugin' ) );
$count++;
}
return add_query_arg( 'shipped_count', $count, $redirect );
}
}
// Show notice
add_action( 'admin_notices', function() {
if ( isset( $_GET['shipped_count'] ) ) {
$count = absint( $_GET['shipped_count'] );
echo '<div class="updated"><p>';
printf(
_n( '%d order marked as shipped', '%d orders marked as shipped', $count, 'my-plugin' ),
$count
);
echo '</p></div>';
}
} );
Best Practices¶
1. Use Compatibility Helpers¶
<?php
// ❌ Don't - breaks with HPOS
$tracking = get_post_meta( $order_id, '_tracking_number', true );
// ✅ Do - works with both
$tracking = Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_number', true );
2. Type-Hint Order Parameters¶
<?php
function process_order( WC_Order $order ) {
$tracking = Woodev_Order_Compatibility::get_order_meta( $order, '_tracking_number' );
}
3. Handle Both Post and Order Objects¶
<?php
function render_meta_box( $post_or_order ) {
$order = ( $post_or_order instanceof WP_Post )
? wc_get_order( $post_or_order->ID )
: $post_or_order;
if ( ! $order instanceof WC_Order ) {
return;
}
}
4. Use Screen Detection¶
<?php
add_action( 'admin_enqueue_scripts', function( $hook_suffix ) {
if ( ! Woodev_Order_Compatibility::is_order_edit_screen() ) {
return;
}
wp_enqueue_script( 'my-plugin-orders' );
} );
5. Cache Version Checks¶
<?php
class Feature_Flags {
private static $is_wc_gte_8;
public static function is_wc_8_or_higher(): bool {
if ( null === self::$is_wc_gte_8 ) {
self::$is_wc_gte_8 = Woodev_Plugin_Compatibility::is_wc_version_gte( '8.0' );
}
return self::$is_wc_gte_8;
}
}
Troubleshooting¶
Meta Not Saving¶
- Verify HPOS declaration:
```php
<?php
// Check at right time
add_action( 'load-woocommerce_page_wc-orders', function() {
// Runs on orders page
} );
add_action( 'load-post.php', function() {
// Runs on order edit (classic)
} );