---
title: "Creating custom Menu toolbar buttons"
description: "Creating custom Menu toolbar buttons for TinyMCE"
canonical_url: "https://www.tiny.cloud/docs/tinymce/latest/custom-menu-toolbar-button/"
md_url: "https://www.tiny.cloud/docs/tinymce/latest/custom-menu-toolbar-button/index.md"
version: "latest"
last_updated: "2026-09-24T22:40:35Z"
tokens: 2835
---
# Creating custom Menu toolbar buttons

A toolbar menu button is a toolbar button that opens a menu when clicked. This menu can also contain submenus. This is useful for grouping together actions that would otherwise be several buttons on the toolbar. It can also be used to reduce visual clutter and save UI space, as menubar menu items and some toolbar buttons could be moved into a toolbar menu button. Potentially, all menubar menu items could be moved into toolbar menu buttons, allowing for the editor to be used without a menubar at all.

For example: The table plugin’s `table` toolbar button opens a menu similar to the menubar Table menu.

## Options

| Name | Value | Requirement | Description |
| --- | --- | --- | --- |
| fetch | `(success: (menu) => void, fetchContext) => void` | required | Function that takes a callback and a `fetchContext` object. The callback function must be passed the list of options for the button’s dropdown. The `fetchContext` object provides information which is useful for choosing which items should be passed to the callback. For more details on `fetchContext`, see [Searchable Menu Buttons](#searchable-menu-buttons) |
| text | string | optional | Text to display if no icon is found. |
| icon | string | optional | Name of the icon to be displayed. Must correspond to an icon: in the [icon pack](../editor-icon-identifiers/), in a [custom icon pack](../creating-an-icon-pack/), or added using the [`addIcon` API](../apis/tinymce.editor.ui.registry/#addIcon). |
| search | boolean or object | optional | If not false, adds a search field to the menu. For more details, see [Searchable Menu Buttons](#searchable-menu-buttons) |
| tooltip | string | optional | Text for button tooltip. |
| onSetup | `(api) => (api) => void` | optional | default: `() => () => {}` - Function that’s invoked when the button is rendered. For details, see: [Using `onSetup`](#using-onsetup). |
| context | string | optional | default: `mode:design` - The context property dynamically enables or disables the button based on the editor’s current state. For details, see: [Context](../context/). |

## API

| Name | Value | Description |
| --- | --- | --- |
| isEnabled | `() => boolean` | Checks if the button is enabled. |
| setEnabled | `(state: boolean) => void` | Sets the button’s enabled state. |
| setText | `(text: string) => void` | Sets the text label to display. |
| setIcon | `(icon: string) => void` | Sets the icon of the button. |

## Menu button example and explanation

The following is a simple toolbar menu button example:

**Example**

```js
tinymce.init({
  selector: 'textarea#custom-toolbar-menu-button',
  height: 500,
  toolbar: 'mybutton',

  setup: (editor) => {
    /* Menu items are recreated when the menu is closed and opened, so we need
       a variable to store the toggle menu item state. */
    let toggleState = false;

    /* example, adding a toolbar menu button */
    editor.ui.registry.addMenuButton('mybutton', {
      text: 'My button',
      fetch: (callback) => {
        const items = [
          {
            type: 'menuitem',
            text: 'Menu item 1',
            onAction: () => editor.insertContent('&nbsp;<em>You clicked menu item 1!</em>')
          },
          {
            type: 'nestedmenuitem',
            text: 'Menu item 2',
            icon: 'user',
            getSubmenuItems: () => [
              {
                type: 'menuitem',
                text: 'Sub menu item 1',
                icon: 'unlock',
                onAction: () => editor.insertContent('&nbsp;<em>You clicked Sub menu item 1!</em>')
              },
              {
                type: 'menuitem',
                text: 'Sub menu item 2',
                icon: 'lock',
                onAction: () => editor.insertContent('&nbsp;<em>You clicked Sub menu item 2!</em>')
              }
            ]
          },
          {
            type: 'togglemenuitem',
            text: 'Toggle menu item',
            onAction: () => {
              toggleState = !toggleState;
              editor.insertContent('&nbsp;<em>You toggled a menuitem ' + (toggleState ? 'on' : 'off') + '</em>');
            },
            onSetup: (api) => {
              api.setActive(toggleState);
              return () => {};
            }
          }
        ];
        callback(items);
      }
    });

  },
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }'
});
```
This example configures a toolbar menu button with the label `My Button` that opens the specified menu when clicked. The top-level menu contains two items. The first menu item inserts content when clicked and the second menu item opens a submenu containing two menu items which insert content when clicked.

The `fetch` function is called when the toolbar menu button’s menu is opened. It is a function that takes a callback and passes it an array of menu items to be rendered in the drop-down menu. This allows for asynchronous fetching of the menu items.

## Searchable menu buttons

The button’s menu can be configured to have an input field for searching, as well as its usual items. The presence of the input field is controlled by the `search` field in the [options](#options). The `search` field can be a `boolean` or an `object` containing a single optional string `placeholder`. By default, `search` is false. If `search` is not false, the button’s menu will contain an input field with any specified placeholder text. As the user types into this field, the `fetch` function will be called with the text in the input field passed back as part of `fetchContext`. The `fetch` function is responsible for using this `fetchContext` to determine which items to pass to its `success` callback.

The `fetchContext` is an object containing a single string property: `pattern`. If the toolbar menu button has not configured `search` to be active, then the `pattern` string will be empty.

## Searchable menu button example and explanation

The following is a simple toolbar menu button example, where searching has been configured:

**Example**

```js
tinymce.init({
  selector: 'textarea#custom-toolbar-menu-button-search',
  height: 500,
  toolbar: 'mybutton',

  setup: (editor) => {
    /* Menu items are recreated when the menu is closed and opened, so we need
       a variable to store the toggle menu item state. */
    let toggleState = false;

    /* example, adding a toolbar menu button */
    editor.ui.registry.addMenuButton('mybutton', {
      text: 'My searchable button',
      search: {
        placeholder: 'Type...'
      },
      fetch: (callback, fetchContext) => {
        if (fetchContext.pattern.length > 0) {
          callback([
            {
              type: 'menuitem',
              text: `You searched for: "${fetchContext.pattern}"`,
              onAction: () => editor.insertContent(`<strong>Inserted selected search result</strong>`)
            }
          ]);
        } else {
          const items = [
            {
              type: 'menuitem',
              text: 'Menu item 1',
              onAction: () => editor.insertContent('&nbsp;<em>You clicked menu item 1!</em>')
            },
            {
              type: 'togglemenuitem',
              text: 'Toggle menu item',
              onAction: () => {
                toggleState = !toggleState;
                editor.insertContent('&nbsp;<em>You toggled a menuitem ' + (toggleState ? 'on' : 'off') + '</em>');
              },
              onSetup: (api) => {
                api.setActive(toggleState);
                return () => {};
              }
            }
          ];
          callback(items);
        }
      }
    });

  },
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }'
});
```
This example configures a toolbar menu button with the label `My searchable button` that opens the specified menu when clicked. The menu will contain a search input field because `search` is not `false`. The input field’s [placeholder attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#placeholder) will be `Type...`.

Initially, when the menu opens, the search input field will be empty, and the `fetch` function is called with an empty `pattern` for its `fetchContext`. In that situation, `fetch` passes back an array of two items to be rendered in the drop-down menu. When the user types in the input field, `fetch` will be called again, except this time, the `pattern` property in `fetchContext` will reflect the value typed in the input field. For illustration purposes, this example then passes back an item that contains this pattern inside the item’s text. In a more real-life example, the `pattern` could be used to filter which of the items are passed to the callback.

## Adding a table picker to a custom menu

The built-in table grid picker can be added to a custom menu button using a `fancymenuitem` with `fancytype: 'inserttable'`. This displays the same interactive grid that appears in the Table plugin’s menu when [`table_grid`](../table/#table_grid) is enabled.

The `onAction` callback receives an object with `numRows` and `numColumns` properties. Pass these values to the `mceInsertTable` command to insert the table.

> **Note:** The table picker requires the `table` plugin.
**Example**

```js
tinymce.init({
  selector: 'textarea#custom-table-picker-menu-button',
  height: 600,
  plugins: 'table',
  toolbar: 'inserttable',
  menubar: false,

  setup: (editor) => {
    editor.ui.registry.addMenuButton('inserttable', {
      icon: 'table',
      tooltip: 'Insert table',
      fetch: (callback) => {
        callback([
          {
            type: 'fancymenuitem',
            fancytype: 'inserttable',
            onAction: (data) => {
              editor.execCommand('mceInsertTable', false, {
                rows: data.numRows,
                columns: data.numColumns,
                options: { headerRows: 1 }
              });
            }
          }
        ]);
      }
    });
  }
});
```

### Example: adding a table picker menu button

```js
tinymce.init({
  selector: 'textarea',
  plugins: 'table',
  toolbar: 'inserttable',
  menubar: false,

  setup: (editor) => {
    editor.ui.registry.addMenuButton('inserttable', {
      icon: 'table',
      tooltip: 'Insert table',
      fetch: (callback) => {
        callback([
          {
            type: 'fancymenuitem',
            fancytype: 'inserttable',
            onAction: (data) => {
              editor.execCommand('mceInsertTable', false, {
                rows: data.numRows,
                columns: data.numColumns,
                options: { headerRows: 1 }
              });
            }
          }
        ]);
      }
    });
  }
});
```

## Using `onSetup`

`onSetup` accepts a function that receives the component’s API. This function should return a callback that returns nothing after being passed the component’s API. This occurs because `onSetup` runs whenever the component is rendered, and the callback returned by `onSetup` is executed when the component is destroyed. The function returned from `onSetup` is essentially an `onTeardown` handler, and can be used to unbind events and callbacks.

To clarify, in code `onSetup` may look like this:

```js
onSetup: (api) => {
  // Runs when the component is created
  // Configure the component or bind event listeners

  return (api) => {
    // Runs when the component is destroyed
    // Unbind event listeners or clean up resources
  };
};
```
To bind a callback function to an editor event use [`editor.on(eventName, callback)`](../apis/tinymce.editor/#on). To unbind an event listener use [`editor.off(eventName, callback)`](../apis/tinymce.editor/#off). Any event listeners *should* be unbound in the teardown callback. The only editor event which does not need to be unbound is `init` e.g. `editor.on('init', callback)`.

> **Note:**
> - The callback function passed to `editor.off()` should be the same function passed to `editor.on()`. For example, if an `editorEventCallback` function is bound to the `NodeChange` event when the button is created, `onSetup` should return `(api) => editor.off('NodeChange', editorEventCallback)`.
> - If `onSetup` does not register any event listeners or only listens to the `init` event, `onSetup` can return an empty function e.g. `return () => {};`.
