Skip to main content

Menus API

Complete reference for creating interactive admin menus.

RegisterMenuCategory

Register a new menu category in the admin menu.

void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")

Parameters:

  • categoryId - Unique identifier for the category
  • categoryName - Display name shown in menu
  • permission - Required permission to see category (default: "@css/generic")

Example:

_api.RegisterMenuCategory("mycategory", "My Custom Category", "@css/generic");

Best Practice: Register categories in the OnSimpleAdminReady event handler:

_api.OnSimpleAdminReady += () =>
{
_api.RegisterMenuCategory("mycategory", Localizer?["category_name"] ?? "My Category");
};

RegisterMenu (Basic)

Register a menu within a category.

void RegisterMenu(
string categoryId,
string menuId,
string menuName,
Func<CCSPlayerController, object> menuFactory,
string? permission = null,
string? commandName = null
)

Parameters:

  • categoryId - Category to add menu to
  • menuId - Unique menu identifier
  • menuName - Display name in menu
  • menuFactory - Function that creates the menu
  • permission - Required permission (optional)
  • commandName - Command for permission override (optional, e.g., "css_god")

Example:

_api.RegisterMenu(
"mycategory",
"mymenu",
"My Menu",
CreateMyMenu,
"@css/generic"
);

private object CreateMyMenu(CCSPlayerController player)
{
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", player);
// Add options...
return menu;
}

Register a menu with automatic context passing - eliminates duplication!

void RegisterMenu(
string categoryId,
string menuId,
string menuName,
Func<CCSPlayerController, MenuContext, object> menuFactory,
string? permission = null,
string? commandName = null
)

Parameters:

  • categoryId - Category to add menu to
  • menuId - Unique menu identifier
  • menuName - Display name in menu
  • menuFactory - Function that receives player AND context
  • permission - Required permission (optional)
  • commandName - Command for permission override (optional)

Example:

// ✅ NEW WAY - No duplication!
_api.RegisterMenu(
"fun",
"god",
"God Mode",
CreateGodMenu,
"@css/cheats",
"css_god"
);

private object CreateGodMenu(CCSPlayerController admin, MenuContext context)
{
// context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
return _api!.CreateMenuWithPlayers(
context, // ← Automatically uses "God Mode" and "fun"
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) => ToggleGod(admin, target)
);
}

// ❌ OLD WAY - Had to repeat "God Mode" and "fun"
private object CreateGodMenuOld(CCSPlayerController admin)
{
return _api!.CreateMenuWithPlayers(
"God Mode", // ← Repeated from RegisterMenu
"fun", // ← Repeated from RegisterMenu
admin,
filter,
action
);
}

MenuContext Properties:

public class MenuContext
{
public string CategoryId { get; } // e.g., "fun"
public string MenuId { get; } // e.g., "god"
public string MenuTitle { get; } // e.g., "God Mode"
public string? Permission { get; } // e.g., "@css/cheats"
public string? CommandName { get; } // e.g., "css_god"
}

UnregisterMenu

Remove a menu from a category.

void UnregisterMenu(string categoryId, string menuId)

Example:

public override void Unload(bool hotReload)
{
_api?.UnregisterMenu("mycategory", "mymenu");
}

CreateMenuWithBack

Create a menu with automatic back button.

object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)

Parameters:

  • title - Menu title
  • categoryId - Category for back button navigation
  • player - Player viewing the menu

Example:

var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", admin);
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
_api.AddMenuOption(menu, "Option 2", _ => DoAction2());
return menu;

Create a menu using context - no duplication!

object CreateMenuWithBack(MenuContext context, CCSPlayerController player)

Example:

private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
// ✅ Uses context.MenuTitle and context.CategoryId automatically
var menu = _api!.CreateMenuWithBack(context, admin);

_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
return menu;
}

CreateMenuWithPlayers

Create a menu showing a list of players.

object CreateMenuWithPlayers(
string title,
string categoryId,
CCSPlayerController admin,
Func<CCSPlayerController, bool> filter,
Action<CCSPlayerController, CCSPlayerController> onSelect
)

