Fires once a post has been saved.
Parameters
$post_id
int- Post ID.
$post
WP_Post- Post object.
$update
bool- Whether this is an existing post being updated.
More Information
save_post
is an action triggered whenever a post or page is created or updated, which could be from an import, post/page edit form, xmlrpc, or post by email. The data for the post is stored in $_POST
, $_GET
or the global $post_data
, depending on how the post was edited. For example, quick edits use $_POST
.
Since this action is triggered right after the post has been saved, you can easily access this post object by using get_post($post_id);
.
NOTE: As of WP 3.7, an alternative action has been introduced, which is called for specific post types: save_post_{post_type}
. Hooking to this action prevents your callback to be unnecessarily triggered.
Avoiding infinite loops
If you are calling a function such as wp_update_post
that includes the save_post
hook, your hooked function will create an infinite loop. To avoid this, unhook your function before calling the function you need, then re-hook it afterward.
/**
* Makes all posts in the default category private.
*
* @see 'save_post'
*
* @param int $post_id The post being saved.
*/
function set_private_categories( $post_id ) {
// If this is a revision, get real post ID.
$parent_id = wp_is_post_revision( $post_id );
if ( false !== $parent_id ) {
$post_id = $parent_id;
}
// Get default category ID from options.
$defaultcat = get_option( 'default_category' );
// Check if this post is in default category.
if ( in_category( $defaultcat, $post_id ) ) {
// unhook this function so it doesn't loop infinitely
remove_action( 'save_post', 'set_private_categories' );
// update the post, which calls save_post again.
wp_update_post( array( 'ID' => $post_id, 'post_status' => 'private' ) );
// re-hook this function.
add_action( 'save_post', 'set_private_categories' );
}
}
add_action( 'save_post', 'set_private_categories' );
Source
do_action( 'save_post', $post_id, $post, $update );
Related
Used by | Description |
---|---|
WP_Customize_Manager::trash_changeset_post()wp-includes/class-wp-customize-manager.php | Trashes or deletes a changeset post. |
wp_publish_post()wp-includes/post.php | Publishes a post by transitioning the post status. |
wp_insert_post()wp-includes/post.php | Inserts or update a post. |
Changelog
Version | Description |
---|---|
1.5.0 | Introduced. |
When using WordPress 3.7 or later, it’s a good idea to use the save_post_{$post->post_type} hook when it makes sense to in order to reduce code and fire less hooks overall when posts are created and updated.
Documentation can be found here: https://developer.wordpress.org/reference/hooks/save_post_post-post_type/
Force a new post of have specific category term,
The save_post_{post_type} hook fires BEFORE the general save_post hook, meaning that save_post will override any meta updates made with save_post_{post_type}. Many plugins like ACF and Pods use the save post action hook, so if you are trying to update a meta field and you are using one of these plugins, you must use the save_post hook instead.
Below is a basic example that will send an email every time a post or page is updated on your website.
The documentation provides a way to avoid an infinite loop by removing the hook and then adding it again after we have used a function such as `wp_update_post`.
The problem about this is that other actions on the same hook may still trigger twice.
I came up with the following alternative:
By doing this we take a “snapshot” of all the registered hooks before we update the post. Then we remove all the actions for the hook, and once it has been updated, we restore the snapshots.
You should also think about removing all `save_post_{$post->post-type}` actions and viceversa.
It would be nice if there was a core function for this. `wp_update_post` already has a third optional parameter to prevent firing the after insert hooks. A fourth parameter could be added to prevent firing the `save_post` and `save_post_{$post->post-type}` hooks so that we don’t have to use workarounds to prevent the infinite loop issue.
To trigger for specific post type, assume we have a post type name ‘book’
The
$post_ID
passed to the action is the ID of the revision while updating a post. To find the ID of the parent post, usewp_get_post_parent_id
.I was trying to add a hook to review the current posts and terms in the database whenever a post was updated (through the regular editing interface, not direct calls to the API) but hit a problem.
If a user updates only a post’s Terms (categories, tags),
save_post
is triggered before the new Terms are pushed to the database. (If the Term changes are saved with any other changes, the Terms are written to the DB beforesave_post
is triggered.)Details:
wp_insert_post()
does look like it saves Terms before triggering thesave_post
action but the Term data it passes to (ultimately)wp_set_object_terms()
is the OLD terms for the post. (Why resave the old data? Dunno.)After
wp_insert_post()
triggerssave_post
, an additional call towp_set_object_terms()
is made (fromWP_REST_Posts_Controller
) that has a the new Terms data in it. (I didn’t dig further to find out why the two calls for this case.)Workaround:
If your hook needs the Terms to be accurate, attach it to
rest_after_insert_(post|page|attachment)
instead. This is triggered when ALL changes are stored, regardless of whether it is just the post content, just the terms, or both.Here is my updated code which will check whether post title is exist or not before inserting into news post title.
Here is a simple example which will save post on publish and also at the same time add the post in another post type also.