Skip to content

Patterns: Restoring revisions is broken #84809

Open

Description

@talldan

Description

From my testing, restoring a revision of a pattern is broken. Doing so throws an error (The wp_pattern_sync_status property has an invalid stored value, and cannot be updated to null.):

Image

I reproduced the error as far back as WordPress 7.0, which would align with when visual revisions were introduced. My guess is the visual revisions aren't restoring post meta, so it might be broader than just pattern post types.

Step-by-step reproduction instructions

  1. Create a post
  2. Add some blocks and select them
  3. Using the block settings menu, convert the blocks to a synced pattern
  4. From the synced pattern toolbar, click Edit original
  5. Make several edits, saving each time to create revisions
  6. From the sidebar in the Pattern tab, click the revisions number to open the revisions UI
  7. Try restoring an old revision
  8. Observe the error

Screenshots, screen recording, code snippet

No response

Environment info

No response

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure

Activity

  1. added
    [Type] BugAn existing feature does not function as intended
    [Feature] PatternsA collection of blocks that can be synced (previously reusable blocks) or unsynced
    on Oct 15, 2026
  2. umeshnevase commented on Oct 15, 2026

    @umeshnevase

    @talldan
    I attempted to replicate the issue on WordPress 7.0 and 7.1.3 but was unable to reproduce it.

  3. prasadkarmalkar commented on Oct 15, 2026

    @prasadkarmalkar
    Contributor

    I am able to replicate the issue locally with the latest Gutenberg. I also noticed that restoring an unsynced pattern converts it into a synced pattern. I will debug more on this.

    Image
  4. davidsword commented on Oct 15, 2026

    @davidsword
    Contributor

    This is broader than patterns, and it's the opposite of not restoring meta: Visual Revisions restores all of the revision's meta, including keys that aren't revisioned.

    restoreRevision fetches the revision's meta and copies the whole object onto the post before saving:

    const revision = await registry
    .resolveSelect( coreStore )
    .getRevision( 'postType', postType, postId, revisionId, {
    context: 'edit',
    _fields: [
    ...new Set( [
    'id',
    'date',
    'modified',
    'author',
    'meta',
    'title',
    'excerpt',
    'content.raw',
    revisionKey,
    ] ),
    ].join(),
    } );
    if ( ! revision ) {
    return;
    }
    // Build the edits object with all restorable fields from the revision.
    const edits = {
    blocks: undefined,
    content: revision.content.raw,
    };
    if ( revision.title?.raw !== undefined ) {
    edits.title = revision.title.raw;
    }
    if ( revision.excerpt?.raw !== undefined ) {
    edits.excerpt = revision.excerpt.raw;
    }
    if ( revision.meta !== undefined ) {
    edits.meta = revision.meta;
    }
    // Apply edits and save.
    dispatch.editPost( edits );
    // Exit revisions mode.
    dispatch.setCurrentRevisionId( null );
    // Save the post to persist the restored revision.
    await dispatch.savePost();

    The revisions endpoint returns every show_in_rest key registered for the parent post type, read from the revision ID:

    https://github.com/WordPress/wordpress-develop/blob/2995d9665a205841390f6c5adc6419ac888428fb/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php#L715-L716

    A key without revisions_enabled is never stored on the revision, so it comes back as its schema default, or null if that default fails the schema:

    https://github.com/WordPress/wordpress-develop/blob/2995d9665a205841390f6c5adc6419ac888428fb/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php#L86-L93
    https://github.com/WordPress/wordpress-develop/blob/2995d9665a205841390f6c5adc6419ac888428fb/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php#L571-L573

    For patterns, wp_pattern_sync_status isn't revisioned and only allows partial or unsynced, so the revision returns null. Saving null deletes the key, which is why an unsynced pattern becomes synced. A synced pattern has no stored value, which also fails the enum, so the update errors with "cannot be updated to null":

    https://github.com/WordPress/wordpress-develop/blob/2995d9665a205841390f6c5adc6419ac888428fb/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php#L160-L182

    On other post types it's silent: restoring resets every non-revisioned show_in_rest key. Strings are saved as '', keys with a registered default get that default, and non-single keys are deleted. Repro on WordPress 7.1.3 with no plugins:

    1. Register a non-revisioned key:
      add_action( 'init', function () {
          register_post_meta( 'post', 'kept_meta', array(
              'show_in_rest' => true,
              'single'       => true,
              'type'         => 'string',
          ) );
      } );
    2. Create a post with 2 revisions, then wp post meta update <ID> kept_meta hello.
    3. With Preferences > Custom fields off, open Revisions, move to the earlier revision, and click Restore.
    4. The save sends "meta":{"kept_meta":"","footnotes":""}, and kept_meta is now empty.

    Core's classic restore only touches wp_post_revision_meta_keys():

    https://github.com/WordPress/wordpress-develop/blob/2995d9665a205841390f6c5adc6419ac888428fb/src/wp-includes/revision.php#L534-L548

    The editor can't tell which keys are revisioned, since revisions_enabled isn't exposed over REST. So the fix likely needs the server: the revisions endpoint returning only wp_post_revision_meta_keys() in meta, or restoring on the server as #82158 did.

  5. prasadkarmalkar commented on Oct 16, 2026

    @prasadkarmalkar
    Contributor

    Thanks @davidsword for the detailed analysis!
    I was looking at an approach that skips null values from the revision's meta in restoreRevision.
    It fixes both pattern cases (the error on synced patterns and unsynced patterns becoming synced). But as you've shown, the root cause is broader than a null check: non-revisioned keys that come back as '', as their default, or as [] would still be overwritten, so it probably needs a server-side fix instead.

  6. talldan commented on Oct 19, 2026

    @talldan
    ContributorAuthor

    Thanks for the testing and diagnosis of the issue.

    I've made a trac ticket (https://core.trac.wordpress.org/ticket/66336) as well for this, which will be needed for the current fix - WordPress/wordpress-develop#14359. (edit: there was an existing trac ticket https://core.trac.wordpress.org/ticket/66329)

  7. annezazu commented on Oct 20, 2026

    @annezazu
    Contributor

    @ellatrix tagging you in this for awareness and in case you can dig in.

  8. adamsilverstein commented on Oct 20, 2026

    @adamsilverstein
    Member

    The analysis from @davidsword seems right - only the revisioned meta fields should be restored.

    Any empty (revisioned) meta value is valid, so those get restored as well, potentially clearing the field.

    The functionality of restoring a revision should match wp_restore_post_revision_meta from core as closely as possible (exactly).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Feature] HistoryHistory, undo, redo, revisions, autosave.[Feature] PatternsA collection of blocks that can be synced (previously reusable blocks) or unsynced[Type] BugAn existing feature does not function as intended

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions