Hooks When Creating or Updating a Post (Classic Editor, Gutenberg and REST API)

Probably all of us know the save_post action hook which usually runs every time a WordPress post of any type (or even a WooCommerce product) is getting created or updated.

But sometimes you need to process only post updates in some specific cases, for example only when you do it via the REST API or using the Classic Editor. Is there a way to do that?

Let’s try to figure it out.

First of all why not to refresh our memory by taking a look at the table below:

Hooks (in the same order)
Classic Editorsave_post_{POST TYPE},
save_post;
Gutenbergsave_post_{POST TYPE},
save_post,
rest_after_insert_{POST TYPE},
save_post,
Additionally if there are any meta boxes:
save_post_{POST TYPE},
save_post;
Other REST API requestssave_post_{POST TYPE},
save_post,
rest_after_insert_{POST TYPE} (or woocommerce_rest_insert_product_object for WooCommerce products);

So, you can see from the table above that save_post is our best friend 😁 It runs every time! And not just every time – it runs even three times in Gutenberg.

It is kind of similar situation to woocommerce_order_update hook which I described here. “Kind of”, because in WooCommerce there is no way to run a custom function connected to the hook only once and we need to do some tricky things for that, but here we can achieve everything easily just by connecting to the right hook and using the proper conditions inside our function.

For example, in the Block editor, if you don’t want save_post to be run three times, you can just switch to the more correct rest_after_insert_{POST TYPE} hook instead. My plugins, especially Simple WP Crossposting and Simple Multisite Crossposting rely on these hooks a lot, that’s why I basically became a pro in this topic 😁

And now let’s take a look at different examples and conditions which we can use.

How to Detect the Block Editor (Gutenberg)

Here actually two things are going to help us:

  1. wp_is_serving_rest_request() function (or REST_REQUEST constant) will help to differentiate from the Classic Editor.
  2. wp_get_referer() function will help to differentiate from other REST API requests.

When using save_post:

add_action( 'save_post', 'misha_gutenberg_only' );

function misha_gutenberg_only() {
	
	if( ! wp_is_serving_rest_request() ) {
		return;
	}
	
	// do the stuff for Gutenberg	
}

When using rest_after_insert_{POST TYPE}:

add_action( 'rest_after_insert_post', 'misha_gutenberg_only', 10, 3 );

function misha_gutenberg_only( $post, $request, $creating ) {
	
	if( 0 !== strpos( wp_get_referer(), admin_url() ) ) {
		return;
	}
	
	// do the stuff for Gutenberg	
}

How to detect if currently meta boxes are being saved

Should I mention one more time that using regular meta boxes in Gutenberg is not a good idea after all? So first of all, please consider using plugin sidebars instead.

But if you’re still using something like this…

Advanced Custom Fields example in Gutenberg
Old-fashioned way of creating custom fields.

… then the example below should be helpful for you:

add_action( 'save_post', 'misha_meta_boxes_only' );

function misha_meta_boxes_only( $post_id ) {
	
	if( ! isset( $_REQUEST[ 'meta-box-loader' ] ) ) || ! $_REQUEST[ 'meta-box-loader' ] ) {
		return;
	}

	// do something here
	
}	

Basically how it works – after your post has been saved WordPress sends one more asynchronous request to update these meta boxes.

Take a look at this if you don’t believe me:

Preloader when saving meta boxes in Gutenberg

Now live with this information πŸ™ƒ

How to Detect the Classic Editor

In order to check whether save_post and save_post_{POST TYPE} hooks are running when you’re saving it via the Classic editor you just need to check a $_POST[ 'action' ] variable. If its value is editpost, it means:

  • Either you’re saving a post via the Classic Editor…
  • …or you’re saving the meta boxes in the Block Editor.

Well, yes, the last part complicates things a little bit, but not a lot, we just need to add a one more condition from this part of the tutorial.

add_action( 'save_post', 'misha_classic_editor_only' );

function misha_classic_editor_only( $post_id ) {
	
	// condition one
	if( ! isset( $_POST[ 'action' ] ) || 'editpost' !== $_POST[ 'action' ] ) {
		return;
	}
	// condition two
	if( ! empty( $_REQUEST[ 'meta-box-loader' ] ) {
		return;
	}

	// Classic Editor stuff
	
}

How to Detect Other REST API Calls

Well, if you’re using WooCommerce, just stick to woocommerce_rest_insert_product_object and nothing else you need to do. Maybe we would need to use some extra conditions later when they add the new product editor, but for now just relax.

Ok, but what to do if we’re going to run rest_after_insert_{POST TYPE} and we need to skip requests from Gutenberg? Well, in that case we can just do the opposite I showed you here.

But another way is to check a user agent:

add_action( 'rest_after_insert_post', 'misha_rest_api_only', 10, 3 );

function misha_rest_api_only( $post, $request, $creating ) {

	if( 0 !== strpos( $request->get_header( 'user_agent' ), 'WordPress/' ) ) {
		return;
	}
	
	// do the stuff for a REST API request sent via WordPress HTTP API
}

So, the user agent in a REST API request when you send it via WordPress is going to be something like: “WordPress/6.5.3; http://SITE URL” but when you’re editing your post via Gutenberg, it is going to be something like: “Mozilla/5.0 (Macintosh; Intel Mac OS X…”.

And should I even mention that you can set a custom User-Agent header when sending your REST API requests?

How to Use Hooks when Publishing New Posts Only?

The last but not least let’s talk about that as well.

Is there is a way to run the hooks mentioned above in the following situations:

  • for completely new posts only,
  • or only when updating existing posts;

Yes, and that’s what a third parameter is for!

save_post example:

add_action( 'save_post', function( $post_id, $post, $update ) {

	if( $update ) {
		// updating an existing post
	} else {
		// creating a new one
	}
	
}, 25, 3 );

rest_after_insert_{POST TYPE} example:

// add_action( 'woocommerce_rest_insert_product_object', function( $post, $request, $creating ) {
add_action( 'rest_after_insert_post', function( $post, $request, $creating ) {

	if( $creating ) {
		// creating a new one
	} else {
		// updating an existing post
	}
	
}, 25, 3 );

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