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 a specific hook which runs when a product is updated, woocommerce_update_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_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 }
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
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: