Create Orders Programmatically
In this tutorial, we will dive deep into creating an order in your WooCommerce store programmatically. I already have a similar tutorial on my blog where we were creating a product programmatically.
First things first, you should know a very simple thing – yes, previously, orders were a WordPress custom post type, but in the latest version of WooCommerce HPOS appeared, and now orders are a standalone primitive. It also means that you can forget about using the wp_insert_post() function to create them. We have to tap into a CRUD layer for that. If it says nothing to you, please be patient, we’re going to dive into it in just a little bit.
Everything I share with you here is exactly what I learned in my own experience while working with my Multisite Order Sync for WooCommerce plugin.
Long story short an order can be created and saved into the database with as far as one simple line of code.
$order = wc_create_order();
/* here we can add products, shipping, change status etc */Do not believe me? Well, just try these lines by yourself and here we are.

Oh, and yes, do not use this code too early! Because if WooCommerce is not initialized yet, nothing will happen.
And you can also pass some default arguments into wc_create_order() function, for example:
$args = array(
'created_via' => 'admin', // default values are "admin", "checkout", "store-api"
'order_id' => 0, // you can update an existing order if you pass its ID here
'customer_id' => 1,
);
$order = wc_create_order( $args );Or you can do kind of similar thing with the methods:
$order = wc_create_order();
$order->set_created_via( 'admin' ); // you can also use custom values here
$order->set_customer_id( 1 );
$order->save();But you know why using the last piece of code is not a good idea? It is because right now we need to run $order->save() anyway, which means that our order is going to be saved to the database twice! What is the point of that here? So I prefer to do it this way:
$order = new WC_Order();
$order->set_created_via( 'admin' );
$order->set_customer_id( 1 );
$order->save();Add Order Items
In this chapter, we are going to talk about how to add any kind of order items to our newly created order.
Order items are products, fees, shipping, coupons, and taxes in case you didn’t know. And also you can check my other tutorial specifically about order items.
Add Products to an Order
Well, we have figured out how to create an empty order, but what about adding some products to it? The add_product() method should be super-helpful here for all kinds of products, it works for product variations as well. But it is also possible to use the add_item() method, that is a little bit more complicated, you can find an example here. More than that, if you have to add custom prices to your product-type order items you have to use add_item().
In the example below, we are going to add a simple product with ID = 136 (and quantity 2) and a variable product with product variation ID = 70.
$order = wc_create_order();
$order->add_product( wc_get_product( 136 ), 2 );
$order->add_product( wc_get_product( 70 ) );
$order->calculate_totals();We have to use the calculate_totals() method as well because without that order total is going to be zero.

Add Fee
For fees, shipping, taxes, and coupons we can use the add_item() method of WC_Order class. The only thing is that you need to create an order item object before adding it to an order using appropriate classes like WC_Order_Item_Fee, WC_Order_Item_Shipping, WC_Order_Item_Tax, and .WC_Order_Item_Coupon
// create Fee object
$fee = new WC_Order_Item_Fee();
$fee->set_name( 'Some fee' );
$fee->set_amount( 20 );
$fee->set_total( 20 );
// add to order
$order = wc_create_order();
$order->add_item( $fee );
$order->calculate_totals();More examples of adding fees to an order can be found here.
Add Shipping
In case you would like to add a shipping method to an order programmatically, please use WC_Order_Item_Shipping class and you will also need an existing shipping method ID. How to find it I mentioned in this tutorial.
// create shipping object
$shipping = new WC_Order_Item_Shipping();
$shipping->set_method_title( 'Free shipping' );
$shipping->set_method_id( 'free_shipping:1' ); // set an existing Shipping method ID
$shipping->set_total( 0 ); // optional
// add to order
$order = wc_create_order();
$order->add_item( $shipping );
$order->calculate_totals();Let’s check out this order in WordPress admin now.

