Menus: Structure, Ordering, and Context Menu Subscription

1. Main menu function

void AddMenuContribution(
    int sort,
    string menuPath,
    Action<MenuCommandContext> callback,
    Func<bool>? isEnabled = null)

2. Main menu path format

<Root>.<Group>.<Item>[.<SubMenu>...]

Examples:

File.openclose.Open
Effects.100.Amplitude.Amplify
View.pluginsgroup.Show Sample Editor
Rule: root is first token, group is second token, final token is the clickable menu item label. Any tokens between group and final token become submenu levels.

3. How ordering works in the host

3.1 Root menu order

3.2 Items inside a root

Practical convention: many plugins use numeric group keys (for example 100, 200) as group+order buckets inside a root.

3.3 Group separators

4. Context menu function

void AddContextMenuContribution(
    string surfaceId,
    int sort,
    string menuPath,
    Action<MenuCommandContext> callback,
    Func<bool>? isEnabled = null)

5. Context menu path format

<Group>.<Item>[.<SubMenu>...]

Examples:

effectsgroup.Smooth
effectsgroup.Amplitude.Amplify
editgroup.Paste

6. Sample Editor context menu subscription

Sample editor wave surface id is:

SampleEditor.Wave

Minimal subscription example:

context.WindowManager.AddContextMenuContribution(
    surfaceId: "SampleEditor.Wave",
    sort: 200,
    menuPath: "effectsgroup.Smooth",
    callback: _ => OpenSmoothDialog(),
    isEnabled: () => _context?.DocumentService.ActiveDocument is not null);

Nested submenu example:

context.WindowManager.AddContextMenuContribution(
    "SampleEditor.Wave",
    200,
    "effectsgroup.Amplitude.Amplify",
    _ => OpenAmplifyDialog());

7. Recommended menu conventions

7.1 Known context-menu surface IDs

Use GetContextMenuContributions(surfaceId) to inspect what is currently registered for a surface.

8. Common mistakes

Back to top