By default, there is no API for adding a new button to the main toolbar of the block editor. So if you want to add a button there, you have to do it manually. This means, in React, you need to subscribe for changes and inject your desired content where you want.

So first of all, you need to subscribe to any change in the current React state. This means, whenever something changes, your function is executed:

const unsubscribe = subscribe( () => {
	// do stuff
} );
Code language: JavaScript (javascript)

The unsubscribe constant is returned as function of subscribe and later used to stop subscribing.

Since we want to add a button to the toolbar, we want to add it in the right place when the DOM is fully loaded:

const unsubscribe = subscribe( () => {
	domReady( () => {
		const editorToolbar = document.querySelector( '.edit-post-header__toolbar .edit-post-header-toolbar__left' );
		
		if ( ! editorToolbar ) {
			return;
		}
		
		// create wrapper to fill with the button
		const buttonWrapper = document.createElement( 'div' );
		
		buttonWrapper.id = 'my-panel-button-wrapper';
		buttonWrapper.classList.add( 'my-panel-button-wrapper' );
		editorToolbar.appendChild( buttonWrapper );
		
		render(
			<button>My Button</button>,
			document.getElementById( 'my-panel-button-wrapper' )
		);
	} );
} );
Code language: JavaScript (javascript)

Here we check first if the left part of the editor toolbar (with selector .edit-post-header__toolbar .edit-post-header-toolbar__left) is already available and return early if it’s not.

Then, we create a wrapper element and append it to the editor toolbar, so that our real content afterwards can be placed inside the container. We use this method since otherwise we cannot add a React component to the editor toolbar without using dangerouslySetInnerHTML.

After that, we render a custom button inside the element we created.

Since the subscribe function will be called every time something’s changing, we now also need to check if the button wrapper already exist. Otherwise we would add an indefinitely amount of buttons to the editor. So before the domReady we add a check whether the wrapper already exists and then unsubscribe and return early:

const unsubscribe = subscribe( () => {
	const wrapper = document.getElementById( 'my-panel-button-wrapper' );
	
	if ( wrapper ) {
		unsubscribe;
		
		return;
	}
	
	domReady( () => {
		const editorToolbar = document.querySelector( '.edit-post-header__toolbar .edit-post-header-toolbar__left' );
		
		if ( ! editorToolbar ) {
			return;
		}
		
		// create wrapper to fill with the button
		const buttonWrapper = document.createElement( 'div' );
		
		buttonWrapper.id = 'my-panel-button-wrapper';
		buttonWrapper.classList.add( 'my-panel-button-wrapper' );
		editorToolbar.appendChild( buttonWrapper );
		
		render(
			<button>My Button</button>,
			document.getElementById( 'my-panel-button-wrapper' )
		);
	} );
} );
Code language: JavaScript (javascript)

Since a button alone is not useful, here’s an example to have a real button element with a download icon and a drop-down that will be displayed on clicking the icon:

import { Button, Dropdown, Tooltip } from '@wordpress/components';
import { subscribe } from '@wordpress/data';
import domReady from '@wordpress/dom-ready';
import { render } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { download } from '@wordpress/icons';

const MyContent = () => (
	<Dropdown
		renderToggle={ ( { isOpen, onToggle } ) => (
			<Tooltip
				text={ __( 'Tooltip notice', 'my-textdomain' ) }
			>
				<Button
					aria-expanded={ isOpen }
					icon={ download }
					onClick={ onToggle }
				/>
			</Tooltip>
		) }
		renderContent={ () => <div>{ __( 'My content', 'my-textdomain' ) }</div> }
	/>
);

// subscribe to add custom button to toolbar
const unsubscribe = subscribe( () => {
	const wrapper = document.getElementById( 'my-panel-button-wrapper' );
	
	if ( wrapper ) {
		unsubscribe;
		
		return;
	}
	
	domReady( () => {
		const editorToolbar = document.querySelector( '.edit-post-header__toolbar .edit-post-header-toolbar__left' );
		
		if ( ! editorToolbar ) {
			return;
		}
		
		// create wrapper to fill with the button
		const buttonWrapper = document.createElement( 'div' );
		
		buttonWrapper.id = 'my-panel-button-wrapper';
		buttonWrapper.classList.add( 'my-panel-button-wrapper' );
		editorToolbar.appendChild( buttonWrapper );
		
		render(
			<MyContent />,
			document.getElementById( 'my-panel-button-wrapper' )
		);
	} );
} );
Code language: JavaScript (javascript)

In the end, I want to thank Aurooba Ahmed for the code inspiration inside her QuickPost plugin.

Leave a Reply

Your email address will not be published. Required fields are marked *