Add Custom Taxes
Right now, let’s also try to add a custom tax order item to our order. However, this whole thing with taxes in orders is kind of complicated. We’re going to dive into it later in this tutorial.
Keep in mind that WC_Order_Item_Tax items won’t be added unless you have taxes enabled in WooCommerce > Settings > General.
$tax = new WC_Order_Item_Tax();
$tax->set_label( 'Custom Tax' );
$tax->set_rate_percent( 15 ); // total tax percentage
// $tax_item->set_tax_total( 2 ); // total tax amount
// $tax_item->set_shipping_tax_total( 0 ); // total tax amount for shipping
$order->add_item( $tax );
$order->calculate_totals( false );Add Coupons
Super-easy!
$order = wc_create_order();
$order->apply_coupon( 'blackfriday13' )There is also a difficult way how you can add coupons to an order using WC_Order_Item_Coupon class and add_item() method of WC_Order class, I described it here.
Add Billing and Shipping Addresses to an Order
In order to add either a billing or shipping address we are going to use the same method set_address(), the only difference is in its second parameter.
$order->set_address( $address, 'billing' )– billing address,$order->set_address( $address, 'shipping' )– shipping address.
By the way the WC_Order class has more specific methods, like set_billing_first_name(), set_shipping_address_1() etc, but I think there is no point in using them here.
Ok, so let’s do it right now.
$address = array(
'first_name' => 'Misha',
'last_name' => 'Rudrastyh',
'company' => 'rudrastyh.com',
'email' => 'no-reply@rudrastyh.com',
'phone' => '+995-123-4567',
'address_1' => '29 Kote Marjanishvili St',
'address_2' => '',
'city' => 'Tbilisi',
'state' => '',
'postcode' => '0108',
'country' => 'GE'
);
$order = wc_create_order();
$order->set_address( $address, 'billing' );
$order->set_address( $address, 'shipping' );I hope everything should be crystal clear from the code snippet above. In case you’re not sure what is your country code in ISO-3166-1 alpha-2, you can check it here.

Add Customer Information to an Order
By a customer in WooCommerce I mean an existing WordPress user on the website. By default, orders are created for a Guest user (you can check it on a screenshot above by the way). So, if you would like to assign an existing WordPress user, you have to use the set_customer_id() method. There are also set_customer_ip_address() and set_customer_user_agent() methods that could be helpful.
As simple as that:
$order = wc_create_order();
$order->set_customer_id( 1 );
$order->set_customer_ip_address( '10.1.1.1' );Add Payment Method
Here we are going to use two methods and they are set_payment_method() and set_payment_method_title(). In the first one you need to pass a payment method ID (the best way how to find it is described here), in the second one please pass any title you are going to use for this payment method for this particular order. It is also possible to set a transaction ID with the set_transaction_id() method.
$order = wc_create_order();
$order->set_payment_method( 'stripe' );
$order->set_payment_method_title( 'Credit/Debit card' );In WooCommerce < 3.0 you were supposed to pass WC_Payment_Gateway object inside the set_payment_method() method like this:
// WooCommerce < 3.0
$payment_gateways = WC()->payment_gateways->payment_gateways();
$order->set_payment_method( $payment_gateways[ 'stripe' ] );So you may find this obsolete implementation over the internet as well.
Assign an Order Status
There are two WC_Order methods available that allow assigning an order status – set_status() and update_status(). The difference is that the first one just makes changes in an order object, but doesn’t update the information in the database, and the second one does make changes and updates the status in the database as well. So if you are going to use set_status(), you are also supposed to use save() method.
$order = wc_create_order();
$order->set_status( 'wc-completed' );
// $order->set_status( 'wc-completed', 'You can pass some order notes here...' );
$order->save();The list of order statuses:
wc-pending– Pending payment (Default),wc-processing– Processing,wc-on-hold– On hold,wc-misha-shipping– Awaiting shipping,wc-completed– Completed,wc-cancelled– Cancelled,wc-refunded– Refunded,wc-failed– Failed,- or any custom order status.
And yes, you can use an order status name without wc- prefix.
Add Order Metadata
Three methods are available for us when we need to add some metadata to an order:
| Method | When to use |
|---|---|
add_meta_data() | When we create an order there could be multiple values for the same meta key. |
update_meta_data() | When we create or update an order, and for every meta key there is always a single value. |
set_meta_data() | Allows to set values for multiple meta keys at the same time (as an array). Keep in mind, that you will need to provide a meta ID as well. |
Example when we have different values for the same meta key:
$order->add_meta_data( 'my_custom_key', 'value-1' );
$order->add_meta_data( 'my_custom_key', 'value-2' );
$order->save();In case we always have a single value for a meta key:
$order->update_meta_data( 'my_custom_key', 'value-1' );
// or absolutely the same because we've provided the third parameter set to true
// $order->add_meta_data( 'my_custom_key', 'value-2', true );
$order->save();Add Taxes
That’s going to be an interesting one because there are multiple moments that we need to keep in mind when working with taxes in orders created programmatically.
First of all, taxes will never be added to your programmatically created orders no matter what until you enable them in WooCommerce > Settings > General:

