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 ‘my_meta’ 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.

16 thoughts on “Saving Post Meta Field Revisions in WordPress

  1. Wow this is very useful! I hope this works with WordPress 3.3.2. Going to give it a try.

  2. 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

  3. 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.

  4. 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.

  5. 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.

  6. 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?

  7. 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.

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

    I owe you a beer (or six).

    1. The fields will be correctly revisioned, yes, but I’ve no idea whether the admin interface will work. I’ll have to give it a test.

  9. Hi,
    Where do you put all the code too ?
    And Will this also help to store post tags and categories under revision ?

    1. Hi Anne,

      This code is aimed at plugin developers. If you don’t know where to put it then it’s not for you.

      It won’t help with storing tags or categories on revisions. That’s another issue altogether :)

      John

  10. I’m having trouble implementing this for multiple metadata fields. I tried to loop over the meta fields, but am getting some unexpected results (notably all meta data associated with the post being deleted on save). I made the revisions below to try to accomplish this. Any feedback to help this work would be appreciated.

    changes to function _restore_revision():


    $meta = get_metadata( 'post', $revision->ID);

    if ( false === $meta ) {
    foreach ($meta as $key => $value) {
    delete_post_meta( $post_id, $key );
    }
    }
    else {
    foreach ($meta as $key => $value) { //loop over all metadata key-value pairs
    update_post_meta( $post_id, $key, $value );
    }
    }

    changes to function _save_post():

    $meta = get_post_meta( $parent->ID);

    if ( false !== $meta ) {
    foreach ($meta as $key => $value) {
    add_metadata( 'post', $post_id, $key, $value );
    }
    }

    1. You’ll need to check your logic there Mark. You’ll notice you’re doing:


      if ( false === $meta ) {
      foreach ($meta as $key => $value) {

      Obviously you can’t loop over a boolean false value. Try dumping the value from get_metadata() and see what you get and go from there.

  11. Hi John,

    This plugin seems to be just what I’m after. I’ve installed and activated it but I’m not sure what you mean by …

    “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.”

    Do I need to add a custom field called foo?

    Basically I’ve got a few different custom fields already, “actor_name”, “actor_age” and I’d love for them to be saved and displayed in revisions.

    Any tips please? Thank you for any help.

    David

    1. Looks like you spotted a typo in my post there David. I changed ‘foo’ to ‘my_meta’. You should change each instance of ‘my_meta’ to your plugin’s meta field name. If you have multiple post meta fields that you’d like to revision, then you’ll need to loop over them.

Comments are closed.