MW
Tutorial

Extending the Editor

Use JavaScript hooks (editor.BlockEdit, blocks.registerBlockType, blocks.getSaveContent.extraProps) to extend core blocks without forking them.

advanced 40 min May 13, 2026
Comfort with block development from earlier parts

EXTENDING THE EDITOR

So far we’ve built our own block. This tutorial shows how to extend other people’s blocks — particularly core blocks — using WordPress’s JS hook system. This is how block-toolbar plugins, format-bar additions, and custom inserter filters work.

The hook system

@wordpress/hooks is the JS equivalent of WP’s PHP hook system. Two flavors:

  • addFilter() — modify a value passing through
  • addAction() — react to something happening

The hooks fire at well-defined points in the block lifecycle. Pick the right one for the modification you want.

Hook 1: blocks.registerBlockType — modify block settings

Fired when WordPress registers a block. Modify the settings object to add attributes, change icons, alter supports:

import { addFilter } from '@wordpress/hooks';

addFilter(
  'blocks.registerBlockType',
  'my-first-block/add-lock-attribute',
  (settings, name) => {
    if (name !== 'core/paragraph') return settings;
    return {
      ...settings,
      attributes: {
        ...settings.attributes,
        isLocked: { type: 'boolean', default: false },
      },
    };
  }
);

Now every paragraph has an isLocked attribute. Setting and using it is the next step.

Hook 2: editor.BlockEdit — wrap the edit component

Wrap a block’s Edit component with a higher-order component to add inspector controls, toolbar buttons, or wrappers:

import { addFilter } from '@wordpress/hooks';
import { createHigherOrderComponent } from '@wordpress/compose';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';

const withLockToggle = createHigherOrderComponent((BlockEdit) => {
  return (props) => {
    if (props.name !== 'core/paragraph') return <BlockEdit {...props} />;

    return (
      <>
        <BlockEdit {...props} />
        <InspectorControls>
          <PanelBody title="Custom" initialOpen={false}>
            <ToggleControl
              label="Locked"
              checked={props.attributes.isLocked}
              onChange={(value) => props.setAttributes({ isLocked: value })}
            />
          </PanelBody>
        </InspectorControls>
      </>
    );
  };
}, 'withLockToggle');

addFilter('editor.BlockEdit', 'my-first-block/with-lock-toggle', withLockToggle);

Now every paragraph has a “Custom” inspector panel with a “Locked” toggle.

Hook 3: blocks.getSaveContent.extraProps — modify saved markup

Add HTML attributes to the saved output without rewriting Save:

addFilter(
  'blocks.getSaveContent.extraProps',
  'my-first-block/locked-class',
  (props, blockType, attributes) => {
    if (blockType.name !== 'core/paragraph') return props;
    if (!attributes.isLocked) return props;
    return {
      ...props,
      className: `${props.className || ''} is-locked`,
      'data-locked': 'true',
    };
  }
);

Now paragraphs with isLocked: true get class="is-locked" and data-locked="true" in the saved HTML.

SlotFill — adding to existing slots

Some Gutenberg UI areas (the document sidebar, the publish flow) are SlotFill points. Plugins can add their own panels:

import { registerPlugin } from '@wordpress/plugins';
import { PluginDocumentSettingPanel } from '@wordpress/editor';
import { TextControl } from '@wordpress/components';

const MyDocumentPanel = () => (
  <PluginDocumentSettingPanel
    name="my-plugin-panel"
    title="My Plugin Settings"
    icon="admin-plugins"
  >
    <TextControl label="External tracking ID" value="" onChange={() => {}} />
  </PluginDocumentSettingPanel>
);

registerPlugin('my-first-block-document-panel', { render: MyDocumentPanel });

This adds a custom panel to the document sidebar in any post-edit screen.

When to extend vs fork

SituationApproach
Add a small option to a core blockFilter (this tutorial)
Need a totally different lookBuild your own custom block
Change behavior for ALL blocksFilter editor.BlockEdit with no type check
Replace a core block entirelyUse the block-deregister + replace pattern

What’s next

Part 10 packages everything into a polished plugin — proper file structure, asset enqueuing, server-side render callbacks, plugin metadata.

You've completed this tutorial!

Get the next one in your inbox. Practical tips, no fluff.

Subscribe

Get weekly notes in your inbox

Practical tips, tutorials and resources. No spam.