A WooCommerce MySQL update can mark orders complete in under a minute, but it only changes the status column. The customer gets no Completed email, the order gets no note, and the completed date stays empty. Most stores are better served by the bulk action or WP-CLI.
I tested every query in this guide in October 2026 on a local WooCommerce 11.2 store with six test orders. It ran MySQL 8.0.35 with High-Performance Order Storage (HPOS) turned on. Every screenshot below is a real result from that store, including the query that touched zero rows on an HPOS store.
You get the right table for your storage type, a backup command, the exact UPDATE for HPOS and legacy stores, and what each query skips. You also get the two safer ways to mark an order as shipped, with the email your customer receives.
What Happens When You Use WooCommerce MySQL to Complete Orders
A WooCommerce MySQL update writes one value, the order status, straight into the database and skips all WooCommerce code. The orders list shows Completed right away, but no Completed email, order note, completed date or analytics change follows. The bulk action and WP-CLI run the full order API and do all four.
That gap is the whole story of this guide. WooCommerce does its order work in PHP when a status changes, and a database query never wakes that code up.
Here is what I saw on the same store for each method, side by side:

So raw SQL fits one job well: closing out thousands of old orders where you want silence. If customers are still waiting on a package, use the order API, because they expect that email.
A popular Stack Overflow answer on bulk changing order status says you need no other tables. That held in 2017, when every order lived in wp_posts, and it no longer holds on a store running HPOS.
Check Which Order Tables Your Store Uses
WooCommerce stores orders in one of two places, and the query you run depends on which one your store uses. Since WooCommerce 8.2, new stores use HPOS tables such as wp_wc_orders, while older stores may still keep orders in wp_posts. Check the setting before writing a single query.
The WooCommerce HPOS documentation says HPOS has been the default for new installs since version 8.2, released in October 2023. It moves orders into four tables: wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data and wp_wc_orders_meta.
Go to WooCommerce > Settings > Advanced > Features. The Order data storage field shows which one is active:

The compatibility mode box under it matters too. With it on, WooCommerce copies each order change to both storage types, but only when the change runs through WooCommerce code.
How to Mark Orders Complete in WooCommerce With MySQL
To mark orders complete in WooCommerce with MySQL, back up the order tables, connect to the database, and list the orders stuck in Processing. Then run one UPDATE on wp_wc_orders for HPOS stores and confirm the result in the orders list. The five steps below take about ten minutes.
If that connection fails with error 2003, read how to fix Can’t connect to MySQL server before you run the update.
Each step shows the real command and its output from my test store. You can compare your screen with mine.
Step 1: Back Up the Order Tables First
A status UPDATE has no undo button. Export the order tables before you run anything. WP-CLI does it in one command. The wp db export reference documents the --tables option I used to keep the file small.

On my store the file came to 70 KB. To roll back, run wp db import orders-backup.sql and the six tables return to how they were.
Warning: Run these queries on a staging copy first if your store takes live orders. An order placed between your SELECT and your UPDATE can get swept into the change.
Step 2: Connect to the Database
Open a MySQL session with the credentials from wp-config.php, or use the SQL tab in phpMyAdmin if your host offers it. Then confirm the server version and that the HPOS order tables exist:

Your table prefix may differ from wp_. Read $table_prefix in wp-config.php and swap it into every query below.
Step 3: Find the Orders Stuck in Processing
Check the storage setting from the database side and then list every order that is still Processing. Run a SELECT before any UPDATE. You then know exactly which rows will change:
SELECT id, status, billing_email FROM wp_wc_orders WHERE status = 'wc-processing';
My store returned six orders, IDs 35 to 40, and the option woocommerce_custom_orders_table_enabled read yes:

Status values in the database carry a wc- prefix. The admin shows “Completed”, but the column stores wc-completed, and a query without the prefix matches nothing.
Step 4: Run the UPDATE on wp_wc_orders
This UPDATE marks orders 35 and 36 as completed and stamps the modified date. The AND status condition protects any order that changed after your SELECT:
UPDATE wp_wc_orders SET status = 'wc-completed', date_updated_gmt = UTC_TIMESTAMP() WHERE id IN (35, 36) AND status = 'wc-processing';
MySQL reported 2 rows matched and 2 changed, and a follow-up SELECT showed both orders as wc-completed:

