### When `save_post` runs, it has already saved the post on the database. If you are using ACF, it has built-in validation. However, if you need to validate things outside of ACF, such as post_title, things get a little more complicated. Looking into WordPress core code, more specifically at the `wp-includes/post.php`'s `update_post()` function, there is no built-in way to intercept a request before it is saved on the database. However, we can hook `pre_post_update` and use `header()` and `get_post_edit_link()` to prevent the post from being saved. <?php /** * Performs custom validation on custom post type "Site" */ function custom_post_site_save($post_id, $post_data) { # If this is just a revision, don't do anything. if (wp_is_post_revision($post_id)) return; if ($post_data['post_type'] == 'site') { # In this example, we will raise an error for post titles with less than 5 characters if (strlen($post_data['post_title']) < 5) { # Add a notification update_option('my_notifications', json_encode(array('error', 'Post title can\'t be less than 5 characters.'))); # And redirect header('Location: '.get_edit_post_link($post_id, 'redirect')); exit; } } } add_action( 'pre_post_update', 'custom_post_site_save', 10, 2); /** * Shows custom notifications on wordpress admin panel */ function my_notification() { $notifications = get_option('my_notifications'); if (!empty($notifications)) { $notifications = json_decode($notifications); #notifications[0] = (string) Type of notification: error, updated or update-nag #notifications[1] = (string) Message #notifications[2] = (boolean) is_dismissible? switch ($notifications[0]) { case 'error': # red case 'updated': # green case 'update-nag': # ? $class = $notifications[0]; break; default: # Defaults to error just in case $class = 'error'; break; } $is_dismissable = ''; if (isset($notifications[2]) && $notifications[2] == true) $is_dismissable = 'is_dismissable'; echo '<div class="'.$class.' notice '.$is_dismissable.'">'; echo '<p>'.$notifications[1].'</p>'; echo '</div>'; # Let's reset the notification update_option('my_notifications', false); } } add_action( 'admin_notices', 'my_notification' );