WooCommerce CRUD & Data Stores
Before writing code
Fetch live docs:
- Web-search
site:developer.woocommerce.com crud data stores for CRUD documentation
- Web-search
site:github.com woocommerce woocommerce HPOS for HPOS architecture
- Fetch
https://github.com/woocommerce/woocommerce/wiki for architecture decisions
CRUD Architecture
Data Objects
WooCommerce's core entities extend WC_Data:
WC_Product — products (and subtypes: WC_Product_Simple, WC_Product_Variable, etc.)
WC_Order — orders (and WC_Order_Refund)
WC_Customer — customer profiles
WC_Coupon — discount coupons
WC_Shipping_Zone — shipping zones
WC_Data Base Class
All CRUD objects inherit from WC_Data:
$data — array of core properties with defaults
$meta_data — array of WC_Meta_Data objects
$id — object ID
$object_type — string identifier (e.g., 'product', 'order')
$data_store — the WC_Data_Store instance handling persistence
Getters and Setters
get_{prop}( $context = 'view' ) — retrieve a property
'view' context — runs woocommerce_{object_type}_get_{prop} filter (for display)
'edit' context — returns raw stored value (for forms/admin)
set_{prop}( $value ) — set a property (in memory, not persisted until save())
save() — persist to database (calls data store's create() or update())
delete( $force_delete ) — remove from database
Meta Data
get_meta( $key, $single, $context ) — get meta value
update_meta_data( $key, $value, $meta_id ) — set meta (in memory)
delete_meta_data( $key ) — mark meta for deletion
save_meta_data() — persist meta changes (called automatically by save())
Data Stores
What Data Stores Do
Data stores abstract the persistence layer. The CRUD object calls generic methods (read, create, update, delete) on its data store, which handles the actual SQL.
Built-In Data Stores
| Object |
Data Store |
Storage |
| Product |
WC_Product_Data_Store_CPT |
wp_posts + wp_postmeta |
| Order (legacy) |
WC_Order_Data_Store_CPT |
wp_posts + wp_postmeta |
| Order (HPOS) |
Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore |
wp_wc_orders + related tables |
| Customer |
WC_Customer_Data_Store |
wp_users + wp_usermeta |
| Coupon |
WC_Coupon_Data_Store_CPT |
wp_posts + wp_postmeta |
Custom Data Stores
You can swap data stores via the woocommerce_{object}_data_store filter:
- Implement
WC_Object_Data_Store_Interface (or extend an existing data store)
- Implement methods:
create(), read(), update(), delete(), read_meta(), update_meta(), delete_meta()
- Filter:
add_filter( 'woocommerce_product_data_store', function() { return 'My_Custom_Store'; } )
HPOS (High-Performance Order Storage)
What Changed
HPOS moves order data from wp_posts/wp_postmeta to dedicated tables:
wp_wc_orders — core order fields (status, currency, total, customer_id, dates)
wp_wc_orders_meta — order meta
wp_wc_order_addresses — billing/shipping addresses
wp_wc_order_operational_data — internal operational data
Why HPOS
- Massive performance improvement for stores with many orders
- Orders no longer pollute the
wp_posts table
- Dedicated indexes for order queries
- Cleaner separation of concerns
Compatibility Modes
- Sync enabled — both posts and custom tables are written (transition period)
- Authoritative: Custom tables — custom tables are the source of truth
- Authoritative: Posts — legacy mode, posts are source of truth
Declaring HPOS Compatibility
add_action( 'before_woocommerce_init', function() {
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables', __FILE__, true
);
}
});
HPOS-Compatible Code
- DO: Use
$order->get_meta(), $order->update_meta_data(), $order->save()
- DO: Use
wc_get_orders() with proper args for querying
- DON'T: Use
get_post_meta(), update_post_meta() on orders
- DON'T: Use
WP_Query to query orders directly
- DON'T: Assume
$order->get_id() is a wp_posts ID
Querying
wc_get_orders()
The proper way to query orders (HPOS-compatible):
- Accepts args like
status, customer_id, date_created, meta_key, meta_value
- Returns array of
WC_Order objects
- Under the hood, delegates to the active order data store
wc_get_products()
Query products — delegates to WC_Product_Query:
type, status, sku, category, tag, price, meta_key, etc.
- Returns array of
WC_Product objects
Best Practices
- Always use CRUD getters/setters, never access
$data directly
- Always call
save() after modifying an object
- Use
'edit' context for admin forms, 'view' context for frontend display
- Use
wc_get_orders() / wc_get_products() for queries, not WP_Query
- Declare HPOS compatibility in every extension
- Test with HPOS authoritative mode enabled
Fetch the WooCommerce CRUD and HPOS documentation for exact method signatures, query parameters, and compatibility requirements before implementing.
1---2name: woo-data-stores3description: Work with WooCommerce CRUD data stores — WC_Product, WC_Order, WC_Customer, WC_Coupon data objects, custom data stores, HPOS migration, and getters/setters. Use when creating or modifying WooCommerce data objects or implementing custom data stores.4---5
6# WooCommerce CRUD & Data Stores
7
8## Before writing code
9
10**Fetch live docs**:
111. Web-search `site:developer.woocommerce.com crud data stores` for CRUD documentation
122. Web-search `site:github.com woocommerce woocommerce HPOS` for HPOS architecture
133. Fetch `https://github.com/woocommerce/woocommerce/wiki` for architecture decisions
14
15## CRUD Architecture
16
17### Data Objects
18
19WooCommerce's core entities extend `WC_Data`:
20- `WC_Product` — products (and subtypes: `WC_Product_Simple`, `WC_Product_Variable`, etc.)
21- `WC_Order` — orders (and `WC_Order_Refund`)
22- `WC_Customer` — customer profiles
23- `WC_Coupon` — discount coupons
24- `WC_Shipping_Zone` — shipping zones
25
26### WC_Data Base Class
27
28All CRUD objects inherit from `WC_Data`:
29- `$data` — array of core properties with defaults
30- `$meta_data` — array of `WC_Meta_Data` objects
31- `$id` — object ID
32- `$object_type` — string identifier (e.g., 'product', 'order')
33- `$data_store` — the `WC_Data_Store` instance handling persistence
34
35### Getters and Setters
36
37- `get_{prop}( $context = 'view' )` — retrieve a property
38 - `'view'` context — runs `woocommerce_{object_type}_get_{prop}` filter (for display)
39 - `'edit'` context — returns raw stored value (for forms/admin)
40- `set_{prop}( $value )` — set a property (in memory, not persisted until `save()`)
41- `save()` — persist to database (calls data store's `create()` or `update()`)
42- `delete( $force_delete )` — remove from database
43
44### Meta Data
45
46- `get_meta( $key, $single, $context )` — get meta value
47- `update_meta_data( $key, $value, $meta_id )` — set meta (in memory)
48- `delete_meta_data( $key )` — mark meta for deletion
49- `save_meta_data()` — persist meta changes (called automatically by `save()`)
50
51## Data Stores
52
53### What Data Stores Do
54
55Data stores abstract the persistence layer. The CRUD object calls generic methods (`read`, `create`, `update`, `delete`) on its data store, which handles the actual SQL.
56
57### Built-In Data Stores
58
59| Object | Data Store | Storage |
60|--------|-----------|---------|
61| Product | `WC_Product_Data_Store_CPT` | `wp_posts` + `wp_postmeta` |
62| Order (legacy) | `WC_Order_Data_Store_CPT` | `wp_posts` + `wp_postmeta` |
63| Order (HPOS) | `Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore` | `wp_wc_orders` + related tables |
64| Customer | `WC_Customer_Data_Store` | `wp_users` + `wp_usermeta` |
65| Coupon | `WC_Coupon_Data_Store_CPT` | `wp_posts` + `wp_postmeta` |
66
67### Custom Data Stores
68
69You can swap data stores via the `woocommerce_{object}_data_store` filter:
701. Implement `WC_Object_Data_Store_Interface` (or extend an existing data store)
712. Implement methods: `create()`, `read()`, `update()`, `delete()`, `read_meta()`, `update_meta()`, `delete_meta()`
723. Filter: `add_filter( 'woocommerce_product_data_store', function() { return 'My_Custom_Store'; } )`
73
74## HPOS (High-Performance Order Storage)
75
76### What Changed
77
78HPOS moves order data from `wp_posts`/`wp_postmeta` to dedicated tables:
79- **`wp_wc_orders`** — core order fields (status, currency, total, customer_id, dates)
80- **`wp_wc_orders_meta`** — order meta
81- **`wp_wc_order_addresses`** — billing/shipping addresses
82- **`wp_wc_order_operational_data`** — internal operational data
83
84### Why HPOS
85
86- Massive performance improvement for stores with many orders
87- Orders no longer pollute the `wp_posts` table
88- Dedicated indexes for order queries
89- Cleaner separation of concerns
90
91### Compatibility Modes
92
93- **Sync enabled** — both posts and custom tables are written (transition period)
94- **Authoritative: Custom tables** — custom tables are the source of truth
95- **Authoritative: Posts** — legacy mode, posts are source of truth
96
97### Declaring HPOS Compatibility
98
99```php
100add_action( 'before_woocommerce_init', function() {
101 if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
102 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
103 'custom_order_tables', __FILE__, true
104 );
105 }
106});
107```
108
109### HPOS-Compatible Code
110
111- **DO**: Use `$order->get_meta()`, `$order->update_meta_data()`, `$order->save()`
112- **DO**: Use `wc_get_orders()` with proper args for querying
113- **DON'T**: Use `get_post_meta()`, `update_post_meta()` on orders
114- **DON'T**: Use `WP_Query` to query orders directly
115- **DON'T**: Assume `$order->get_id()` is a `wp_posts` ID
116
117## Querying
118
119### wc_get_orders()
120
121The proper way to query orders (HPOS-compatible):
122- Accepts args like `status`, `customer_id`, `date_created`, `meta_key`, `meta_value`
123- Returns array of `WC_Order` objects
124- Under the hood, delegates to the active order data store
125
126### wc_get_products()
127
128Query products — delegates to `WC_Product_Query`:
129- `type`, `status`, `sku`, `category`, `tag`, `price`, `meta_key`, etc.
130- Returns array of `WC_Product` objects
131
132## Best Practices
133
134- Always use CRUD getters/setters, never access `$data` directly
135- Always call `save()` after modifying an object
136- Use `'edit'` context for admin forms, `'view'` context for frontend display
137- Use `wc_get_orders()` / `wc_get_products()` for queries, not `WP_Query`
138- Declare HPOS compatibility in every extension
139- Test with HPOS authoritative mode enabled
140
141Fetch the WooCommerce CRUD and HPOS documentation for exact method signatures, query parameters, and compatibility requirements before implementing.