Hooks
Every default, limit and message has a filter, and every change fires an action.
axle_block_restore_post_typesFilter( string[] $post_types )Post types that get Axle Block Restore in the post editor and the abilities. Default every REST-enabled post type with revisions and the editor. Types without revisions are dropped
axle_block_restore_site_editor_post_typesFilter( string[] $post_types )Post types that get it in the Site Editor. Default wp_template, wp_template_part and wp_block. Types that do not exist, have no revisions or are not in the REST API are dropped
axle_block_restore_capabilityFilter( string $capability, string $post_type )Capability a user needs before the scripts load for a post type. Default the type’s own edit_posts capability: edit_theme_options for templates and parts, edit_posts for patterns and posts. An empty value falls back to the default
axle_block_restore_copy_post_typesFilter( string[] $post_types )Post types the Another post view copies from, and the search route allows. Default every post type the user can edit that has Axle Block Restore. Types the user cannot edit are dropped. An empty array turns the view off and makes the route answer 403
axle_block_restore_copy_max_resultsFilter( int $max )Most posts one search returns. Default 20
axleBlockRestore.canRestoreFilter( boolean canRestore, Object block )Whether a block’s menu and the "Restore selected block" command are offered for a block
axleBlockRestore.revisionsPerPageFilter( number perPage )Revisions loaded at a time, 1 to 100. Default 50
axleBlockRestore.removedRevisionsFilter( number count )How many of the newest revisions the Removed blocks view looks through, 1 to 100. Default 20
axleBlockRestore.removedSimilarityFilter( number similarity )Share of words, 0 to 1, a block of the same type now must have in common with an old block for it to count as edited, not removed. Default 0.6. 1 lists every block that is not identical
axleBlockRestore.removedBlocksFilter( Object[] entries, { revisionIds } )The blocks listed as removed, newest removal first. Each has name, title, text, path, block, revisionId, revisionIds and rewritten
axleBlockRestore.copyPostTypesFilter( string[] postTypes )Post types the Another post view offers. Can only narrow the PHP list. An empty array hides the view
axleBlockRestore.attributeRoleFilter( string role, string name, string blockName, Object schema )Whether an attribute is content, style or keep for a partial restore. Default from block.json role, the attribute’s source and the style list. Any other return value is ignored
axleBlockRestore.styleAttributesFilter( string[] names, string blockName )Attribute names that count as style. Default style, className, align, layout, backgroundColor, textColor, fontSize, fontFamily, gradient, borderColor
axleBlockRestore.restoreModesFilter( string[] modes )Ways to restore one block: replace, content, style. Default all three. replace is always kept, unknown names are dropped
axleBlockRestore.commandsFilter( Object[] commands, string|null blockId )Command palette entries, each { name, label, icon, callback }. Add, change or remove entries
axleBlockRestore.openedAction( { clientId, postType, postId, focusSearch, view } )The window opened. view is revisions or removed. clientId is null for the whole post, template, part or pattern. Template and part IDs look like theme//slug
axleBlockRestore.restoredAction( { mode, block, revisionId, postId, postType } )A block came back. mode is replace, content, style, insert-after, replace-at, insert-at, inner-replace, inner-insert, removed-place, removed-end, removed-below, other-post-below or other-post-end. For the last two the details also have sourcePostId and sourcePostType, and revisionId is a revision of that post or current
No hook matches that. Try a shorter word.
Abilities
What AI agents can do through the WordPress Abilities API and MCP, with the same permissions as the screen.
axle-block-restore/list-revisionsRead-only. Revisions of post_id, newest first, with who saved each
axle-block-restore/find-deletedRead-only. Blocks whose text is in an old revision but not in the post, with the revision_id and path to restore them
axle-block-restore/find-textRead-only. Blocks in old revisions containing text, and whether each is still in the post
axle-block-restore/restore-blockPuts the block at path in revision_id back: mode insert at a top-level position (default: where it was), or replace the block at target_path (default: the same path). Saves the post, which makes a new revision, so it can be undone. Refuses while someone else has the post open in the editor, unless force is true
Built from REFERENCE.md for version 3.1.0.
Scripts
There is no build step. The scripts use the wp.* globals WordPress already loads. They load in the post editor for post types with revisions, and in the Site Editor for templates, template parts, synced patterns and anything else that has revisions.
| Script | Job |
|---|---|
assets/js/matching.js | Block text, similarity ranking, classification, word diff, revision search, attribute roles, partial restores, the inner block comparison, and the removed blocks list with the position a block goes back to. No WordPress dependency, so it is unit tested in Node |
assets/js/components.js | The restore window, built on wp.components and wp.blockEditor, and the lookup of which entity a block belongs to |
assets/js/index.js | The two menu entries (BlockSettingsMenuControls and PluginMoreMenuItem) and the command palette entries |
The window uses core components only (Modal, Card, PanelBody, Button, Notice, Spinner, Badge). assets/editor.css holds layout and nothing else.
More on hooks
// No restoring for reusable blocks, and log every restore.
wp.hooks.addFilter( 'axleBlockRestore.canRestore', 'my-plugin', ( canRestore, block ) => canRestore && 'core/block' !== block.name );
wp.hooks.addAction( 'axleBlockRestore.restored', 'my-plugin', ( details ) => console.log( 'Restored', details.block.name, 'from revision', details.revisionId ) );
// Treat a custom block's "heading" attribute as content, and a "variant" attribute as style.
wp.hooks.addFilter( 'axleBlockRestore.attributeRole', 'my-plugin', ( role, name, blockName ) => {
if ( 'my/card' !== blockName ) {
return role;
}
return 'heading' === name ? 'content' : 'variant' === name ? 'style' : role;
} );
// Templates and parts only for people who can manage options, and no support for patterns in the Site Editor.
add_filter( 'axle_block_restore_site_editor_post_types', static fn( array $types ) => array_diff( $types, array( 'wp_block' ) ) );
add_filter( 'axle_block_restore_capability', static fn( string $cap, string $type ) => in_array( $type, array( 'wp_template', 'wp_template_part' ), true ) ? 'manage_options' : $cap, 10, 2 );
Load that script with wp-hooks as a dependency, on enqueue_block_editor_assets.
More on abilities
On WordPress 6.9 and later, Axle Block Restore registers abilities, which AI agents find and run through the MCP adapter and anything else through the REST API under /wp-abilities/v1/. Each one is marked public, which the MCP adapter needs before it lists an ability.
Abilities work with post IDs, so they reach templates, template parts and patterns by the ID of their post. Restoring only the content or only the styling is an editor feature: the saved markup of a block (its classes and inline styles) only comes from the block’s own save() function, which runs in JavaScript.
In the editor the plugin guesses which old block matches which current one. An agent does not need the guess: it finds text that was deleted or changed, then names the exact block to bring back. A path counts blocks from 0, one number per level: [2] is the third top-level block, [2, 0] the first block inside it.
All four need edit_post on the post, in a post type that has Axle Block Restore. The find abilities look through 20 revisions by default, 50 at most (axle_block_restore_max_revisions).
| Hook | Type | Use |
|---|---|---|
axle_block_restore_restored | action | ( int $post_id, array $block, int $revision_id, string $mode ). A block was saved back through restore-block |
axle_block_restore_max_revisions | filter | ( int $max ). Most revisions the find abilities look through. Default 50 |
REST
Revisions are fetched 50 at a time from /wp/v2/<rest base>/<id>/revisions with context=edit and _fields=id,wp_id,modified,date_gmt,slug,content.raw,axle_block_restore_author, so the server returns raw block markup and never renders the content. The rest base is templates, template-parts or blocks in the Site Editor.
Template and template part revisions come from core’s templates controller, so each one has id set to the template’s ID (theme//slug), its own ID in wp_id, and its time in modified, in the site’s time zone. The window reads those.
The plugin adds one read-only route, /axle-block-restore/v1/posts (see Copying a block from another post), and one read-only field to revision responses:
| Field | Value |
|---|---|
axle_block_restore_author | Display name of the revision’s author. Editors cannot always list users, so the name comes with the revision |
Data
Nothing is stored. Restores are normal editor changes, saved when the post is updated.