Saving Post Meta Field Revisions in WordPress

If your plugin or theme uses custom post meta fields then you may want to store revisions to these fields when a post revision is saved. It’s easy to do.

For each of our meta fields, we’ll need to do three things:

  1. Store a revision of the meta field when a post is saved
  2. Revert to the correct revision of the meta field when a post is reverted
  3. Optionally, display the meta field on the revisions screen

In the code below I’m just saving revisions to one meta field, ‘my_meta’. You can of course save revisions to more than one meta field by looping over a list of fields your plugin uses.

Sorry about the poor code formatting in this article. I really need to fix the CSS on my site.

Storing a revision of the meta field when a post is saved

For this we hook into the save_post action and save the post meta data only if the post being saved is a revision. We need to use the low level add_metadata() function because if we use add_post_meta() then the meta data will be stored against the post instead of the revision.

function my_plugin_save_post( $post_id, $post ) {

        $parent_id = wp_is_post_revision( $post_id );

        if ( $parent_id ) {

                $parent  = get_post( $parent_id );
                $my_meta = get_post_meta( $parent->ID, 'my_meta', true );

                if ( false !== $my_meta )
                        add_metadata( 'post', $post_id, 'my_meta', $my_meta );

        }

}
add_action( 'save_post', 'my_plugin_save_post' );

Reverting to the correct revision of the meta field when a post is reverted

For this we hook into the wp_restore_post_revision action and update the meta field to the version stored at the revision we’re reverting to. Again we’re using the low level get_metadata() function instead of get_post_meta() so we can grab the meta data from the revision instead of the current post.

function my_plugin_restore_revision( $post_id, $revision_id ) {

        $post     = get_post( $post_id );
        $revision = get_post( $revision_id );
        $my_meta  = get_metadata( 'post', $revision->ID, 'my_meta', true );

        if ( false !== $my_meta )
                update_post_meta( $post_id, 'my_meta', $my_meta );
        else
                delete_post_meta( $post_id, 'my_meta' );

}
add_action( 'wp_restore_post_revision', 'my_plugin_restore_revision', 10, 2 );

Displaying the meta field on the revisions screen

For this we need to hook into the _wp_post_revision_fields action to tell WordPress which fields to show on the revisions screen, and hook into the _wp_post_revision_field_my_meta filter to display the meta field. Note that the filter name is specific for each field, so you’ll need to add a hook for each of your meta fields in the format _wp_post_revision_field_{field_name}.

function my_plugin_revision_fields( $fields ) {

        $fields['my_meta'] = 'My Meta';
        return $fields;

}
add_filter( '_wp_post_revision_fields', 'my_plugin_revision_fields' );

function my_plugin_revision_field( $value, $field ) {

        global $revision;
        return get_metadata( 'post', $revision->ID, $field, true );

}
add_filter( '_wp_post_revision_field_my_meta', 'my_plugin_revision_field', 10, 2 );

Putting it all together

Download the example code as a plugin here.

You’ll need to manually add/edit the ‘foo’ field using the Custom Fields panel on the post editing screen, then you can play around with viewing and reverting revisions.

Some considerations

The code used to display the meta field on the revisions screen will generate a PHP notice because WordPress looks for the my_meta element of the post object before calling the field callback function. There’s a ticket on Trac somewhere about improvements to the revisions screen but I can’t find it currently.

When you compare two post revisions using the compare functionality on the revisions screen the meta fields won’t be taken into account. Again, the revisions screen in WordPress needs some improvements in this area.

10 thoughts on “Saving Post Meta Field Revisions in WordPress

  1. Great tip. It seems to work, except one little thing: If i hit the “Compare Revisions” button, I always get “These revisions are identical.”, but using “get_post_meta” on the revision ID, I can see the “old” meta values. Restoring works also, only the compare function doesn’t work properly.

    Cheers
    Roman

  2. You’re right Roman, the revision comparison functionality doesn’t take post meta fields into account, and if the other post fields are the same then it’ll give you the “These revisions are identical” message.

    I’m sure there’s a ticket on WordPress’ Trac regarding improvements to the revisions screen, but for the life of me I can’t find it. I’m sure it covered this situation too.

  3. Found this page and it’s very helpful, however something I’m not seeing explained is the function in the plugin file, pmr_fields. Can you elaborate on what this function does and why it is there?

    Thanks again, this is very helpful information.

  4. Hi Josh,

    In the plugin the function pmr_fields() is the function which outputs the fields on the Revisions screen. It’s the bit explained in the “Displaying the meta field on the revisions screen” section in the post above.

    Hope this helps. John.

  5. Thanks for the response.

    So in the case of multiple types of custom fields, e.g. a custom post type with some of its own custom meta fields and other custom meta fields for pages, would that function be able to selectively output fields based on the type of post?

    i.e. is anything passed to that function that would help determine which fields to return?

  6. If you want to introduce logic to determine which fields to show based on post type then you’ll have to use something like the get_current_screen() object. There may be other parameters passed to that filter, but I don’t know off the top of my head.

  7. Thanks. I got it working easily enough by bringing in the global $post object to that function.

    I owe you a beer (or six).

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>