I recently needed to write a sync function that was triggered when a WooCommerce product is saved in the admin, i.e. added or updated. Strangely, there isn’t a WooCommerce-specific hook in the documentation. Since WooCommerce products are a WordPress custom post type, we should be able to make use of the save_post_{$post_type} action. When I tried the following:
add_action('save_post_product', 'mp_sync_on_product_save', 10, 3); function mp_sync_on_product_save( $post_id, $post, $update ) { $product = wc_get_product( $post_id ); // do something with this product }
I found that the retrieved product contained the data from before the latest changes had been saved. But the hook runs after the post has been saved to the database – how could that be? Then it hit me – most of the WooCommerce product data is stored as post_meta. And the save_post hook runs before the post_meta is updated. So we need to use a hook which fires after the post_meta is updated or added. It turns out we have to hook on 2 separate actions in our case (added_post_meta, when the product post_meta is added; and updated_post_meta, when it is updated). We also want to use added_post_meta, rather than add_post_meta as this runs after the changes are saved to the database.
The post_meta hooks fire when any post_meta is changed, which means we need to check a few things:
- If multiple product fields have been updated, the hook will be triggered for each. Luckily, there is a meta_key called ‘_edit_lock’ which is always set when editing a post, so we can trigger our function when this is set.
- We also need to check that we are working with a product post_type.
Putting all this together gives us our modified hook which runs when all the WooCommerce product data has been saved to the database:
add_action( 'added_post_meta', 'mp_sync_on_product_save', 10, 4 ); add_action( 'updated_post_meta', 'mp_sync_on_product_save', 10, 4 ); function mp_sync_on_product_save( $meta_id, $post_id, $meta_key, $meta_value ) { if ( $meta_key == '_edit_lock' ) { // we've been editing the post if ( get_post_type( $post_id ) == 'product' ) { // we've been editing a product $product = wc_get_product( $post_id ); // do something with this product } } }
Updated method for WooCommerce 3.x
As of 3.0, WooCommerce has specific hooks which run when a product is updated (woocommerce_update_product) and when a product is created (woocommerce_new_product). This allows us to use a much simpler function where we no longer have to check if a product is being updated, or if the _edit_lock flag is set:
add_action( 'woocommerce_new_product', 'mp_sync_on_product_save', 10, 1 ); add_action( 'woocommerce_update_product', 'mp_sync_on_product_save', 10, 1 ); function mp_sync_on_product_save( $product_id ) { $product = wc_get_product( $product_id ); // do something with this product }
Hello Robin,
I’m looking to do something that I don’t know if it’s possible, but when I was looking I came across this article and I would like to know if you have any solution for this specific thing.
What I need is for the “Manage Stock” box to be checked by default when adding a new product.
Is this possible with some hook or snippet?
thanks in advance
Hello, I have a question. I’m changing the item’s status to Available for pre-order after checking the item’s in-stock field. I save the change, when the products are displayed, the function is triggered, but the user sees the result only after the page is refreshed. And how to update the page using php I don’t know Please tell me.
get_meta( ‘_moscow_field’,true );
if ( $moscow_text_field == “Да” ) {
$product->set_backorders(‘notify’);
}
else {
$product->set_backorders(‘no’);
}
$product->save();
}
add_action( ‘woocommerce_before_shop_loop_item’, ‘shsa_backorders_custom_fields_save’);
Will your 3.x work for adding new products and updating as well?
Good question! I just checked the code base and the woocommerce_update_product action only runs when a product is updated. To hook on a new product being created, use the woocommerce_new_product action. I’ve updated my code above accordingly. Thanks for spotting this.
Hi Robin , first of all very thanks for this great article.I followed your guide & I am trying to create a inventory check system which will monitor the behavior of inbuilt stock management of WooCommerce.But execution time of my code is higher and some time it store some wrong values for product. i will very great-full for your valuable advice on this issue. I am sharing my code
Code –
https://pastebin.com/Sap4Cnwm
Hi Siddharth. I can’t see anything obviously wrong with your code. You haven’t included the check_stock_in_master function, so this might be where things are slowing down. A few thoughts:
use:
WooCommerce now uses various lookup tables which are more efficient than postmeta queries, so these functions will speed up your code and also be more future-proof.
I would just use $product_id instead of $current_product_id.
Also:
Just use $manage_stock.
Thanks for reply
Code for check_stock_in_master
function check_stock_in_master($post_ID)
{
/* This function will read the value from wp_custom_woocommerce_stock_master according to post ID
And reurn an $rowcount
*/
global $post;
global $wpdb;
$current_post = $post_ID;
$results = $wpdb->get_results( “SELECT * FROM wp_custom_woocommerce_stock_master WHERE post_id = $current_post”);
$rowcount = $wpdb->num_rows;
return $rowcount;
}
Hi robin, I think there is a issue with
if(get_post_type( $post_id ) == ‘product’ && get_post_status( $post_id ) == ‘publish’) block & in if ($product->is_type( ‘variable’ )) block. I added break point on this & in the case of variable product it causes the problem, Some time variation value added but some time missed.
Any Advice. i will very great-full for your valuable advice on this issue.
Hi robin, As you suggested i tried woocommerce_update_product hook & it is working as expected but only one issue i am facing is i think this hook runs more than one time.in my case it runs two times if i added 4 variations of the product.Is it true or there is something wrong with my implementation.
Please advice.
Hello Robin, I want a list of updated product in woocommerce.Can you please help me with the issue. I need to create a changelog for customers about the details of products updated or added.
Hi Malik. You probably want to start with a WC_Query, something like:
Here’s some documentation on wc_get_products.
Hi,
code is working fine. May I know how I can append email the product url to the user who published the product?
add_action( ‘woocommerce_new_product’, ‘mp_sync_on_product_save’, 10, 1 );
add_action( ‘woocommerce_update_product’, ‘mp_sync_on_product_save’, 10, 1 );
function mp_sync_on_product_save( $product_id ) {
$product = wc_get_product( $product_id );
wp_update_post( array( ‘ID’ => $product->get_id(), ‘post_status’ => ‘pending’ ) );
$vendor_id = get_post_field( ‘post_author’, $product_id );
$vendor = get_userdata( $vendor_id );
$produtct_id=$product->get_id();
$subject = “New order #$order_id”;
$body = ‘New product has been added by ‘ . $vendor_id . $product_id . ‘ ‘ ;
$headers = array(
‘Content-Type: text/html;’,
‘charset=UTF-8’,
);
$to = array( ‘roshandimateo@hotmail.com’ );
$headers[] = ‘Cc: roshandimateo@hotmail.com‘;
wp_mail( $to, $subject, $body, $headers );
}
Hi Robin,
I want to store Stock status & Stock quantity of simple product & as well as variable product when new product added in woocommerce.Basically i am trying to make parallel stock inventory system.
My code is –
add_action( ‘added_post_meta’, ‘wc_set_product_stock_callback_post_meta’,10,4);
function wc_set_product_stock_callback_post_meta($meta_id, $post_id, $meta_key, $meta_value )
{
global $wpdb;
global $current_user;
if ( $meta_key == ‘_edit_lock’ )
{
if(get_post_type( $post_id ) == ‘product’ )
{
$product_id = $post_ID;
$tablename = $wpdb->prefix.’woocommerce_stock_master’;
if(!$product_id)
{
return;
}
wp_get_current_user();
$user_id = $current_user->ID;
// get an instance of the WC_Product Object
$product = wc_get_product( $product_id );
$stock_qty = $product->get_stock_quantity();
$product_id = $product->get_id();
$stock_notes = ‘New Stock’;
$producut_in_master = check_stock_in_master($product_id);
if($producut_in_master insert( $tablename, array( ‘post_ID’ => $product_id, ‘user_ID’ => $user_id, ‘stock_quantity’=> $current_stk ,’notes’=> $stock_notes) );
}else{
//update statement
$check_stk = check_stock_value_by_id($product_id);
$current_stk = $check_stk[0]->stock_quantity;
//$current_stk = $current_stk + $stock_qty;
$current_stk = $stock_qty;
$stock_notes = ‘Stock Update.’;
$wpdb->update(
$tablename,
array(
‘stock_quantity’ => $current_stk,
‘notes’ => $stock_notes
),
array(‘post_id’ => $product_id)
);
}
}else{
return;
}
error_log(‘Quantity is ‘.$stock_qty . “–“.$producut_in_master );
}
error_log(‘Edit Lock Error -‘. $post_id);
}
My problem is i am unable to read value of variable products. Can you please help on this
hi robin hislop and everyone who is able to see this.
i need a help regarding this hook “updated_post_meta”. i need to trigger this hook when the stock of the product get changed. something like this:
if($metakey == “_stock”){
// my code
}
so, the problem is with variable product. when i change the quantity of the any variant of this product this hook is not triggered. But gets triggered when i change the quantity of this parent product . But i also want this hook get triggered when i change the quantity of any variant also. Is there any other hook regarding this ?
Thank you.
Hi. Can you post your code in Pastebin or similar please?
Hey Robin,
I really need a way to save/update a product when something has been edited with the rest API. I want to use your code, and use woocommerce_update_product instead, but not sure how to then actually trigger the save once something has been edited via an API call… Please help.
The woocommerce_update_product action only has 1 parameter – $product_id – so you don’t need to worry about any of the others. It also only runs when a product is updated, so we don’t need to check for this. Try this:
Simple!
Hi, excuse my ignorance, where should this code be inserted?
Thanks
functions.php
Hi Robin, thanks for sharing this…really save me some time.
One question: if you’re sitting on the edit product page the “_edit_lock” meta key is continuously updated and so any associated code I’ve associated with this hook will run too which is not ideal.
Do you have any pointers as to how to differentiate between an “_edit_lock” meta update like you’ve described (which we can use to know when all the meta updates for a product are complete) and the other one that is just fired on a heartbeat when sitting on the edit page?
Cheers
Hi Ryan. You make a good point, and illustrate why this solution is not ideal. It may be worth investigating the new woocommerce_update_product hook which was introduced in 3.0 (I think). Let me know how you get on, it might be a more elegant solution.
If I add
update_post_meta( $post_id, ‘poster_artist’, $meta_value );
after the line: // do something with this product
is it correct that the $meta_key should be the name of the column/field in the database I wish to change?
Be careful not to trigger an infinite loop by calling update_post_meta in the function.
When the added_post_meta or updated_post_meta actions are triggered, $meta_key holds the name of the meta key in the postmeta table. It’s not a database column name, but rather a field name which is stored in the meta_key column of the postmeta table. The value is stored in $meta_value.
Hello i am updating the product meta data on the hook woocommerce_update_product.
The reason is i want to calculate some custom fields.
But i have the problem that the update of the meta data triggers the hook itself again. So it does never stop. (the infinite loop you already warned about).
Does someone have a solution for this or is there a workflow i don’t know yet? This is my first try with wordpress plugins.
Hi Julius, thanks for sharing your solution. Another simple option would be to write the meta data directly:
instead of using $_product->update_meta_data since this won’t trigger the woocommerce_update_product hook again.
Hi Robin,
ah thanks for the hint! This seems easier.
here is how you can update post meta data and save posts without infinite loops:
https://wordpress.stackexchange.com/a/51364
just remove the hook itself
then save
and then add the hook again
Unfortunately “updated_post_meta” runs even on edit product page (GET method), and before saving anything!
how is it possible ?
Hi Reza. If you look at the source code for updated_post_meta you will see that the meta data is saved before the updated_{post_type}_meta hook runs:
The update_{post_type}_meta (no “d”) hook does run before the data is saved, but this is a different hook.
Thank you, you saved me a lot of time.
Hi Robin,
your post helped me alot. It was easier, than expected. 🙂
I’ve tested your code via a custom plugin and I’am wondering why my debug.log tells me, that the hook keeps firing.
Do you have a idea why that might be?
Kind regards,
Kevin
Hi Kevin. Can you post the line from your debug log?
Thank you very much for the prompt reply.
I am writing custom ‘error’ messages via error_log into the debug.log to see what’s happening. So the log only writes the message: ‘updated successfully’. It does so for about 1 minute and then stops.
Remember that when you save a product, you will be updating multiple post_meta. The hook will fire on each one. This is why we check the ‘_edit_lock’ key to make sure we only run our code once on each save/update.
sounds reasonable.
Thank you 🙂
I’m getting error “Maximum function nesting level ” when hook to the added_product_meta, when i go to the new product page.
Could you please help me? Regards.
It sounds like you’ve nested the function call – in other words, you’re calling the hook from within the function. This can happen if you update the product inside the mp_sync_on_product_save function, which will then call mp_sync_on_product_save, which will update the function etc. Can you post your code?
the hook `updated_post_meta` also trigger on product create, is there is hook only trigger on product update?
If you look at the code, the mp_sync_on_product_save function is triggered by 2 different actions: added_post_meta and updated_post_meta. So it runs when a product is added or updated.
If you only want it to run on updates, just remove the following line:
Is their any way i could show up all the products (newly added & modified) to show it on a page in the similar form of changelog. Simply, each time i add a product or modified a product it should log details and show up on a page. The above code seems it is creating logs but could you please tell, how to show it up. I hope there needs to be some shortcode to display.
Hi Jason, I think you’d need to use a different approach. Simply use some sort of Recently Added Products widget to display a list of recently modified or added products. Or just use the built-in WooCommerce shortcode: