Overview

The API allows you to add custom menu items to tab and group context menus. You can also manipulate existing menu sections by removing or reordering them.

Tab menu customization

Register a callback to add custom items to tab context menus:

const ref = api.onTabMenu((menu, leaf) => {
  menu.addItem((item) => {
    item
      .setTitle("Custom Action")
      .setIcon("star")
      .setSection("my-plugin")
      .onClick(() => {
        console.log("Action for tab:", leaf.id);
      });
  });
});

The callback receives:

  • menu - The Menu instance being built
  • leaf - The WorkspaceLeaf the menu is for

Unregistering callbacks:

The callback returns a MenuEventRef that can be used to unregister:

// Register callback
const ref = api.onTabMenu((menu, leaf) => {
  // Add menu items
});
 
// Later: unregister callback
ref.unload();

Always unregister menu callbacks when your plugin unloads:

export default class MyPlugin extends Plugin {
  private tabMenuRef?: MenuEventRef;
 
  async onload() {
    const vtPlugin = this.app.plugins.getPlugin("vertical-tabs");
    if (vtPlugin?.api) {
      this.tabMenuRef = vtPlugin.api.onTabMenu((menu, leaf) => {
        // Add custom items
      });
    }
  }
 
  async onunload() {
    this.tabMenuRef?.unload();
  }
}

Group menu customization

Group menu customization works identically to tab menus:

const ref = api.onGroupMenu((menu, group) => {
  menu.addItem((item) => {
    item
      .setTitle("Custom Group Action")
      .setIcon("folder")
      .setSection("my-plugin")
      .onClick(() => {
        console.log("Action for group:", group.id);
      });
  });
});
 
// Later: unregister
ref.unload();

Menu items are organized into sections. Use setSection() to group your items:

api.onTabMenu((menu, leaf) => {
  menu.addItem((item) => {
    item
      .setTitle("Action 1")
      .setSection("my-plugin");
  });
  
  menu.addItem((item) => {
    item
      .setTitle("Action 2")
      .setSection("my-plugin");
  });
});

Items in the same section will be grouped together visually.

Manipulating existing sections

Removing sections

Remove all menu items in a section:

api.onTabMenu((menu, leaf) => {
  // Remove a section by name
  api.removeMenuSection(menu, "unwanted-section");
  
  // Add your own items
  menu.addItem((item) => {
    item.setTitle("My Action");
  });
});

Reordering sections

Move a section to a new position in the menu:

api.onTabMenu((menu, leaf) => {
  // Place "my-section" after "another-section"
  api.placeSectionAfter(menu, "my-section", "another-section");
  
  // Place section at the front (pass undefined for after)
  api.placeSectionAfter(menu, "my-section", undefined);
});

If the target section is not found, the operation does nothing.

Replacing built-in customization menu

Replace Vertical Tabs’ built-in customization menu items with your own:

api.onTabMenu((menu, leaf) => {
  // 1. Create your custom menu items with a section name
  menu.addItem((item) => {
    item
      .setTitle("Set Custom Icon")
      .setIcon("star")
      .setSection("my-customization")
      .onClick(async () => {
        await api.setTabIcon(leaf.id, "star", "my-plugin");
      });
  });
  
  menu.addItem((item) => {
    item
      .setTitle("Set Custom Color")
      .setIcon("palette")
      .setSection("my-customization")
      .onClick(async () => {
        await api.setTabColor(leaf.id, "#ff0000", "my-plugin");
      });
  });
  
  // 2. Place your section after "customization"
  api.placeSectionAfter(menu, "my-customization", "customization");
  
  // 3. Remove the built-in "customization" section
  api.removeMenuSection(menu, "customization");
});

This pattern works for both tab and group menus. The same approach applies to onGroupMenu() callbacks.

Common patterns

Conditional menu items

Add menu items based on context:

api.onTabMenu((menu, leaf) => {
  // Only add for markdown files
  const view = leaf.view;
  if (view.getViewType() === "markdown") {
    menu.addItem((item) => {
      item
        .setTitle("Markdown Action")
        .setSection("my-plugin")
        .onClick(() => {
          // Handle markdown-specific action
        });
    });
  }
});

Accessing tab metadata

Use the API to check current metadata:

api.onTabMenu(async (menu, leaf) => {
  const metadata = await api.getTabMetadata(leaf.id);
  
  menu.addItem((item) => {
    const hasIcon = !!metadata?.icon;
    item
      .setTitle(hasIcon ? "Remove Icon" : "Add Icon")
      .setSection("my-plugin")
      .onClick(async () => {
        if (hasIcon) {
          await api.clearTabMetadata(leaf.id, "my-plugin");
        } else {
          await api.setTabIcon(leaf.id, "star", "my-plugin");
        }
      });
  });
});

Create nested menu items:

api.onTabMenu((menu, leaf) => {
  menu.addItem((item) => {
    item
      .setTitle("Color")
      .setSection("my-plugin");
    
    const submenu = item.setSubmenu();
    
    submenu.addItem((subitem) => {
      subitem
        .setTitle("Red")
        .onClick(async () => {
          await api.setTabColor(leaf.id, "#ff0000", "my-plugin");
        });
    });
    
    submenu.addItem((subitem) => {
      subitem
        .setTitle("Blue")
        .onClick(async () => {
          await api.setTabColor(leaf.id, "#0000ff", "my-plugin");
        });
    });
  });
});