Your client called. They went into Appearance and accidentally deleted a template. Or worse: they opened the Plugin Editor and saved broken PHP.

This is the scenario every developer dreads when handing off a WordPress site. The fix is straightforward: remove the menus they shouldn't touch in the first place. Here's how to do it properly, including the parts most tutorials skip.

  1. Add remove_menu_page() or remove_submenu_page() calls inside a function
  2. Hook that function to admin_menu (not admin_init — that's the most common mistake)
  3. For submenus like the Theme Editor, use priority 999 so your code runs after WordPress registers all menus
  4. Combine with current_user_can() checks if you need actual security, not just UX cleanup

Why Hiding Menus Is Not the Same as Securing Them

Hiding a menu item removes it from the sidebar, but it does not block direct URL access. A user who knows the address wp-admin/theme-editor.php can still reach it.

If you need real security, combine menu removal with capability checks using current_user_can(). This guide covers the UX side: cleaning up the admin panel so clients only see what they need.

Disabling the Theme and Plugin Editors

The most common reason to touch this code is preventing clients from opening the code editors. Add this to your functions.php:

function client_remove_editor_menus() {
    remove_submenu_page( 'themes.php', 'theme-editor.php' );
    remove_submenu_page( 'plugins.php', 'plugin-editor.php' );
}
add_action( 'admin_menu', 'client_remove_editor_menus', 999 );

The priority 999 matters. WordPress registers these submenus late in the process, and a lower priority hook may run before they exist, which means remove_submenu_page() silently fails and the menu stays visible.

For production sites, there's a cleaner option: add this to wp-config.php:

define( 'DISALLOW_FILE_EDIT', true );

This also blocks the REST API from writing theme files. Use DISALLOW_FILE_EDIT when you want the restriction to survive theme updates. Use the functions.php approach when you need finer control, such as allowing admins to access the editor while blocking editors or contributors.

Removing Top-Level Admin Menus

To strip down the sidebar for client users, use remove_menu_page() with the slug of each top-level menu:

function client_clean_admin_sidebar() {
    remove_menu_page( 'edit.php' );                     // Posts
    remove_menu_page( 'upload.php' );                   // Media
    remove_menu_page( 'edit.php?post_type=page' );      // Pages
    remove_menu_page( 'edit-comments.php' );            // Comments
    remove_menu_page( 'themes.php' );                   // Appearance
    remove_menu_page( 'plugins.php' );                  // Plugins
    remove_menu_page( 'users.php' );                    // Users
    remove_menu_page( 'tools.php' );                    // Tools
    remove_menu_page( 'options-general.php' );          // Settings
}
add_action( 'admin_menu', 'client_clean_admin_sidebar' );

Keep the menus the client actually needs. If they only publish blog posts, keep edit.php and upload.php and remove the rest.

Deployment note: If you're putting this code directly in your theme's functions.php, it will be wiped on the next theme update. Put it in a child theme or, better, a small site-specific plugin that you version-control separately. This is the part that causes the most "it stopped working" support tickets months after delivery.

Hiding Menus by User Role

Removing menus globally locks out everyone including yourself. The practical pattern is to check the current user's role or capabilities before removing anything:

function client_hide_menus_by_role() {
    if ( current_user_can( 'administrator' ) ) {
        return;
    }
    remove_menu_page( 'plugins.php' );
    remove_menu_page( 'themes.php' );
    remove_menu_page( 'tools.php' );
    remove_menu_page( 'options-general.php' );
    remove_menu_page( 'users.php' );
}
add_action( 'admin_menu', 'client_hide_menus_by_role' );

For sites running WooCommerce, you often want the shop_manager role to access WooCommerce but nothing else in the sidebar. Check against specific capabilities rather than role names:

function client_hide_menus_for_shop_managers() {
    if ( current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'manage_options' ) ) {
        remove_menu_page( 'themes.php' );
        remove_menu_page( 'plugins.php' );
        remove_menu_page( 'users.php' );
        remove_menu_page( 'tools.php' );
        remove_menu_page( 'options-general.php' );
    }
}
add_action( 'admin_menu', 'client_hide_menus_for_shop_managers' );

The manage_options capability belongs to administrators by default. Editors, Authors, and shop managers don't have it, so this check cleanly separates developer-level access from client access.

Hiding Custom Post Type Menus

