WordPress 6.4+ provides a complete set of tools for managing content contributor workflows: block locking (prevent specific blocks from being moved or deleted), a curated synced-pattern library (reusable components that only editors can modify), and post_status-based editorial stages. Combining these three features creates a structured content workflow without a third-party editorial plugin.
Problem: A WordPress editorial team working in the block editor accidentally overwrites each other's content — there is no block-level locking mechanism, and the patterns library and template library are not coordinated across the team.
Solution: Use Block Locking (introduced in WordPress 5.9) — set "lock": {"move": true, "remove": true} in block metadata or via the block editor's lock controls to prevent non-admin users from moving or deleting specific blocks. Manage shared content through the Patterns library (wp_block post type) and restrict pattern editing with the edit_posts capability check on the patterns REST endpoint.
The code below locks blocks programmatically on the server side, registers a custom post status for an editorial review stage, restricts pattern editing to editors, and adds a custom workflow status to the block editor toolbar via a JavaScript plugin.
_x( 'In Review', 'post status', 'my-plugin' ),
'public' => false,
'exclude_from_search' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
'label_count' => _n_noop( 'In Review (%s)',
'In Review (%s)', 'my-plugin' ),
] );
} );
// 2. Restrict who can edit synced patterns (reusable blocks)
add_filter( 'block_editor_settings_all', function ( array $settings, WP_Block_Editor_Context $ctx ): array {
if ( ! current_user_can( 'edit_others_posts' ) ) {
// Hide synced pattern editing for contributors/authors
$settings['__experimentalBlockPatternCategories'] = array_filter(
$settings['__experimentalBlockPatternCategories'] ?? [],
fn( $cat ) => $cat['name'] !== 'reusable'
);
}
return $settings;
}, 10, 2 );
// 3. Lock blocks in a template via filter (programmatic lock)
add_filter( 'render_block_data', function ( array $block ): array {
// Lock all core/buttons blocks on the homepage from being removed
if ( is_front_page() && $block['blockName'] === 'core/buttons' ) {
$block['attrs']['lock'] = [
'move' => true,
'remove' => true,
];
}
return $block;
} );
// 4. Add "In Review" status to post status dropdown in editor
add_action( 'enqueue_block_editor_assets', function () {
wp_enqueue_script(
'my-plugin-review-status',
plugin_dir_url( __FILE__ ) . 'js/review-status.js',
[ 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n' ],
'1.0.0',
true
);
} );
// js/review-status.js — add "In Review" to the block editor status selector
const { registerPlugin } = wp.plugins;
const { PluginPostStatusInfo } = wp.editPost;
const { SelectControl } = wp.components;
const { withSelect, withDispatch, compose } = wp.data;
const { __ } = wp.i18n;
const ReviewStatusPanel = compose(
withSelect( select => ( {
status: select( 'core/editor' ).getEditedPostAttribute( 'status' ),
} ) ),
withDispatch( dispatch => ( {
onStatusChange: status => dispatch( 'core/editor' ).editPost( { status } ),
} ) )
)( ( { status, onStatusChange } ) =>
wp.element.createElement( PluginPostStatusInfo, null,
wp.element.createElement( SelectControl, {
label: __( 'Review Stage', 'my-plugin' ),
value: status,
options: [
{ label: __( 'Draft', 'my-plugin' ), value: 'draft' },
{ label: __( 'In Review', 'my-plugin' ), value: 'in-review' },
{ label: __( 'Published', 'my-plugin' ), value: 'publish' },
],
onChange: onStatusChange,
} )
)
);
registerPlugin( 'my-plugin-review-status', { render: ReviewStatusPanel } );
NOTE: Block locking set via render_block_data applies only at render time and does not persist in the post content — users with block editor access can still remove the lock by editing the block attributes directly in the code editor view; for content that must truly be immutable, use a server-side rendered dynamic block with no editable attributes instead of locking a static block.