Customize the WordPress Admin Menu: Remove, Add, and Restrict by Role

The WordPress admin sidebar menu is fully customizable through PHP. For agency and freelance work this matters a great deal: clients who see menu items for tools they should never touch — Appearance → Editor, Plugins, Settings → Permalinks — will eventually touch them and break something. Trimming the admin menu down to only what a specific role needs reduces support requests and keeps the dashboard focused. WordPress provides remove_menu_page() to remove top-level items, remove_submenu_page() for sub-items, and add_menu_page() / add_submenu_page() to add custom ones. All of these hook into admin_menu. For network admin menu modifications, the corresponding hook is network_admin_menu. This article covers the full pattern — removing default items by role, adding a custom top-level menu with a submenu, and the important distinction between hiding a menu item and actually removing the capability to access that screen.

Problem: Non-administrator users — editors, clients — can see and accidentally access admin menu items like the Theme Editor, Plugin Manager, and Permalink Settings that they should never touch.

Solution: Use remove_menu_page() and remove_submenu_page() on the admin_menu hook, conditioned on the current user's role or capability, to hide items from specific users.

<?php
add_action( 'admin_menu', 'restrict_admin_menu_for_editors', 999 );

function restrict_admin_menu_for_editors() {
    // Only restrict non-administrators
    if ( current_user_can( 'manage_options' ) ) {
        return;
    }

    // Remove top-level menu pages (slug = the file path or menu slug)
    remove_menu_page( 'plugins.php' );           // Plugins
    remove_menu_page( 'tools.php' );             // Tools
    remove_menu_page( 'edit-comments.php' );     // Comments

    // Remove specific submenu items
    remove_submenu_page( 'themes.php',  'theme-editor.php' );   // Appearance → Editor
    remove_submenu_page( 'options-general.php', 'options-permalink.php' ); // Settings → Permalinks
}

// ── Add a custom top-level menu ────────────────────────────────────────
add_action( 'admin_menu', 'add_custom_admin_menu' );

function add_custom_admin_menu() {
    // add_menu_page( page_title, menu_title, capability, menu_slug, callback, icon, position )
    add_menu_page(
        'Site Reports',
        'Reports',
        'edit_posts',
        'site-reports',
        'render_reports_page',
        'dashicons-chart-bar',
        25
    );

    // add_submenu_page( parent_slug, page_title, menu_title, capability, menu_slug, callback )
    add_submenu_page(
        'site-reports',
        'Traffic Report',
        'Traffic',
        'edit_posts',
        'site-reports-traffic',
        'render_traffic_report_page'
    );
}

function render_reports_page() {
    echo '<div class="wrap"><h1>' . esc_html__( 'Site Reports', 'textdomain' ) . '</h1></div>';
}

NOTE: Removing a menu item with remove_menu_page() only hides the link — it does not prevent a user from accessing the URL directly. If someone knows the URL of the Plugins page they can still navigate to it. To truly restrict access, remove the underlying capability with remove_cap() or check capabilities at the top of the page callback itself. Hiding the menu is a UX improvement; capability checks are the actual security control.