How to Test the WooCommerce Order Confirmation Template Without Placing a Real Order
You have just branded the WooCommerce order-confirmation page — the “thank you, your order is received” screen — in your block theme. Now you want to look at it. And you hit the wall every store developer hits here: the confirmation page only exists for a real, completed order. There is no template preview in the Site Editor that fills it with live order data, no menu item that just opens it. To see your work you apparently have to add something to the cart, go through checkout, pay, and land on the page once — and do the whole dance again after every edit.
You don’t. The confirmation page is a normal URL, and you can build the order it needs in a single command. This post shows the fast way to render the order-received endpoint on demand, how to verify it came out right without eyeballing it, and the three quiet reasons a confirmation template looks broken when the markup is actually fine.
Just need the command? Jump to Create a test order in one command for the script that prints a ready-to-open URL.
Quick summary: The WooCommerce order-confirmation page lives at /checkout/order-received/<id>/?key=<order_key> and needs a real order plus its matching key. Instead of checking out by hand, run a short wp eval-file script that calls wc_create_order(), adds a product and addresses, and prints get_checkout_order_received_url() — the full URL, key included. Then verify with a headless curl that no blocks fell back to “invalid block.”
Why the confirmation page is hard to preview
Every other store template has an easy preview. Single product, shop, cart, checkout — open a product or the cart page and there it is. The order-confirmation template is different because it renders against a specific order, and it refuses to render against the wrong one. The page is served from the order-received endpoint, and the URL carries two things WooCommerce checks:
https://your-store.test/checkout/order-received/261/?key=wc_order_kfT8avB41rgGo
The number is the order ID. The key is the order’s unique key, stored on the order itself. WooCommerce compares the key in the URL against the order’s own key, and only renders the confirmation for a match (a logged-in customer viewing their own order, or an admin, can also pass). Guess the ID and omit the key and you get an “invalid order” screen, not your template. So the real task is not “find the URL” — it is “get an order ID together with its key.” That is the part people make harder than it needs to be.
If you already have orders: list them with WP-CLI
On a store that already has orders, you don’t need to create one — you need the key that goes with an ID. One caveat trips people up first: on any current WooCommerce install, High-Performance Order Storage (HPOS) is the default, and orders no longer live in wp_posts. So wp post list --post_type=shop_order returns nothing on a store that plainly has orders. Use the WooCommerce command instead, which reads whichever storage is active:
wp wc shop_order list --user=1 \
--fields=id,status,total,order_key --format=table
The --user=1 is required — the WooCommerce REST commands run as a user, and an admin ID satisfies the capability check. With an id and its order_key in hand, assemble the URL by hand: /checkout/order-received/<id>/?key=<order_key>. That’s the whole trick on a populated store. On a fresh store, or when you want an order shaped exactly for the section you’re testing, build one.
Create a test order in one command
You can create an order with wp wc shop_order create, but adding line items on the command line means passing JSON through the shell, and the quoting gets ugly fast — doubly so through a Lima or SSH wrapper. It is far cleaner to do it in PHP, where no order markup ever crosses a shell boundary. Save this as test-order.php somewhere WP-CLI can reach:
<?php
// test-order.php — run with: wp eval-file test-order.php
$order = wc_create_order();
// Add the first published product so the items table has a row.
$products = wc_get_products( array( 'limit' => 1, 'status' => 'publish' ) );
if ( $products ) {
$order->add_product( $products[0], 1 );
}
$address = array(
'first_name' => 'Jane',
'last_name' => 'Doe',
'address_1' => '123 Test Street',
'city' => 'Amsterdam',
'postcode' => '1011 AB',
'country' => 'NL',
'email' => 'jane@example.com',
);
$order->set_address( $address, 'billing' );
$order->set_address( $address, 'shipping' );
$order->calculate_totals();
$order->set_status( 'processing' );
$order->save();
echo $order->get_checkout_order_received_url() . "\n";
Then run it:
wp eval-file test-order.php
It prints the finished URL — get_checkout_order_received_url() assembles the endpoint with the correct key, so there is nothing to look up and nothing to concatenate. Open it in the browser and your template renders against a genuine order. Change a template file, reload the same URL, see the change. That is the loop you wanted.
Two details make the script worth keeping. Setting the status to processing (rather than leaving it pending) is what makes it read as a completed purchase. And doing everything through the WC_Order API — add_product(), calculate_totals(), save() — means totals, tax, and the items table are computed the same way a real checkout computes them, so what you preview is what a customer would see.
Verify it rendered — without opening a browser
Because the URL carries the key, it renders for an anonymous request too — which means curl can check it. This matters most right after you have hand-authored the confirmation template, because the order-confirmation blocks are picky about their names, and a mistyped or invented block name doesn’t error loudly. It renders as an “invalid block” placeholder that is easy to miss in a busy page. A two-line headless check catches it:
URL="https://your-store.test/checkout/order-received/261/?key=wc_order_kfT8avB41rgGo"
# Should print nothing — any output means a block failed to render.
curl -s "$URL" | grep -oiE "block-editor-warning|is not registered|wp-block-missing"
# Confirm the confirmation blocks actually rendered.
curl -s "$URL" | grep -o "wp-block-woocommerce-order-confirmation[a-z-]*" | sort -u
The first command is the important one: silence is success. The second is your positive proof — it lists the confirmation blocks that made it into the HTML, so you can see status, summary, totals, and the address blocks are present. For reference, the block set WooCommerce actually registers for this template is: order-confirmation-status, -summary, -totals-wrapper (wrapping -totals), -downloads-wrapper (wrapping -downloads), -shipping-wrapper and -billing-wrapper (each wrapping the matching -address block), -additional-fields-wrapper (wrapping -additional-fields), and -additional-information. If you hand-wrote the template from memory and invented tidy-sounding names like order-confirmation-products or -customer-addresses, they will not exist, and this check is how you find out before a customer does.
Why some sections are “missing” (and shouldn’t worry you)
Build the minimal test order above and you will notice the shipping address, the downloads section, and any extra checkout fields are nowhere on the page. That is not a bug in your template and not a bug in the order — those wrapper blocks self-hide when they have nothing to show. It is worth knowing which condition each one waits for, because otherwise you will “fix” a section that was never broken:
| Section | Renders only when… |
|---|---|
| Status, summary, totals | Always — every order has these. |
| Shipping address | The order ships to an address different from billing. Identical addresses collapse to one block. |
| Downloads | The order contains at least one downloadable product. |
| Additional fields | The store has custom checkout fields with values on the order. |
So to exercise the shipping column, give your test order a shipping address that differs from billing — change one line in the script before save():
$shipping = $address;
$shipping['address_1'] = '9 Different Road';
$shipping['city'] = 'Rotterdam';
$order->set_address( $shipping, 'shipping' );
Re-run the script for a second order and the two-column address layout appears. The same principle covers downloads (add a downloadable product) and custom fields (register one, or set the meta the field reads). Test each layout branch with an order shaped to trigger it, rather than expecting one order to show everything at once.
If your edits still don’t show up
One more trap deserves a flag, because it wastes afternoons. If you have opened the order-confirmation template (or the header and footer it pulls in) in the Site Editor and saved even once, WordPress stores a copy of that template in the database — and from then on the database copy is what renders, not your theme file. You edit order-confirmation.html, sync it, reload, and nothing changes, because the front end never reads the file anymore. This is the single most common reason a correct template edit appears to do nothing on a block theme. The fix is a short WP-CLI clear of the database override; we wrote it up in full in why your WordPress block pattern changes aren’t showing up. If your headless curl check shows the old markup after a confirmed file change, this is almost always why.
Clean up after yourself
Test orders are real orders — they show in reports, count toward sales figures, and can decrement stock. On a local or staging store that rarely matters, but leave the habit in place and delete them when you’re done, along with the throwaway script:
# Delete a specific test order (force = skip trash).
wp wc shop_order delete 261 --force=true --user=1
# Remove the helper script.
rm test-order.php
Never run test orders against a production store’s real order sequence if you can avoid it — build the confirmation page on local or staging, where a stray order is free to delete. That is where the one-command loop pays off most anyway: you can regenerate an order, reload, and tweak the template as many times as it takes, without a payment gateway or a real card in sight.
Frequently Asked Questions
- What is the URL of the WooCommerce order confirmation page? It is the
order-receivedendpoint on your checkout page:/checkout/order-received/<order-id>/?key=<order-key>. Both the order ID and its matching key are required — WooCommerce shows an “invalid order” message if the key is missing or wrong, unless you are logged in as the order’s customer or an admin. - Can I preview the order-confirmation template in the Site Editor? You can open and edit the template in the Site Editor, but it previews with placeholder data, not a live order — totals, addresses, and the items table only populate against a real order at the
order-receivedURL. To see it rendered for real, point the endpoint at an order as described above. - Why doesn’t
wp post list --post_type=shop_orderreturn my orders? Because current WooCommerce defaults to High-Performance Order Storage (HPOS), which keeps orders in dedicated tables rather thanwp_posts. Usewp wc shop_order list --user=1, which reads whichever storage is active. - How do I get a WooCommerce order’s key? List orders with
wp wc shop_order list --user=1 --fields=id,order_key, or, when creating one programmatically, call$order->get_checkout_order_received_url(), which returns the full confirmation URL with the key already appended. - Why is the shipping address section missing from my confirmation page? The shipping block self-hides when the order’s shipping address matches its billing address. Give the test order a distinct shipping address to render the two-column layout. The downloads and additional-fields sections hide the same way until an order actually has downloadable items or custom checkout fields.
Need a WooCommerce Developer for Your Store?
We build and optimize WooCommerce stores for SMEs — from custom checkout flows and payment integrations to performance tuning and ongoing maintenance. Fixed-price quotes available.
- Custom checkout and cart optimization
- Payment gateway integration (Stripe, Mollie, PayPal)
- WooCommerce performance and speed optimization
- Ongoing store maintenance and support