Parameters:

  • title - Menu title
  • categoryId - Category for back button
  • admin - Admin viewing menu
  • filter - Function to filter which players to show
  • onSelect - Action when player is selected (admin, selectedPlayer)

Example:

return _api!.CreateMenuWithPlayers(
"Select Player",
"mycategory",
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) =>
{
// Do something with selected player
DoAction(admin, target);
}
);

object CreateMenuWithPlayers(
MenuContext context,
CCSPlayerController admin,
Func<CCSPlayerController, bool> filter,
Action<CCSPlayerController, CCSPlayerController> onSelect
)

Example:

private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context, // ← Automatically uses correct title and category!
admin,
player => player.PawnIsAlive && admin.CanTarget(player),
(admin, target) => PerformAction(admin, target)
);
}

AddMenuOption

Add a clickable option to a menu.

void AddMenuOption(
object menu,
string name,
Action<CCSPlayerController> action,
bool disabled = false,
string? permission = null
)

Parameters:

  • menu - Menu to add option to
  • name - Option display text
  • action - Function called when selected
  • disabled - Whether option is disabled (default: false)
  • permission - Required permission to see option (optional)

Example:

var menu = _api!.CreateMenuWithBack("Actions", "mycategory", admin);

_api.AddMenuOption(menu, "Heal Player", _ =>
{
target.SetHp(100);
});

_api.AddMenuOption(menu, "Admin Only Option", _ =>
{
// Admin action
}, false, "@css/root");

return menu;

AddSubMenu

Add a submenu option that opens another menu.

void AddSubMenu(
object menu,
string name,
Func<CCSPlayerController, object> subMenuFactory,
bool disabled = false,
string? permission = null
)

Parameters:

  • menu - Parent menu
  • name - Submenu option display text
  • subMenuFactory - Function that creates the submenu
  • disabled - Whether option is disabled (default: false)
  • permission - Required permission (optional)

Example:

var menu = _api!.CreateMenuWithBack("Main Menu", "mycategory", admin);

_api.AddSubMenu(menu, "Player Actions", admin =>
{
return CreatePlayerActionsMenu(admin);
});

_api.AddSubMenu(menu, "Server Settings", admin =>
{
return CreateServerSettingsMenu(admin);
}, false, "@css/root");

return menu;

Opening Menus

OpenMenu

Display a menu to a player.

void OpenMenu(object menu, CCSPlayerController player)

Example:

var menu = CreateMyMenu(player);
_api!.OpenMenu(menu, player);

Note: Usually menus open automatically when selected, but this can be used for direct opening.


Complete Examples

Simple Player Selection Menu

private void RegisterMenus()
{
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");

_api.RegisterMenu(
"actions",
"slay",
"Slay Player",
CreateSlayMenu,
"@css/slay"
);
}

private object CreateSlayMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.PawnIsAlive && admin.CanTarget(player),
(admin, target) =>
{
target.PlayerPawn?.Value?.CommitSuicide(false, true);
admin.PrintToChat($"Slayed {target.PlayerName}");
}
);
}

Nested Menu with Value Selection

private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);

var players = _api.GetValidPlayers()
.Where(p => p.PawnIsAlive && admin.CanTarget(p));

foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateHpValueMenu(admin, player);
});
}

return menu;
}

private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "mycategory", admin);

var hpValues = new[] { 1, 10, 50, 100, 200, 500 };

foreach (var hp in hpValues)
{
_api.AddMenuOption(menu, $"{hp} HP", _ =>
{
if (target.IsValid && target.PawnIsAlive)
{
target.PlayerPawn?.Value?.SetHealth(hp);
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
}
});
}

return menu;
}

private object CreateAdminMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);

// Everyone with menu access sees this
_api.AddMenuOption(menu, "Basic Action", _ => DoBasicAction());

// Only root admins see this
_api.AddMenuOption(menu, "Dangerous Action", _ =>
{
DoDangerousAction();
}, false, "@css/root");