If your site uses Custom Post Types and the client doesn't need to manage one of them, remove it by its registered post type name:

function client_hide_cpt_menus() {
    remove_menu_page( 'edit.php?post_type=portfolio' );
    remove_menu_page( 'edit.php?post_type=testimonial' );
}
add_action( 'admin_menu', 'client_hide_cpt_menus' );

Replace portfolio and testimonial with your actual post type slugs.

How to Find the Correct Menu Slug (Including Plugin Menus)

This is where most tutorials leave you guessing. Plugins rarely use their display name as the menu slug, and for taxonomy-based submenus, the slug contains encoded characters that don't behave the way you'd expect.

The reliable way is to print the global menu arrays to your debug log. Add this temporarily to functions.php:

add_action( 'admin_menu', function() {
    global $menu, $submenu;
    error_log( print_r( $menu, true ) );
    error_log( print_r( $submenu, true ) );
}, 9999 );

Then check wp-content/debug.log. Each menu entry's slug is at index [2] of the array. Once you have the correct slugs, remove this debug code.

To remove plugin-registered menus once you have their slugs:

function client_hide_plugin_menus() {
    remove_menu_page( 'wpcf7' );         // Contact Form 7
    remove_menu_page( 'rank-math' );     // Rank Math SEO
    remove_menu_page( 'woocommerce' );   // WooCommerce
}
add_action( 'admin_menu', 'client_hide_plugin_menus', 999 );

The ampersand gotcha: This one has caused real confusion. Submenu slugs that contain query parameters are stored with HTML-encoded ampersands in the $submenu array. So edit-tags.php?taxonomy=category&post_type=product becomes edit-tags.php?taxonomy=category&post_type=product. If your remove_submenu_page() call uses a raw &, it won't match and the menu stays visible. I've spent longer than I'd like to admit debugging this before checking the actual array value.

Hiding Dashboard Widgets

Menu removal handles the sidebar, but the Dashboard itself is also worth cleaning up. Clients rarely need to see the "WordPress News" feed or "Quick Draft" widget. Use remove_meta_box() on the wp_dashboard_setup hook:

function client_clean_dashboard() {
    remove_meta_box( 'dashboard_primary', 'dashboard', 'side' );     // WordPress News
    remove_meta_box( 'dashboard_quick_press', 'dashboard', 'side' ); // Quick Draft
    remove_meta_box( 'dashboard_activity', 'dashboard', 'normal' );  // Activity
}
add_action( 'wp_dashboard_setup', 'client_clean_dashboard' );

The widget IDs vary by plugin. Use var_dump( $GLOBALS['wp_meta_boxes']['dashboard'] ) in a temporary function to list all registered dashboard widgets on your specific site.

Troubleshooting: When Nothing Works

If your code runs but menus don't disappear, these are the most common causes:

Wrong hook: Using admin_init instead of admin_menu is the top mistake. The menu array doesn't exist yet during admin_init, so removal calls do nothing.

Priority too low: Some plugins register menus at priority 1020 or higher. If your hook runs at the default priority of 10, the menus get re-added after your removal. Bump your priority to 1021 or higher and test.

Submenu slug mismatch: If the slug contains &, use & in your code. Print the $submenu global to confirm the exact string stored by WordPress.

Multisite: On WordPress multisite, some menus are network-specific and require separate handling on the network_admin_menu hook. Super Admin capabilities are separate from site-level administrator capabilities.

Removing Menus Without Code: Admin Menu Editor

If you'd rather not touch functions.php, the Admin Menu Editor plugin gives you a drag-and-drop interface for hiding, reordering, and restricting menus by role. The free version covers most use cases; the Pro version adds per-user restrictions and capability-based access rules.

For client sites where you want a reliable, auditable configuration and prefer not to maintain custom code, it's worth the dependency. For your own development workflow where you're already editing functions.php, the code approach gives you more control and keeps the plugin count down.

Either approach pairs well with the broader functions.php cleanup routine: removing unused Gutenberg block CSS belongs in the same pass and reduces frontend load for clients who aren't using the block editor.

If a client ever loses admin access after a customization gone wrong, resetting the WordPress admin password via WP-CLI or direct database access is the fastest recovery path. And if the site goes offline entirely after a wp-config.php edit, a database connection error from a mistyped credential is usually the cause.