Add validation and error handling when saving custom fields?

I have a function that defines a custom field on a post type. Say the field is “subhead”.

When the post is saved, I want to do some validation on the input, and display an error message on the post edit screen if necessary. Something like:

Read More
// Handle post updating
function wpse_update_post_custom_values($post_id, $post) {

    // Do some checking...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors->add('oops', 'There was an error.');

    }

    return $errors;

} 
add_action('save_post','wpse_update_post_custom_values',1,2);

I’m trying to hook this to the save_post action, but I can’t figure out how to handle errors. There doesn’t appear to be an error object passed into the function, and if i create my own WP_Error obj and return it, it’s not respected by whatever mechanism spits out errors on the post edit page.

I currently have an on-page error message inside my custom meta box, but this is less than ideal–I’d rather have a big, red, up-at-the-top error like WP normally displays.

Any ideas?

UPDATE:

Based on @Denis’ answer, I tried a few different things. Storing errors as a global didn’t work, because WordPress does a redirect during the save_post process, which kills the global before you can display it.

I ended up storing them in a meta field. The problem with this is that you need to clear them out, or they won’t go away when you navigate to another page, so I had to add another function attached to the admin_footer that just clears out the errors.

I wouldn’t have expected that error handling for something so common (updating posts) would be this clunky. Am I missing something obvious or is this the best approach?

// Handle post updating
function wpse_5102_update_post_custom_values($post_id, $post) {

    // To keep the errors in
    $errors = false;

    // Do some validation...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors .= 'whoops...there was an error.';

    }

    update_option('my_admin_errors', $errors);

    return;

} 
add_action('save_post','wpse_5102_update_post_custom_values',1,2);


// Display any errors
function wpse_5102_admin_notice_handler() {

    $errors = get_option('my_admin_errors');

    if($errors) {

        echo '<div class="error"><p>' . $errors . '</p></div>';

    }   

}
add_action( 'admin_notices', 'wpse_5102_admin_notice_handler' );


// Clear any errors
function wpse_5102__clear_errors() {

    update_option('my_admin_errors', false);

}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );

Related posts

Leave a Reply

7 comments

  1. Store errors in your class or as a global, possibly in a transient or meta, and display them in admin notices on POST requests. WP does not feature any flash message handler.

  2. I suggest to use sessions since this will not create strange effects when two users editing at the same time. So this is what I do:

    Sessions are not started by wordpress. So you need to start a session in your plugin, functions.php or even wp-config.php:

    if (!session_id())
      session_start();
    

    When saving the post, append errors and notices to the session:

    function my_save_post($post_id, $post) {
       if($something_went_wrong) {
         //Append error notice if something went wrong
         $_SESSION['my_admin_notices'] .= '<div class="error"><p>This or that went wrong</p></div>';
         return false; //might stop processing here
       }
       if($somthing_to_notice) {  //i.e. successful saving
         //Append notice if something went wrong
         $_SESSION['my_admin_notices'] .= '<div class="updated"><p>Post updated</p></div>';
       }
    
       return true;
    } 
    add_action('save_post','my_save_post');
    

    Print notices and errors and then clean the messages in the session:

    function my_admin_notices(){
      if(!empty($_SESSION['my_admin_notices'])) print  $_SESSION['my_admin_notices'];
      unset ($_SESSION['my_admin_notices']);
    }
    add_action( 'admin_notices', 'my_admin_notices' );
    
  3. Based on pospi‘s suggestion to use transients, I came up with the following. The only problem is there is no hook to put the message below the h2 where other messages go, so I had to do a jQuery hack to get it there.

    First, save the error message duing your save_post (or similar) handler. I give it a short lifetime of 60 seconds, so it is there just long enough for the redirect to happen.

    if($has_error)
    {
      set_transient( "acme_plugin_error_msg_$post_id", $error_msg, 60 );
    }
    

    Then, just retrieve that error message on the next page load and display it. I also delete it so it wont get displayed twice.

    add_action('admin_notices', 'acme_plugin_show_messages');
    
    function acme_plugin_show_messages()
    {
      global $post;
      if ( false !== ( $msg = get_transient( "acme_plugin_error_msg_{$post->ID}" ) ) && $msg) {
        delete_transient( "acme_plugin_error_msg_{$post->ID}" );
        echo "<div id="acme-plugin-message" class="error below-h2"><p>$msg</p></div>";
      }
    }
    

    Since admin_notices fires before the primary page content is generated, the notice is not where the other post edit messages go, so I had to use this jQuery to move it there:

    jQuery('h2').after(jQuery('#acme-plugin-message'));
    

    Since the post ID is part of the transient name, this should work in most multi-user environments except when multiple users are concurrently editing the same post.

  4. When save_post runs, it has already saved the post on the database.

    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 validation before saving/inserting custom post type
    */
    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'] == 'my_custom_post_type') {
            // Deny post titles with less than 5 characters
            if (strlen($post_data['post_title'] < 5)) {
                header('Location: '.get_edit_post_link($post_id, 'redirect'));
                exit;
            }
        }
    }
    add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
    

    If you to notify the user what went wrong, check this gist: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

  5. Trying to use the script above, I ran into a strange problem. Two messages are shown on the edit screen, after the post update. One is showing state of the content from previous save and another one from the current. For example, if I save the post properly and then make an error, first one is “error” and second one is “ok” – altough they are generated in the same time. If I change the script and append only one message (e.g. “error”), initiate one update with “error” and after that another one with “ok”, “error” message stays (is displayed for the second time). I must save with “ok” once again to get rid of it. I really don’t know what’s wrong, I’ve tested it on three different local servers and there’s the same issue on each one of them. If anyone has any idea or suggestion, please help!

  6. I’ve written a plugin that adds in a flash error handling for post edit screens and prevents posts being published until required fields are filled out:

    https://github.com/interconnectit/required-fields

    It allows you to make any post fields mandatory but you can use the API it provides to make any custom fields required too with a customisable error message and validation function. It defaults to checking if the field is empty or not.