Context Menus

Context menus (right-click menus) appear throughout Visual Studio. Your extension can add commands to existing context menus or create custom ones.

Quick Start with Community Toolkit

Add a command to Solution Explorer’s context menu:

[Command(PackageIds.MyCommand)]
internal sealed class MyCommand : BaseCommand<MyCommand>
{
    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        await VS.MessageBox.ShowAsync("Command executed!");
    }
}

In your .vsct file:

<Button guid="guidMyPackageCmdSet" id="MyCommand" priority="0x0100" type="Button">
  <Parent guid="guidSHLMainMenu" id="IDG_VS_CTXT_SOLUTION_BUILD"/>
  <Strings>
    <ButtonText>My Command</ButtonText>
  </Strings>
</Button>

Context Menu GUIDs

Solution Explorer

MenuGUIDGroup IDDescription
Solution nodeguidSHLMainMenuIDG_VS_CTXT_SOLUTION_BUILDSolution context menu
Project nodeguidSHLMainMenuIDG_VS_CTXT_PROJECT_BUILDProject context menu
Folder nodeguidSHLMainMenuIDG_VS_CTXT_FOLDER_ADDFolder context menu
Item/File nodeguidSHLMainMenuIDG_VS_CTXT_ITEM_OPENFile context menu
References nodeguidSHLMainMenuIDG_VS_CTXT_REFROOT_ADDReferences context menu
Reference itemguidSHLMainMenuIDG_VS_CTXT_REFERENCESingle reference

Code Editor

MenuGUIDGroup IDDescription
Editor contextguidSHLMainMenuIDG_VS_CODEWIN_NAVIGATETOLOCATIONCode editor right-click
Editor marginguidSHLMainMenuIDG_VS_EDITOR_OUTLINING_CMDSLeft margin

Tool Windows

MenuGUID/GroupDescription
Error ListCMDSETID_StandardCommandSet2K / IDG_VS_ERRORLIST_ERRORGROUPError List items
Output WindowguidSHLMainMenu / IDG_VS_OUTWINDOW_OUTPUTCMDSOutput window
Task ListguidSHLMainMenu / IDG_VS_TASKLIST_CLIENTGROUPTask List items

Adding Commands to Context Menus

VSCT Structure

<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable">

  <Extern href="stdidcmd.h"/>
  <Extern href="vsshlids.h"/>

  <Commands package="guidMyPackage">

    <!-- Define a group to hold your commands -->
    <Groups>
      <Group guid="guidMyPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <!-- Parent this group to Solution Explorer's solution context menu -->
        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/>
      </Group>
    </Groups>

    <Buttons>
      <Button guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100" type="Button">
        <Parent guid="guidMyPackageCmdSet" id="MyMenuGroup"/>
        <Icon guid="guidImages" id="myIcon"/>
        <Strings>
          <ButtonText>My Context Command</ButtonText>
        </Strings>
      </Button>
    </Buttons>

  </Commands>

  <Symbols>
    <GuidSymbol name="guidMyPackage" value="{your-package-guid}"/>
    <GuidSymbol name="guidMyPackageCmdSet" value="{your-cmdset-guid}">
      <IDSymbol name="MyMenuGroup" value="0x1020"/>
      <IDSymbol name="MyCommandId" value="0x0100"/>
    </GuidSymbol>
  </Symbols>

</CommandTable>

Common Context Menu IDs

<!-- Solution Explorer -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/>      <!-- Solution -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/>      <!-- Project -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_FOLDERNODE"/>    <!-- Folder -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>      <!-- File/Item -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_REFERENCEROOT"/> <!-- References -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_REFERENCE"/>     <!-- Reference -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_WEBREFFOLDER"/>  <!-- Web References -->

<!-- Code Editor -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>       <!-- Editor -->

<!-- Class View -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CV_PROJECT"/>    <!-- CV Project -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CV_ITEM"/>       <!-- CV Item -->

<!-- Other -->
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_NOCOMMANDS"/>    <!-- No selection -->

Multiple Context Menus

Add your command to multiple locations using CommandPlacements:

<CommandPlacements>
  <!-- Add to Solution context menu -->
  <CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE"/>
  </CommandPlacement>

  <!-- Also add to Project context menu -->
  <CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/>
  </CommandPlacement>

  <!-- And File context menu -->
  <CommandPlacement guid="guidMyPackageCmdSet" id="MyCommandId" priority="0x0100">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
  </CommandPlacement>
</CommandPlacements>

Dynamic Visibility

Control when your command appears:

With Community Toolkit

[Command(PackageIds.MyCommand)]
internal sealed class MyCommand : BaseCommand<MyCommand>
{
    protected override void BeforeQueryStatus(EventArgs e)
    {
        // Only show for .cs files
        var item = VS.Solutions.GetActiveItemAsync().Result;
        Command.Visible = item?.FullPath?.EndsWith(".cs") == true;
    }

    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        // Command implementation
    }
}

Traditional OleMenuCommand

private void InitializeCommand()
{
    var commandId = new CommandID(PackageGuids.guidMyPackageCmdSet, PackageIds.MyCommand);
    var command = new OleMenuCommand(Execute, commandId);
    command.BeforeQueryStatus += OnBeforeQueryStatus;

    var commandService = (IMenuCommandService)GetService(typeof(IMenuCommandService));
    commandService.AddCommand(command);
}

