Order Items

Welcome to a complete tutorial about WooCommerce order items. Here we are going to talk about how to work with order items in code and then I have a very interesting real life example for you.

What are Order Items Exactly?

In order to understand what order items really are, let’s imagine a simple situation. In WooCommerce we have orders and we have products, right? When customer makes a purchase, an order is created, right? And somehow a purchased product is connected to this order, right? But what if tomorrow the actual price of the product is going to be changed? And the day after tomorrow it happens again?

That’s why WooCommerce has order items. They are not a post type and more than that, there are separate SQL tables in database for them – {prefix}woocommerce_order_items and {prefix}woocommerce_order_itemmeta.

If you open any edit order page, you would probably see something like this:

WooCommerce order items

What do you think are order items on the above screenshot?

  • line_item – products (and I think it is 100% clear here).
  • fee – yes, any additional fees added to an order are also order items.
  • shipping – information about shipping is also stored in database as order items.
  • coupon – didn’t expect that? But yes.

Get Order Items of a Specific Order

In the example below I will show you how to get WooCommerce order items by order ID. It is all about working with get_items() method of WC_Order object.

// some order ID we want to get order items of
$order_id = 63;
// get and order object
$order = wc_get_order( $order_id );

// let's decide what order item types we would like to get
$types = array( 'line_item', 'fee', 'shipping', 'coupon' ); 
// defaults to line_item which allows to get products from order

// iterating through each order item in the order
foreach( $order->get_items( $types ) as $item_id => $item ) {

	// line_item | fee | shipping | coupon
	$item_type = $item->get_type();

	// WC_Order object
	$item_order = $item->get_order();

	// order ID
	$item_order_id = $item->get_order_id();

	// order item name (product title, name of a shipping method or coupon code)
	$item_name = $item->get_name();

	// product only
	if( $item->is_type( 'line_item' ) ) {

		// product quantity
		$item_quantity = $item->get_quantity();

		// product subtotal without discounts
		$item_subtotal = $item->get_subtotal();

		// WC_Product object or WC_Product_Variation object
		$item_product = $item->get_product();

		// product ID
		$item_product_id = $item->get_product_id();

		// get order item variation ID
		$item_product_variation_id = $item->get_variation_id();

	}

	// products, fees and shipping
	if( $item->is_type( array( 'line_item', 'fee', 'shipping' ) ) ) {
		// order item total
		$item_total = $item->get_total();
	}

}

Add an Order Item to an Order

Good news is that you can add any order item to an order and it doesn’t matter what order status is. It works great for paid orders as well as for unpaid ones.

The only thing you should keep in mind is when you add any order item to an existing order you should never charge extra, it could even lead to some legal issues. So just be careful with that.

Add Product

// create an order item first
$order_item_id = wc_add_order_item(
	$order_id,
	array(
		'order_item_name' => 'Some product', // may differ from the product name
		'order_item_type' => 'line_item', // product
	)
);
if( $order_item_id ) {
	// provide its meta information
	wc_add_order_item_meta( $order_item_id, '_qty', 2, true ); // quantity
	wc_add_order_item_meta( $order_item_id, '_product_id', 15, true ); // ID of the product
	// you can also add "_variation_id" meta
	wc_add_order_item_meta( $order_item_id, '_line_subtotal', 10, true ); // price per item
	wc_add_order_item_meta( $order_item_id, '_line_total', 20, true ); // total price
}

It is not necessary to provide the actual price of the product as an order item. You can set 0 for example if you would like to add a product to an order as a gift.

Add an order item to an order WooCommerce

You see that order total is $0.00? It is because we have to recalculate order every time we add an order item there.

$order = wc_get_order( $order_id );
$order->calculate_totals();

Another way to do that without using wc_add_order_item() and wc_add_order_item_meta() functions.

$item = new WC_Order_Item_Product();
$item->set_name( 'Some product' );
$item->set_quantity( 2 );
$item->set_product_id( 15 );
$item->set_subtotal( 10 );
$item->set_total( 10 );

$order = wc_get_order( $order_id );
$order->add_item( $item );
$order->calculate_totals();

Add Fee

$order_item_id = wc_add_order_item( 
	$order_id, 
	array(
 		'order_item_name' => 'Some fee',
 		'order_item_type' => 'fee',
	)
);
if( $order_item_id ) {
	wc_add_order_item_meta( $order_item_id, '_fee_amount', 20, true );
	wc_add_order_item_meta( $order_item_id, '_line_total', 20, true );
}

Another way without wc_add_order_item() and wc_add_order_item_meta() functions.

$item = new WC_Order_Item_Fee();
$item->set_name( 'Some fee' );
$item->set_amount( 20 );
$item->set_total( 20 );

$order = wc_get_order( $order_id );
$order->add_item( $item );
$order->calculate_totals();

Add Shipping

$item = new WC_Order_Item_Shipping();
$item->set_method_title( 'Free shipping' );
$item->set_method_id( 'free_shipping:1' ); // set an existing Shipping method ID
$item->set_total( 0 ); // optional

$order = wc_get_order( $order_id );
$order->add_item( $item );
$order->calculate_totals();

Add Coupon to an Order Programmatically

Easier than expected.

$order = wc_get_order( $order_id );
$order->apply_coupon( 'blackfriday13' );

