To run code when a product is published in WooCommerce, hook into transition_post_status and check that the post type is product and the new status is publish while the previous status was anything else — this fires your function at the exact moment a product goes live.
Why Hook Into the Publish Transition
- Auto-set a default value for a custom meta field the moment a product is created
- Send an internal notification or sync the new product to an external system
- Log or timestamp exactly when each product first went live, for reporting
Step 1: Add the Transition Snippet
/** Run custom logic the moment a product is first published */
add_action( 'transition_post_status', 'run_logic_on_product_publish', 10, 3 );
function run_logic_on_product_publish( $new_status, $old_status, $post ) {
if ( 'product' !== $post->post_type ) {
return;
}
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
update_post_meta( $post->ID, 'first_published_flag', current_time( 'mysql' ) );
}
}
The post_type check is essential — transition_post_status fires for every post type on the site (posts, pages, custom types), not just products, so skipping this guard means your callback runs unnecessarily on unrelated content too.
Making It Run Only Once, Ever
Because this hook fires on every publish transition, unpublishing and republishing the same product triggers your function again. To limit it to a true “first publish only” event, check whether the meta already exists before writing:
function run_logic_once_per_product( $new_status, $old_status, $post ) {
if ( 'product' !== $post->post_type || 'publish' !== $new_status || 'publish' === $old_status ) {
return;
}
if ( ! metadata_exists( 'post', $post->ID, 'first_published_flag' ) ) {
update_post_meta( $post->ID, 'first_published_flag', current_time( 'mysql' ) );
}
}
add_action( 'transition_post_status', 'run_logic_once_per_product', 10, 3 );Troubleshooting
The Function Doesn’t Fire While Testing in the Editor
This hook only fires on an actual status transition — clicking the Publish button, or a REST/cron-triggered status change. Inspecting $post properties while still drafting in the block editor before the first save won’t trigger it, since no transition has happened yet.
Runs Every Time You Republish a Product
This is expected default behavior — see the “run once” version above if repeated firing on republish is a problem for your use case.
Frequently Asked Questions
Does this work for products created via the REST API or a CSV import?
Yes, as long as the import process actually sets the post status to publish through WordPress’s normal status transition mechanism, which the core product importer does.
Can I use this to send an email notification?
Yes — replace the update_post_meta() call with wp_mail() or your preferred notification method inside the same conditional block.
Is there a WooCommerce-specific hook instead of the WordPress core one?
WooCommerce doesn’t provide its own dedicated “product published” hook, which is why transition_post_status combined with a post-type check is the standard approach.
Conclusion
The transition_post_status hook, guarded by a post-type and status check, is the reliable way to run code when a product is published in WooCommerce. Add the “already exists” check if you need the logic to fire only once per product rather than on every republish.
See also How to Run a Successful WooCommerce Ecommerce Business. Then read How to Create a Custom Order Status in WooCommerce.
Prefer no code? All-in-One Utilities Woo Add-on adds over 50 WooCommerce features, so you can switch them on without code.