private void OnBeforeQueryStatus(object sender, EventArgs e)
{
    ThreadHelper.ThrowIfNotOnUIThread();

    var command = (OleMenuCommand)sender;

    // Get selected items in Solution Explorer
    var monitorSelection = (IVsMonitorSelection)GetService(typeof(SVsShellMonitorSelection));
    monitorSelection.GetCurrentSelection(
        out IntPtr hierarchyPtr,
        out uint itemId,
        out IVsMultiItemSelect multiSelect,
        out IntPtr containerPtr);

    // Show only for single selection
    command.Visible = multiSelect == null && hierarchyPtr != IntPtr.Zero;
}

UI Context Visibility

Show commands only in specific contexts:

<VisibilityConstraints>
  <VisibilityItem guid="guidMyPackageCmdSet" id="MyCommandId"
                  context="UICONTEXT_SolutionHasSingleProject"/>
</VisibilityConstraints>

Common UI contexts:

ContextWhen Visible
UICONTEXT_SolutionExistsA solution is open
UICONTEXT_SolutionHasSingleProjectSolution has exactly one project
UICONTEXT_SolutionHasMultipleProjectsSolution has multiple projects
UICONTEXT_SolutionBuildingBuild in progress
UICONTEXT_DebuggingDebug session active
UICONTEXT_DesignModeNot debugging
UICONTEXT_CodeWindowCode editor has focus
UICONTEXT_NoSolutionNo solution open

Create a submenu in a context menu:

<Menus>
  <Menu guid="guidMyPackageCmdSet" id="MySubmenu" priority="0x0100" type="Menu">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
    <Strings>
      <ButtonText>My Extension</ButtonText>
    </Strings>
  </Menu>
</Menus>

<Groups>
  <Group guid="guidMyPackageCmdSet" id="MySubmenuGroup" priority="0x0100">
    <Parent guid="guidMyPackageCmdSet" id="MySubmenu"/>
  </Group>
</Groups>

<Buttons>
  <Button guid="guidMyPackageCmdSet" id="SubCommand1" priority="0x0100" type="Button">
    <Parent guid="guidMyPackageCmdSet" id="MySubmenuGroup"/>
    <Strings>
      <ButtonText>Sub Command 1</ButtonText>
    </Strings>
  </Button>

  <Button guid="guidMyPackageCmdSet" id="SubCommand2" priority="0x0200" type="Button">
    <Parent guid="guidMyPackageCmdSet" id="MySubmenuGroup"/>
    <Strings>
      <ButtonText>Sub Command 2</ButtonText>
    </Strings>
  </Button>
</Buttons>

Complete Example

A context menu command that processes selected files:

[Command(PackageIds.ProcessFilesCommand)]
internal sealed class ProcessFilesCommand : BaseCommand<ProcessFilesCommand>
{
    protected override void BeforeQueryStatus(EventArgs e)
    {
        ThreadHelper.JoinableTaskFactory.Run(async () =>
        {
            var items = await VS.Solutions.GetActiveItemsAsync();
            var hasFiles = items.Any(i => !string.IsNullOrEmpty(i.FullPath) &&
                                          File.Exists(i.FullPath));
            Command.Visible = hasFiles;
            Command.Enabled = hasFiles;
        });
    }

    protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
    {
        var items = await VS.Solutions.GetActiveItemsAsync();
        var files = items
            .Where(i => File.Exists(i.FullPath))
            .Select(i => i.FullPath)
            .ToList();

        if (!files.Any())
        {
            await VS.MessageBox.ShowWarningAsync("No files selected");
            return;
        }

        await VS.StatusBar.ShowProgressAsync("Processing files...", 0, files.Count);

        for (int i = 0; i < files.Count; i++)
        {
            await ProcessFileAsync(files[i]);
            await VS.StatusBar.ShowProgressAsync(
                $"Processing {Path.GetFileName(files[i])}...",
                i + 1,
                files.Count);
        }

        await VS.StatusBar.ShowMessageAsync($"Processed {files.Count} files");
    }

    private async Task ProcessFileAsync(string filePath)
    {
        await Task.Delay(100); // Your processing logic
    }
}

VSCT for the command:

<Groups>
  <Group guid="guidMyPackageCmdSet" id="SolutionExplorerGroup" priority="0x0600">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
  </Group>
</Groups>

<Buttons>
  <Button guid="guidMyPackageCmdSet" id="ProcessFilesCommand" priority="0x0100" type="Button">
    <Parent guid="guidMyPackageCmdSet" id="SolutionExplorerGroup"/>
    <Icon guid="ImageCatalogGuid" id="Process"/>
    <CommandFlag>IconIsMoniker</CommandFlag>
    <CommandFlag>DynamicVisibility</CommandFlag>
    <Strings>
      <ButtonText>Process Files</ButtonText>
    </Strings>
  </Button>
</Buttons>

Best Practices

  1. Use groups - Don’t parent directly to menus; create a group first
  2. Set priorities - Control order with priority values (lower = higher in menu)
  3. Dynamic visibility - Hide commands that don’t apply to the selection
  4. Use submenus - Group related commands under one submenu to reduce clutter
  5. Consistent naming - Match VS naming conventions
  6. Add icons - Commands with icons are easier to find
Tip

Use the DynamicVisibility command flag in VSCT when implementing BeforeQueryStatus to control visibility programmatically.

Warning

Context menu commands should execute quickly. For long operations, show a progress indicator and consider running asynchronously.

See Also