WordPress admin customization allows plugins and themes to tailor the backend experience for content editors — removing irrelevant menu items, adding custom dashboard widgets, modifying the columns displayed in post list tables, and controlling which screen options are available — reducing cognitive load for non-technical clients who should not see 40 menu items they will never use. The admin menu is managed via the admin_menu hook: add_menu_page() adds a top-level menu item with a custom icon (a Dashicons class name or inline SVG as a base64 data URI), add_submenu_page() adds items under any existing or custom top-level menu, and remove_menu_page() hides items from the menu for the current user. Menu items should be hidden based on user capabilities — never remove menus for administrators, always check with current_user_can() rather than removing menus blindly. Dashboard widgets (the boxes on wp-admin/index.php) are added with wp_add_dashboard_widget() and removed by hooking into wp_dashboard_setup and calling remove_meta_box() with the widget ID — common removals are WordPress news, Quick Draft, and Activity widgets that add noise for client dashboards. Custom columns in the Posts list table are added via the manage_{post_type}_posts_columns filter (to define the column header) and the manage_{post_type}_posts_custom_column action (to output the column data for each row) — useful for displaying featured image thumbnails, custom field values, or SEO scores directly in the list view. Sortable columns require registering them via the manage_{post_type}_sortable_columns filter and handling the sort in a pre_get_posts hook that adds meta_key and orderby to the query. The custom user roles post covers defining what each user type can do; admin customization covers what they see in the backend once logged in.
Problem: A WordPress site built for a restaurant client has the full WordPress admin menu with 18 top-level items — the client only needs to manage their menu items (a CPT), update their hours (an options page), and view form submissions. The dashboard is full of WordPress news widgets, and the Posts list shows Author and Tag columns that are irrelevant for a single-author restaurant site.
Solution: Create a mu-plugin that hides irrelevant admin menus for the “editor” role, removes default dashboard widgets, adds a custom “Today’s Reservations” dashboard widget, and customizes the dish CPT list columns to show category and price.
// ── Hide admin menus for non-admin users ──────────────────────────────────
add_action( 'admin_menu', 'myplugin_restrict_admin_menu' );
function myplugin_restrict_admin_menu(): void {
if ( current_user_can( 'manage_options' ) ) return; // show everything to admins
$menus_to_remove = [
'edit.php', // Posts
'edit-comments.php', // Comments
'tools.php', // Tools
'options-general.php',// Settings
];
foreach ( $menus_to_remove as $menu_slug ) {
remove_menu_page( $menu_slug );
}
}
// ── Remove default dashboard widgets, add custom one ──────────────────────
add_action( 'wp_dashboard_setup', 'myplugin_setup_dashboard' );
function myplugin_setup_dashboard(): void {
remove_meta_box( 'dashboard_quick_press', 'dashboard', 'side' );
remove_meta_box( 'dashboard_primary', 'dashboard', 'side' );
remove_meta_box( 'dashboard_activity', 'dashboard', 'normal' );
remove_meta_box( 'dashboard_right_now', 'dashboard', 'normal' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_add_dashboard_widget(
'myplugin_reservations_today',
__( "Today's Reservations", 'myplugin' ),
'myplugin_render_reservations_widget'
);
}
}
function myplugin_render_reservations_widget(): void {
$today = current_time( 'Y-m-d' );
$reservations = get_posts( [
'post_type' => 'reservation',
'meta_key' => '_reservation_date',
'meta_value' => $today,
'numberposts' => 20,
] );
if ( ! $reservations ) {
echo '' . esc_html__( 'No reservations today.', 'myplugin' ) . '
';
return;
}
echo '';
foreach ( $reservations as $res ) {
$time = get_post_meta( $res->ID, '_reservation_time', true );
$party = get_post_meta( $res->ID, '_party_size', true );
printf(
'- %s — %s, party of %s
',
esc_html( $time ),
esc_html( $res->post_title ),
esc_html( $party )
);
}
echo '
';
}
// ── Custom columns for 'dish' CPT ─────────────────────────────────────────
add_filter( 'manage_dish_posts_columns', function( array $columns ): array {
unset( $columns['author'], $columns['tags'] );
$columns['dish_category'] = __( 'Category', 'myplugin' );
$columns['dish_price'] = __( 'Price', 'myplugin' );
return $columns;
} );
add_action( 'manage_dish_posts_custom_column', function( string $column, int $post_id ): void {
switch ( $column ) {
case 'dish_category':
$cats = get_the_terms( $post_id, 'dish_category' );
echo $cats ? esc_html( implode( ', ', wp_list_pluck( $cats, 'name' ) ) ) : '—';
break;
case 'dish_price':
$price = get_post_meta( $post_id, '_dish_price', true );
echo $price ? '$' . esc_html( number_format( (float) $price, 2 ) ) : '—';
break;
}
}, 10, 2 );
NOTE: Removing admin menus with remove_menu_page() only hides the menu link — it does NOT prevent direct URL access to that admin page. A determined user can still navigate directly to /wp-admin/edit.php even if the “Posts” menu link is hidden. To truly restrict access, combine menu removal with capability checks in the admin_init hook: check current_user_can() for the required capability and call wp_die() with a 403 status if the current user lacks it. Never rely solely on menu visibility as a security control.