Second, taxes are forced to be calculated automatically based on the settings set up in the “Standard rates”, “Reduced rate rates”, and “Zero rate rates” tabs. It means the simple thing, if you apply the $order->calculate_taxes() method to your order, some of your custom taxes (except ones added as WC_Order_Item_Tax objects) are going to be overridden. The thing is that tax calculation is also happening by default when you run $order->calculate_totals(), but can be turned off if changed to $order->calculate_totals( false ). However, taxes, added to order items directly can be reflected in the order total with the following code:
$order->update_taxes();
$order->calculate_totals( false );But you can also save yourself some trouble by setting order totals directly using the $order->set_total() method instead of the $order->calculate_totals().
Complete code
Just for your convenience.
$order = new WC_Order();
// $order = wc_create_order();
// add products
$order->add_product( wc_get_product( 136 ), 2 );
$order->add_product( wc_get_product( 70 ) );
// add shipping
$shipping = new WC_Order_Item_Shipping();
$shipping->set_method_title( 'Free shipping' );
$shipping->set_method_id( 'free_shipping:1' ); // set an existing Shipping method ID
$shipping->set_total( 0 ); // optional
$order->add_item( $shipping );
// add billing and shipping addresses
$address = array(
'first_name' => 'Misha',
'last_name' => 'Rudrastyh',
'company' => 'rudrastyh.com',
'email' => 'no-reply@rudrastyh.com',
'phone' => '+995-123-4567',
'address_1' => '29 Kote Marjanishvili St',
'address_2' => '',
'city' => 'Tbilisi',
'state' => '',
'postcode' => '0108',
'country' => 'GE'
);
$order->set_address( $address, 'billing' );
$order->set_address( $address, 'shipping' );
// add payment method
$order->set_payment_method( 'stripe' );
$order->set_payment_method_title( 'Credit/Debit card' );
// order status
$order->set_status( 'wc-completed', 'Order is created programmatically' );
// add two meta values of the same meta key
$order->add_meta_data( 'my_custom_key', 'value-1' );
$order->add_meta_data( 'my_custom_key', 'value-2' );
// calculate and save
$order->calculate_totals();
$order->save();
Misha Rudrastyh
Hey guys and welcome to my website. For more than 15 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
Thanks a lot for this one, Misha! Needed to programmatically create new orders while testing WooCommerce webhooks for a project, and this is a complete tutorial on how to create orders programmatically.
Thank you from the whole heart. You are my best fiend man.
I really like your tutorials. You helped me a lot setting up a custom payment gateway! :D Do you know a hook I can use when creating orders with php?
woocommerce_new_orderis nice but I can’t get the shipping data via get_items().If I use
woocommerce_checkout_order_processedit works, but only gets triggered if an order if created on the frontend.@Jay
some actions aren’t taken if the order isn’t saved
$order->save()perhaps your action needs to run after?