// Submenu with permission
_api.AddSubMenu(menu, "Advanced Options", admin =>
{
return CreateAdvancedMenu(admin);
}, false, "@css/root");

return menu;
}

Dynamic Menu with Current State

private object CreateToggleMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);

var players = _api.GetValidPlayers()
.Where(p => admin.CanTarget(p));

foreach (var player in players)
{
// Show current state in option name
bool hasGod = GodPlayers.Contains(player.Slot);
string status = hasGod ? "✓ ON" : "✗ OFF";

_api.AddMenuOption(menu, $"{player.PlayerName} ({status})", _ =>
{
if (hasGod)
GodPlayers.Remove(player.Slot);
else
GodPlayers.Add(player.Slot);

// Recreate menu to show updated state
var newMenu = CreateToggleMenu(admin, context);
_api.OpenMenu(newMenu, admin);
});
}

return menu;
}

Best Practices

1. Use MenuContext

// ✅ Good - Uses context
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");

private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(context, admin, filter, action);
}

// ❌ Bad - Duplicates title and category
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");

private object CreateMenuOld(CCSPlayerController admin)
{
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
}

2. Register in OnSimpleAdminReady

_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Also call directly for hot reload

private void RegisterMenus()
{
if (_menusRegistered) return;

_api!.RegisterMenuCategory("category", "Category Name");
_api.RegisterMenu("category", "menu", "Menu Name", CreateMenu);

_menusRegistered = true;
}

3. Always Unregister

public override void Unload(bool hotReload)
{
if (_api == null) return;

_api.UnregisterMenu("category", "menu");
_api.OnSimpleAdminReady -= RegisterMenus;
}

4. Validate Player State

private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.IsValid && // Player exists
!player.IsBot && // Not a bot
player.PawnIsAlive && // Alive
admin.CanTarget(player), // Can be targeted
(admin, target) =>
{
// Extra validation before action
if (!target.IsValid || !target.PawnIsAlive)
return;

DoAction(admin, target);
}
);
}

5. Use Translations for Menu Names

_api.RegisterMenuCategory(
"mycategory",
Localizer?["category_name"] ?? "Default Name",
"@css/generic"
);

_api.RegisterMenu(
"mycategory",
"mymenu",
Localizer?["menu_name"] ?? "Default Menu",
CreateMenu
);

Permission Override

The commandName parameter allows server admins to override menu permissions via CounterStrikeSharp's admin system.

Example:

_api.RegisterMenu(
"fun",
"god",
"God Mode",
CreateGodMenu,
"@css/cheats", // Default permission
"css_god" // Command name for override
);

Admin config can override:

{
"css_god": ["@css/vip"]
}

Now VIPs will see the God Mode menu instead of requiring @css/cheats!


Common Patterns

Player List with Actions

private object CreatePlayerListMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);

foreach (var player in _api.GetValidPlayers())
{
if (!admin.CanTarget(player)) continue;

_api.AddSubMenu(menu, player.PlayerName, admin =>
{
var actionMenu = _api.CreateMenuWithBack($"Actions: {player.PlayerName}", context.CategoryId, admin);

_api.AddMenuOption(actionMenu, "Slay", _ => player.CommitSuicide());
_api.AddMenuOption(actionMenu, "Kick", _ => KickPlayer(player));
_api.AddMenuOption(actionMenu, "Ban", _ => BanPlayer(admin, player));

return actionMenu;
});
}

return menu;
}

Category-Based Organization

private void RegisterAllMenus()
{
// Player management category
_api!.RegisterMenuCategory("players", "Player Management", "@css/generic");
_api.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
_api.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");

// Server management category
_api.RegisterMenuCategory("server", "Server Management", "@css/generic");
_api.RegisterMenu("server", "map", "Change Map", CreateMapMenu, "@css/changemap");
_api.RegisterMenu("server", "settings", "Settings", CreateSettingsMenu, "@css/root");
}