Drop the id IN (...) part to change every Processing order at once. Do that only after the SELECT above returned the list you expected.
Step 5: Confirm the Status in the Orders List
Open WooCommerce > Orders and reload the page. Orders 35 and 36 now show the Completed label. The admin reads the new value straight from the table:

The list looking right is the trap. Everything else WooCommerce normally does at this moment did not happen, which the next section proves.
What the Raw SQL Update Skips
A raw SQL status change skips every action WooCommerce attaches to the Completed status. On my store it sent zero emails, added no order note, left the completed date empty and kept the analytics row at Processing. The order looks finished, but the paper trail stops there.
I checked four places after the UPDATE on orders 35 and 36:

Here is what each one means for your store:
- Completed email: The mail folder of my test mail catcher held 0 messages. Neither customer heard anything.
- Order note: The last note on both orders still read “Order status changed from Pending payment to Processing.”
- Completed date:
date_completed_gmtinwp_wc_order_operational_datastayedNULL. Any report filtered by completed date misses these orders. A WooCommerce to QuickBooks sync that fires on status changes misses them too. - Analytics:
wp_wc_order_statsstill heldwc-processing. The Analytics screens kept counting both orders as Processing.
Plugins hooked to the Completed status miss the change too. A loyalty plugin that awards points on completion, like the ones in this roundup of the best WooCommerce points and rewards plugins, never hears about these orders.
Customers notice as well. The My Account orders page, which most stores show through the WooCommerce shortcodes for the account area, shows Completed with no email to explain it.
Why the wp_posts Query Fails on HPOS Stores
The classic query that updates wp_posts changes nothing on an HPOS store, because the real orders no longer live there. On my store it matched 0 rows, and on a legacy store it worked but left the HPOS tables out of sync. Pick the query that matches your storage setting.
Most older guides, including the Stack Overflow answer above, still give this query:
UPDATE wp_posts SET post_status = 'wc-completed' WHERE post_type = 'shop_order' AND post_status = 'wc-processing';
With HPOS on, MySQL reported 0 rows matched. The rows left in wp_posts are shop_order_placehold drafts that only reserve the order IDs:

So the query fails quietly. There is no error, and nothing on the orders screen changes, which can send you hunting for a cache problem that does not exist.
Then I switched the store back to WordPress posts storage, with compatibility mode off, and ran the same update on orders 38 and 39. This time it changed 2 rows:

The catch shows up later. A join of the two tables showed 38 and 39 as completed in wp_posts but still processing in wp_wc_orders, and wp wc hpos status reported 2 unsynced orders:

The HPOS CLI tools page explains why that number matters. WooCommerce refuses to switch storage types while orders are pending sync, until you run wp wc hpos sync.
Note: If you ever move that store to HPOS, run wp wc hpos sync first. Otherwise the sync can carry the old Processing status across and undo your SQL work.
How to Mark an Order as Shipped in WooCommerce
To mark an order as shipped in WooCommerce, set its status to Completed with the bulk action in the Orders list or with WP-CLI. Both run the full order API. The buyer gets the Completed email, the order gets a note and the date is saved. WooCommerce has no separate Shipped status by default.
The WooCommerce order statuses guide describes Processing as the cue to ship. It says you then change the status to Completed by hand, which is the step both methods below do for you.
Use the Bulk Action in the Orders List
The bulk action is the right tool for a few dozen orders. Tick the orders, pick “Change status to completed” from the Bulk actions menu, and click Apply:

The list shows 20 orders per page by default. Raise Number of items per page under Screen Options at the top right to handle more in one pass.
Use WP-CLI for Many Orders
WP-CLI changes one order per command through the same code the admin uses. It scales to thousands in a short shell loop. The WooCommerce CLI overview explains that wp wc commands need a user ID for permission checks:
wp wc shop_order update 37 --status=completed --user=1 --porcelain
On my store it printed the order ID, saved a completed date of 2026-10-09 10:52:35, and added two order notes. One logged the status change, and the other recorded the “Completed order” email it sent:

To complete every Processing order, feed the IDs from wp wc shop_order list --status=processing --field=id --user=1 into a for loop. The analytics row still read Processing right after, which is expected: WooCommerce Analytics imports order changes every 12 hours by default.
Check What the Customer Receives
The email that goes out is the standard Completed order email. On my store order 40 got one with the heading “Good things are heading your way!” and the line “We have finished processing your order.”:

That wording already reads as a shipping notice, which is why most stores never add a Shipped status. If you need tracking numbers or a true Shipped step, that is custom code, and a small plugin is the clean place for it. This guide on how to create a WooCommerce plugin covers the structure.
If your store needs custom statuses, emails or bulk jobs built to fit your order flow, our custom WooCommerce development services can build them. It saves you running hand-written SQL on live order data.
Common Mistakes With WooCommerce Order SQL
Most failed order updates come from a handful of repeat mistakes rather than from bad SQL syntax. Each one below either broke a query in my tests or came close to it. All five are cheap to avoid once you know to look for them.
Here is the list:
- Skipping the backup: A wrong WHERE clause can change every order in the store, and only an export brings them back.
- Leaving off the prefix: The column stores
wc-completed. A check forstatus = 'completed'matches 0 rows. - Using the wrong table: On HPOS the
wp_postsquery changes nothing, and on legacy storage thewp_wc_ordersquery changes the wrong copy. - Forgetting the guard: Keep
AND status = 'wc-processing'in the WHERE clause so a refunded or canceled order never flips to Completed. - Expecting the email: SQL sends nothing. If customers need a notice, use the bulk action or WP-CLI instead.
One more habit pays off. Run the same SELECT after the UPDATE and compare the count with the one you saw before.
Conclusion
A WooCommerce MySQL update is fast and quiet, and quiet is its problem. It changes the status column, and on my test store it skipped the email, the order note, the completed date and the analytics row.
Use raw SQL only for old orders you want closed without telling anyone, and only after a backup. Match the query to your storage setting, because the wp_posts version does nothing on HPOS stores.
For any order a customer is still waiting on, use the bulk action or wp wc shop_order update. Both mark the order complete the way WooCommerce expects, and both send the email that tells the buyer it shipped.
Frequently Asked Questions (FAQs)
Q1. Is it safe to run SQL queries directly on my WooCommerce database?
It is safe for the status column if you back up first and test on staging. The risk is what the query skips: emails, order notes, completed dates and plugin hooks never run.
Q2. Will marking orders complete with MySQL send the completion email?
No. On my WooCommerce 11.2 test store, two orders completed by SQL produced zero emails. The email is sent by PHP code that a database query never triggers.
Q3. How do I undo a MySQL order status change?
Restore the export you made before the change with wp db import orders-backup.sql. Without a backup, run a second UPDATE that sets the same order IDs back to wc-processing.
Q4. Does completing an order with SQL change stock levels?
No. The order statuses guide says stock is already reduced once an order reaches Processing. So moving a Processing order to Completed with SQL leaves stock levels exactly where they were.
Q5. What is the difference between wc-completed and completed?
The database stores order statuses with a wc- prefix. The column holds wc-completed. WP-CLI and the REST API accept the short name completed and add the prefix for you.
Q6. Can I mark orders complete automatically instead of by hand?
Yes. The order statuses guide says paid orders where every product is virtual and downloadable skip Processing and complete on their own. For anything else, schedule a WP-CLI loop with cron. Each order still gets its email and note.

2 responses
Hello Abhijit, great to see this blog. Quick question – is there a way to display all open orders, and select individual orders to mark as complete ? Reason i ask is because we are trying to mark orders which were cash payments (get cash and return change), we want to display the input order# and the based on the net amount collect cash and return change, then mark it as complete. I know this too much asking, but will be happy if you could guide. Thanks
Hi, Sap
Many thanks for such fantastic feedback! I am glad you found the blog useful.
Regarding the question, yes it is possible to incorporate showing all open (or processing) orders and marking individual ones as complete, especially cash payment orders. This can be done by a custom script or using WooCommerce hooks by listing all the open orders on a separate admin page, and then manually marking each according to your specific conditions.
Here’s a general approach:
1. Retrieve Open Orders: You can use the
WP_Queryfunction on WordPress to fetch all orders with a specific status (e.g., “processing“).2. Display Orders: On your custom admin page, you could list these orders and provide input fields to input the cash collected and calculate change.
3. Mark as Complete: Once the cash is collected and the change is calculated, you could use a custom action to update the order status to completed.
For marking orders as complete based on specific conditions, you can also use WooCommerce’s built-in functions like:
$order = wc_get_order($order_id); $order->update_status('completed');Alternatively, there are plugins available that can help automate part of this process, but a custom solution will give you more flexibility for handling cash payments.
Let me know if you’d like more detailed guidance or specific examples! Happy to help further!
Best,
Abhijit