Well, you can also add coupons to an order using WC_Order_Item_Coupon class and add_item() method of WC_Order class if you want, here I described how.

Updating Order Items

Every order item added to an order can be updated. You can update for example its name or subtotal. But in case of updating subtotals do not forget to recalculate the order.

Updating order item name:

wc_update_order_item( $order_item_id, array( 'order_item_name' => 'New product' ) );

Changing the quantity of the product order item:

wc_update_order_item_meta( $order_item_id, '_qty', 3 );
wc_update_order_item_meta( $order_item_id, '_line_total', 30 );

$order = wc_get_order( $order_id );
$order->calculate_totals();

Removing Order Items

The cool thing is that when you remove an order item with wc_delete_order_item( $order_item_id ) you shouldn’t care about deleting all its metadata, because it will be removed automatically.

But if you just want to delete some meta, wc_delete_order_item_meta( $item_id, $key, $value, $delete_all ). The required parameters are only $item_id and $key.

Example. How to Add Extra Product Fields

In this example I will show you how to add extra form fields to your WooCommerce product pages and after that use the values of that fields on every stage of the purchase.

1. Add Extra Form Fields to Product Pages

As an example let’s add “Name on T-Shirt” field.

I think it is fully clear that we can not use that product custom meta as a product attribute. If you would like to allow the customer to choose the color of T-Shirts or the size of a pizza, please consider using product attributes of course.

allows you to add extra form fields to your WooCommerce product pages.

add_action( 'woocommerce_before_add_to_cart_button', 'misha_before_add_to_cart_field' );

function misha_before_add_to_cart_field() {
	global $product; // awesome - we have a global product object here

	// let's add the input field only for products in a specific category with "t-shirts" slug
	if ( ! has_term( 'tshirts', 'product_cat', $product->get_id() ) ) {
		return;
	}

	echo '<div class="misha-before-add-to-cart-field">
		<label for="name-on-t-shirt">Name on T-Shirt: </label>
		<input type="text" id="name-on-t-shirt" name="name-on-t-shirt" placeholder="Enter a name" maxlength="15">
	</div>';

}
add a custom form field on the product page

2. Product Form Field Validation

In case of adding a name on T-Shirt field validation is not necessary obviously. But sometimes you may need it. So, let’s look how to do it.

add_action( 'woocommerce_add_to_cart_validation', 'rudr_field_validation', 10, 3 );
function rudr_field_validation( $result, $product_id, $quantity ) {

	if( has_term( 'tshirts', 'product_cat', $product_id ) && empty( $_REQUEST[ 'name-on-t-shirt' ] ) ) {
		wc_add_notice( 'We need your name please!', 'error' );
		return false;
	}
	
	return $result;
}
Product field validation

Once your product custom field is required, you should also consider replacing “Add to cart” with “Read more” buttons on catalog and product archive pages.

add_filter( 'woocommerce_loop_add_to_cart_link', 'rudr_add_to_cart_url', 25, 3 );

function rudr_add_to_cart_url( $html, $product, $args ) {

	if( has_term( 'tshirts', 'product_cat', $product->get_id() ) ) {
		return sprintf(
			'<a href="%s" class="button">Read more</a>',
			esc_url( $product->get_permalink() )
		);
	}
	return $html;

}

Now we have that:

product read more buttons instead of add to cart on catalog pages
Polo is in T-Shirts category, so it doesn’t have “Add to cart” button on the Shop page.

3. Display “Name on T-shirt” in Cart

// add field value to cart item data
add_filter( 'woocommerce_add_cart_item_data', 'misha_save_field_value_to_cart_data', 10, 3 );

function misha_save_field_value_to_cart_data( $cart_item_data, $product_id, $variation_id ) {

	if( ! empty( $_POST[ 'name-on-t-shirt' ] ) ) { // here could be another validation if you need
		$cart_item_data[ 'name-on-t-shirt' ] = sanitize_text_field( $_POST[ 'name-on-t-shirt' ] );
	}

	return $cart_item_data;

}

// display "Name on T-shirt" in Cart
add_filter( 'woocommerce_get_item_data', 'misha_display_field', 10, 2 );

function misha_display_field( $item_data, $cart_item ) {

	if( ! empty( $cart_item[ 'name-on-t-shirt' ] ) ) {
		$item_data[] = array(
			'key'     => 'Name on T-shirt',
			'value'   => $cart_item[ 'name-on-t-shirt' ],
			'display' => '', // in case you would like to display "value" in another way (for users)
		);
	}

	return $item_data;

}

4. Add Order Item Meta Finally

Action hook woocommerce_checkout_create_order_line_item fires for every cart item and allows to process any cart item data manually. As easy as that – we are going to use add_meta_data() method to store cart item data as order item meta data.

add_action( 'woocommerce_checkout_create_order_line_item', 'misha_add_order_item_meta', 10, 4 );

function misha_add_order_item_meta( $item, $cart_item_key, $values, $order ) {

	if ( ! empty( $values[ 'name-on-t-shirt' ] ) ) {
		$item->add_meta_data( 'Name on a T-Shirt', $values[ 'name-on-t-shirt' ] );
	}

}

Once an order item has some meta data, it will be automatically added to the thank you page, to edit order page in WordPress admin and to emails as well.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 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

Follow me on X