
Use CMB2 to add a custom metabox to add tracking information to WooCommerce orders. The information is then added to the "Completed Order" email.
Update 15 July 2020: Based on Steve’s request I updated the code to display the tracking information in the “My Account” area.
Update 10 May 2021: I got an email reporting that the code does not work when the Email Template Customizer for WooCommerce is being used. This plugin does not use the hook I use so I have added code to accommodate this plugin. More info in the post.
Update 7 March 2022: WooCommerce sends the email before CMB2 saves the tracking data. I did a lot of deep debugging and found the hook involved. I have updated the code so that CMB2 saves the data before the emails are sent. Now when shop managers change the status, enter tracking info and Update the order in one go, the tracking info will be in the email.
Update 4 May 2022: Aaron found an “unintended consequence” of my change so I have updated the code further to only call WC_Meta_Box_Order_Data::save()
for shop orders.
Update 21 August 2022: In version 0.8 I have added a check to verify that the CMB2 plugin is active. This check runs when editing an order as that is the only page that the code runs on.
Update 17 October 2022: WooCommerce 6.9.4 changed how WC_Meta_Box_Order_Data::save()
is called and my code breaks. In version 0.9 I have commented out the changes of March and May 2022 and just use WooCommerce’s built in feature to defer the email by 10 seconds. I use and extend that functionality in my ‘Defer WooCommerce emails for a few minutes‘ post.
Update 13 September 2023: Enabled HPOS support by changing get_post_meta() calls to $order->get_meta() versions.
CMB2 does not yet support HPOS but I reported it and they’ve developed a fix that should be in the next release.
Elizabeth commented on my Add info to WooCommerce order emails post asking if I knew a way to put tracking information into the order complete email. As shown in the original post, adding content to emails is easy – as long as you have the data to add.
I quickly put together a plugin that uses CMB2 to add a metabox where the tracking number and tracking url can be added to an order.
The metabox
The CMB2 fields are very simple – a ‘text‘ field for the tracking number (which will likely be alphanumeric) and ‘text_url‘ field for the tracking url.
The ‘object_type‘ (aka post type) for the WooCommerce Order page is ‘shop_order.’

CMB2 is optional here as you can manually add ‘tracking_number‘ and ‘tracking_url‘ custom fields to the order. In fact, when you add them via the metabox fields you will see them listed in the Custom Fields section! CMB2 primarily makes for a nice interface.

Order email
The email code looks longer than you might think – that’s because of error checking (that the order has the tracking number and url) and slightly different text for plain text and html emails.
You can see that it only runs for the ‘customer_completed_order‘ email.
The shipping provider is not stored in the order’s post meta but is determined by examining the tracking url. This is probably quite reliable.
That’s it.

The code
Custom REST API endpoint
I wrote additional code for this small project but have purposely omitted it from this post because it was a paid project and it would be unfair to make that code available for free. The code was a custom REST API endpoint that allowed a third party service push a tracking number and url to the website. The endpoint stored the number and url in the post meta and the code above uses that in the order email.
Supporting ‘Email Template Customizer for WooCommerce’ plugin
I got an email reporting that my code did not work when the Email Template Customizer for WooCommerce plugin was in use. I found that that plugin does not call the ‘woocommerce_email_order_details‘ hook. My solution is to use a hook that the plugin does support – ‘woocommerce_email_after_order_table‘. This solution requires the website owner to add a ‘WC Hook’ field to the ‘Completed Order’ email and to choose the ‘woocommerce_email_after_order_table‘ action.
Here are screenshots of the changes to make to the email. Click to see the full size image.
Hi,
Since we are using code for the most part to make this functional, can you suggest a code snippet to create the custom metabox instead of installing CMB2 plugin?
Thanks
@KoolPal: I use CMB2 because it is easy and it takes care of validation and security issues. You could try creating your own metabox after reading these two posts: https://jeremyhixon.com/tool/wordpress-meta-box-generator-v2-beta/ https://premium.wpmudev.org/blog/creating-meta-boxes/
Can the tracking data also be added to the my-account orders listing? Just in case the customer deletes the email with the tracking data, it would help to add it into their account data.
Thanks!
@Steve – Great idea. I have updated the code to add the tracking info to the “My Account” area.
This is exactly what I need- but I’m not sure how to actually integrate this code into my website. I have the CMB2 Plugin installed, but I don’t know what to do or where to put the code snippet that you’ve posted. Could you describe what I’m supposed to do to get from the base CMB2 plugin to the functionalities shown in your screenshots here?
Thank you SO much :)
@Uzair: See my video of how to use my code snippets. The code in this post is a plugin!
Hey Damien!
Thanks so much for this! I was wondering if you could help.
I’ve installed the plugin but when I click to activate I get a ‘Fatal Error’ : Parse error: syntax error, unexpected ‘\’ (T_NS_SEPARATOR), expecting identifier (T_STRING) in /home3/natasch5/public_html/wp-content/plugins/shipmenttracking.php_/shipmenttracking.php on line 20
Any idea why this could be happening? Thanks!
@Natascha: What is the wp-content/plugins/shipmenttracking.php_/shipmenttracking.php plugin?
Is that my code?
If it is not, please email me the shipmenttracking.php file.
Hey Damien,
your Plugin wirken great for me until recently. It somehow got deleted without me noticing I guess. The metaboxes for entering the tracking Info is still there. However the Information does not end up in the e-mail That gets sent to the customer.
I tried to reinstall your code. I copied the snippet from above. Created a new folder in my plugins directory on my sftp server. I pasted your code into a new file called Index.php.
After trying to activate the plugin i get a notice saying it would cause a fatal error.
Any idea what I am doing wrong?
I appreciate the help. Thank you!
@Moritz – What is the error message?I downloaded the code and activated it on WordPress 5.5.3 and I did not get an error. I did two tests: 1) create /wp-content/plugins/tracking-info-to-wc-order.php 2) create /wp-content/plugins/tracking-info-to-wc-order/index.php In both cases the plugin activated without a fatal error.
Do you have CMB2 installed and active? (my plugin will activate without it but it needs it to get the field for the tracking info).
Hello, I can’t manage to install the plugin, when I save it and then want to import it, it doesn’t work, the format is not the right own, I can’t select .php. Any idea what I am doing wrong. Thanks
I emailed Marlene. WordPress only allows uploading zip files for plugins, not a PHP file. She used FTP and got it uploaded and activated it.
Hello Damien,
You can forget my previous comment I solve it an installed everything and have the tracking info metaboxes.
Thank, Marlene
Hi Damien,
Thank you for this plugin! Awesome, this worked perfectly.
Just a few questions… – How to add a logo for shipping company on right side of Tracking URL – text – How to add an “check URL-button” under Tracking URL-box
Anyways … great job making this litte plugin.
Cheers from Norway! Thomas K
Hi Damien,
Thanky you for this code! It works fine, but I have one problem:
I would normally open the order, change the status to completed, add tracking info and save the order. But in this way, the tracking information is not sent with the e-mail to the customer, because I guess the e-mail is sent before the post data is saved.
Now I add the tracking information, save the order, then change the status to completed and save the order again. Now the email contains tracking information as this was saved the first time.
Do you know how I can solve this problem? In other words: how to save the tracking data to the order before the automatic e-mail is sent out.
Thank you!
@Berry – This situation sounds perfect for my Defer WooCommerce emails for a few minutes code. Out of the box it delays sending the Completed Order email for 10 minutes. Try it out and let me know if it works.
Hi,
I use the e-mail template customizer. When I put in the variable via the hook it prints how the order was paid, not the shipping info. Do you know what I am doing wrong?
Best regards
I have the same problem… did you manage to sole it yet?
@Martin – Are you using the ‘woocommerce_email_after_order_table’ hook in the Email Customizer?
Do you have the version of the code that has the line? add_action( ‘woocommerce_email_after_order_table’, ‘dcwd_add_tracking_info_to_order_completed_email’, 5, 4 );
I installed your plugin but i don’t receive email when i click completed order. Please help
@Sonam: If you deactivate my plugin do you get the email?
Are there any error or warning messages in your web server error_log file?
You can also check whether WordPress sends the email (it might get blocked somewhere else). There are plugins for this but I wrote a small one that records when WordPress sends an email.
Please try this and then email me your results.
I receive email but not the tracking number and shipping provider as mentioned in your plugin.
This is very helpful. Thank you
Thank you for sharing this, this is very helfpul. I am running into an issue that when i upload the .php file under the plugins directory and activate the plugin. The whole text of the php file is being displayed on the websites header . is there something that i am missing Thanks
@Lohit: If you are seeing the text of the PHP file then it is the web server that is not reading the file as a PHP file. I suggest asking the Support team for that web server as they will be able to find the solution very quickly.
Hi im trying how to figure out your plugin to work. well i installed cmb2 plugin and uploaded your code to php file and uploaded it to wpcontent plugins. and nothing is showed in installed plugins to activate plugin.
@ronol – It sounds like you did everything right. Does the tracking-info-to-wc-order.php filename have a .txt extension? (I know that Windows might add that when you save the file from Gist).
Hi. Thanks for the code. I uploaded it and it shows on the my account page and in the order page properly. The only issue, which is a biggy, is that the update button does not work now so i can’t save the information. I have to go the the order list and choose bulk option of change to completed to finish an order. Im using oceanwp on a centos 8 cloud server if that helps. Any help would be great.
@brad – I tested my code (version 0.5) on a site with WooCommerce 5.8.0 and Twenty Fifteen theme (it was active on my test site). I was able to add tracking info to a Processing order.
I think that the browser Console window will have some error messages – maybe another plugin is conflicting with CMB2.
You can’t imagine how useful have been this to me, you save my life! Thanks a tone, really.
But I have only one problem: if the customer buys 2 productos (one físcal so is trackable, and a virutal and downloadable one, non trakable) eventhoug I enter the tracking info it doesn’t display on the e-mail. Is there a way to solve this?
Thanks!
@Mar – I cannot reproduce this error. I experimented with 3 order situations – physical product only, downloadable + physical products and virtual + physical products. In all 3 emails the tracking information was in the email.
I will need more information from you about your website configuration. I emailed you but it bounced. I left a message on your Facebook page but you did not reply.
I can’t believe how amazing you are! THANKS A TONE for trying to contact me and for testing on your on web!! I’ve answered you via Facebook. If it works for you, I’ll try again… maybe I have something not well configured. We keep in touch! and again, thanks
Hi Damien! This code is really helpful, thanks.
Everything works fine but the tracking code information is not added to the completed order email. I am not using the Email Template Customizer for WooCommerce.
WordPress 5.8.2 / WooCommerce 6.0.0.
Can you help me please?
I guess I have found the bug. The problem happens only when the tracking number and URL are saved at the same time as the order status is changed to completed (on update). If the tracking number and URL are updated FIRST and the order status is updated AFTER, the tracking information is correctly added to the email. Is this an CMB2 thing? Is it possible to force the custom fields update before sending the email to prevent this from happening? Thanks.
@Miguel – WooCommerce sends the email before CMB2 saves the tracking data. I did a lot of deep debugging and found the hook involved. I have updated the code so that CMB2 saves the data before the emails are sent.
Amazing job, Damien! Thank you for your contribution and dedication.
Hello Damien, You arre a real philanthopist for making this and your other plugins available to all. It’s much appreciated. After reading the instructions properly I was able to get it installed and it is working a real treat – alongside the deferred email sending. Thank you
@James – Thanks. I’m delighted that it works for you. It was an interesting challenge to develop.
Hi Damien! This code is really helpful, thanks.
Is there a possibility to add a hardcoded tracking URL so we don’t have to enter it manually every time?
@Alex: The simplest solution could be to add a default value to the tracking url field. Then it would get picked up by the other functions.
After line 32 (with ‘protocols’) add:
'default' => 'https://the_tracking_url/path/to/tracking',
Hi Damien,
Been looking for code like this for a long time. Thank you so much! Just a quick question, how do I manage to get a “Track Order” button next to the View button on the My Account > Orders page, where all the orders are…
Like here: https://imgur.com/X3xeAwj I want it to basically look like this, and be linked to the Tracking URL set in the Order Page: https://imgur.com/xqZf9b5
Hope you can help
@Brendon – I wrote this code back in March after your comment but forgot to post a link here: https://pastebin.com/kzRp6LbQ
<?php /* Plugin Name: Tracking button to My Orders Plugin URI: https://www.damiencarbery.com/ Description: Add a Track button to the My Orders page. Use the CMB2 tracking info from https://www.damiencarbery.com/2020/01/add-tracking-info-to-woocommerce-order/. Author: Damien Carbery Author URI: https://www.damiencarbery.com Version: 0.1 */
// Add Track button only when the tracking plugin is active. add_action( 'plugins_loaded', 'dcwd_check_for_tracking_plugin' ); function dcwd_check_for_tracking_plugin() { if ( function_exists( 'dcwd_add_tracking_info_to_order_completed_email' ) ) { add_filter( 'woocommerce_my_account_my_orders_actions', 'dcwd_add_track_button_to_my_orders_actions', 20, 2 ); } }
// Verify that the order has a tracking url before displaying the Track button. function dcwd_add_track_button_to_my_orders_actions( $actions, $order ) { $tracking_url = get_post_meta( $order->get_id(), 'tracking_url', true ); if ( !empty( $tracking_url ) ) { $actions[ 'track' ] = array( 'url' => $tracking_url, 'name' => 'Track' ); }
return $actions; }
Thank you so much Damien for posting this – yay for open source! Very helpful to me for my site.
It seems (perhaps due to a recent WordPress or WooCommerce update) that the following lines are now causing the “unintended consequences” of which you warned users in your code comment:
remove_action( ‘woocommerce_process_shop_order_meta’, ‘WC_Meta_Box_Order_Data::save’, 40 ); add_action( ‘save_post’, ‘WC_Meta_Box_Order_Data::save’, 50 );
These lines caused an error (sometimes with a message, sometimes “hidden”) every time a post was updated on my site (some examples: when updating a blog post I got an error message, when a customer purchased a product the payment still went through but showed up as “processing payment” on the admin side).
I was able to prevent this from happening by simply commenting out these lines. The only downside is that I have to make sure I add the tracking data and update before marking the order complete. I added the following to the tracking_number field in the dcwd_order_metabox function so that it shows up on the page to remind me:
‘description’ => ‘Be sure to add tracking data and click update before setting the order status to completed. Setting the order status to completed and then clicking update will send an email to the customer. If the tracking data has not been added (with an order update), the email sent to the customer will not contain the tracking data.’,
Also a couple of typos you might want to fix: Change “FexEd” to “FedEx” Change “Move the saving or order meta” to “Move the saving of order meta”
Apologies, the error caused the status to be “Pending payment” not “Processing payment” as I said in my previous comment. Without the error a new order status would be “Processing.”
@Aaron: Thank you so much for trying out the code and investigating the unintended consequences. I’ve fixed the two typos – thanks for that too.
Maybe I could fix the core issue by checking the post type before calling WC_Meta_Box_Order_Data:save. Something like this:
add_action( 'save_post', 'dcwd_save_cmb2_order_info', 50, 3 ); function dcwd_save_cmb2_order_info( $post_id, $post, $update ) { if ( 'shop_order' == $post->post_type) { WC_Meta_Box_Order_Data::save($post_id); } }
I’ll try this out and let you know.
Hi, I’m also facing the same issue (new orders showing Pending Payment instead of Processing). Tried the code block from above but didn’t work :(
Is there a workaround while still being able to send email after updating the tracking #, URL, and mark ‘Complete’ all at the same time?
Thank you so much!
@Vickie – Did you try the latest version of the code (version 0.7)? It has the ‘save_post’ action I mentioned in reply to Aaron.
Yes I tried the latest version 0.7, when I comment out lines 39-44 it no longer has that problem but have to update order with tracking info first, then mark Complete. It may be conflicting with a plugin that I use to skip payment?
This is the plugin I’m using: https://wordpress.org/plugins/wc-place-order-without-payment/
Thank you!
@Vickie – I could not reproduce that issue. I installed and activated that plugin. I submitted an order and then edited the order, entered tracking number and url and set status to Completed and then clicked Update. I got the Completed Order email with the tracking url in it.
Hey! Thank you for the amazing job. I’m using Email Customizer plugin and for me the
add_action( ‘plugins_loaded’, ‘dcwd_check_for_email_template_customizer’ );
line doesn’t work. I’m sure of this cause if I use the default email template your code works as expected. The problem seems to be related to the hook “plugin_loaded” itself that is not being called when using Email Customizer. I don’t have idea why. If I change to “after_setup_theme” is going to work but I’m not sure if it’s safe to use it.
What do you think about this?
Again, thank you so much! Robby
@Roberto: Sorry that it has taken me two weeks to reply to you. I installed Email Customizer 1.1.8, WooCommerce 6.5.1 and CMB2 and my tracking code (version 0.7).
I did not have to change the code to make the tracking information appear in the email. I did add a WC Hook component to the email as shown in the screenshots in post.
This all tells me that the ‘plugins_loaded’ action is run.
Hi Damien.
I am using version 0.7 and WooCommerce 6.7.0. When creating a new order there’s something preventing it to being assign to the customer (it’s assign to guest). The issue doesn’t happen with your plugin disabled.
Can you check this out? Thanks.
@Miguel – I do not see this issue with 0.7 and WooCommerce 6.8.0. I created an order with a new user. I edited the order, added the tracking info, set status to Completed and clicked Update. The customer received an email with the tracking info.
Hi Damien… your code is awesome, thank you so muuch… :)
I am triying with a Multisite Wordpress, when i create orders into two o more sites, the orders can’t saving – saved like invitate client – so in account page user, not appear and when i try to delete order, this changes state to “Pending for payment”.
I would greatly appreciate your help and comments.
Greetings.
@Julio: Is the plugin Network Active or activated separately on each subsite? I did not write the code to work on Multisite (though it might work).
Please tell me how you create an order on two or more sites. Is there an error message when you try to delete an order?
I emailed Julio. He explained that what was happening. He changed the hook in
add_action( 'wp_loaded', 'dcwd_move_wc_order_meta_save');
to
add_action( 'woocommerce_process_shop_order_meta', 'dcwd_move_wc_order_meta_save');
and it fixed it.
I don’t understand how it still works as that hook is mentioned in the
remove_action()
call in the ‘dcwd_move_wc_order_meta_save
‘ function.Changing add_action to ‘woocommerce_process_shop_order_meta’ seemed to help for me as well as I was getting a critical error when deleting orders my pending orders. Not sure if it throws on other types of orders.
The error I got was the following:
An error of type E_ERROR was caused in line 557 of the file /var/www/html/wp-content/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php. Error message: Uncaught Exception: Order status is missing. in /var/www/html/wp-content/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php:557 Stack trace: #0 /var/www/html/wp-content/plugins/plants-customizer/include/tracking.php(54): WC_Meta_Box_Order_Data::save(41) #1 /var/www/html/wp-includes/class-wp-hook.php(307): plants_tracking_save_post_order_data(41, Object(WP_Post), true) #2 /var/www/html/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters(NULL, Array) #3 /var/www/html/wp-includes/plugin.php(476): WP_Hook->do_action(Array) #4 /var/www/html/wp-includes/post.php(4673): do_action(‘save_post’, 41, Object(WP_Post), true) #5 /var/www/html/wp-includes/post.php(4775): wp_insert_post(Array, false, true) #6 /var/www/html/wp-includes/post.php(3575): wp_update_post(Array) #7 /var/www/html/wp-admin/edit.php(121): wp_trash_post(41) #8 {main} thrown
@Boris: WooCommerce 6.9.4 changed how
WC_Meta_Box_Order_Data::save()
is called and my code breaks. In version 0.9 I have commented out the changes of March and May 2022 and just use WooCommerce’s built-in feature to defer the email by 10 seconds. I use and extend that functionality in my ‘Defer WooCommerce emails for a few minutes‘ post.Hello and thanks for your very helpful snippets. If I understand correctly, it is not possible with your plugin to use REST API to allow third parties to send the tracking link directly in woocommerce. Right?