Add Custom Tabs with Options on “Edit Site” Multisite Settings page
Before we begin, I will show you the desired result:

Let’s do it 🚀
Step 1. Hook the tabs. Create a new one.
The first and the most simplest step begins with network_edit_site_nav_links
. If you ever added custom table columns or hook bulk actions, you are very familiar with the look of this code.
add_filter( 'network_edit_site_nav_links', 'misha_new_siteinfo_tab' ); function misha_new_siteinfo_tab( $tabs ){ $tabs['site-misha'] = array( 'label' => 'Misha', 'url' => 'sites.php?page=mishapage', 'cap' => 'manage_sites' ); return $tabs; }
- Line 7 –
mishapage
is the page slug, we will use in in Step 2 and Step 3 too. So, be careful! - Line 8 – if a current user doesn’t have this capability, the tab just won’t be displayed for him.
- Using
network_edit_site_nav_links
hook it is possible to remove default tabs too, example:unset( $tabs['site-themes'] ); // site-users, site-info, site-settings
or change labels or capabilities of the default tabs:
$tabs['site-users']['label'] = 'Guys';
Where to insert the code?
Okay, usually I do not describe this part, because for most of my tutorials by default – current theme (preferably child theme) or a custom plugin.
But not now! You code must be in a custom plugin which is active for the whole network.
Step 2. Trick with option pages
Ok, the interesting part is that the hook from the previous step (network_edit_site_nav_links
) wasn’t supposed to be used for creating new tabs. Just for changing the default ones.
If you look at the URLs of the other tabs, like Info, Users etc, you will see that the tabs linked directly to php files.
So, what to do? 🤔
- First of all I create a simple submenu page under "All Sites" link.
- Some CSS tricks to hide it from submenu, so the page will be available only if you navigate to it from the tabs.
Just to make it simpler for you, I decided not to deal with the Settings API this time. Maybe I will publish the next tutorial about it.
/* * Add submenu page under Sites */ add_action( 'network_admin_menu', 'misha_new_page' ); function misha_new_page(){ add_submenu_page( 'sites.php', 'Edit website', // will be displayed in <title> 'Edit website', // doesn't matter 'manage_network_options', // capabilities 'mishapage', 'misha_handle_admin_page' // the name of the function which displays the page ); } /* * Some CSS tricks to hide the link to our custom submenu page */ add_action('admin_head','misha_trick'); function misha_trick(){ echo '<style> #menu-site .wp-submenu li:last-child{ display:none; } </style>'; } /* * Display the page and settings fields */ function misha_handle_admin_page(){ // do not worry about that, we will check it too $id = $_REQUEST['id']; // you can use $details = get_site( $id ) to add website specific detailes to the title $title = 'Misha\'s settings'; echo '<div class="wrap"><h1 id="edit-site">' . $title . '</h1> <p class="edit-site-actions"><a href="' . esc_url( get_home_url( $id, '/' ) ) . '">Visit</a> | <a href="' . esc_url( get_admin_url( $id ) ) . '">Dashboard</a></p>'; // navigation tabs network_edit_site_nav( array( 'blog_id' => $id, 'selected' => 'site-misha' // current tab ) ); // more CSS tricks :) echo ' <style> #menu-site .wp-submenu li.wp-first-item{ font-weight:600; } #menu-site .wp-submenu li.wp-first-item a{ color:#fff; } </style> <form method="post" action="edit.php?action=mishaupdate">'; wp_nonce_field( 'misha-check' . $id ); echo '<input type="hidden" name="id" value="' . $id . '" /> <table class="form-table"> <tr> <th scope="row"><label for="some_field">Some option</label></th> <td><input name="some_field" class="regular-text" type="text" id="some_field" value="' . esc_attr( get_blog_option( $id, 'some_field') ) . '" /></td> </tr> </table>'; submit_button(); echo '</form></div>'; } /* * Save settings */ add_action('network_admin_edit_mishaupdate', 'misha_save_options'); function misha_save_options() { $blog_id = $_POST['id']; check_admin_referer('misha-check'.$blog_id); // security check update_blog_option( $blog_id, 'some_field', $_POST['some_field'] ); wp_redirect( add_query_arg( array( 'page' => 'mishapage', 'id' => $blog_id, 'updated' => 'true'), network_admin_url('sites.php') )); // redirect to /wp-admin/sites.php?page=mishapage&blog_id=ID&updated=true exit; } add_action( 'network_admin_notices', 'misha_notice' ); function misha_notice() { if( isset( $_GET['updated'] ) && isset( $_GET['page'] ) && $_GET['page'] == 'mishapage' ) { echo '<div id="message" class="updated notice is-dismissible"> <p>Congratulations!</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button> </div>'; } }
Some comments about the code.
- Line 4 – when creating new menu and submenu pages in Multisite Dashboard, you should use
network_admin_menu
instead ofadmin_menu
. - Please note, that the tab name
site-misha
must match on line 48 and in Step 1 on line 5 - The page slug
mishapage
must match also on lines 11, 88, 102, in Step 1 on line 7 and in Step 3 on line 6! - Nonce check – lines 62 and 83.
Step 3. $_GET validation
The last but not the least, we have to validate the some stuff. Consider that once you have implemented the first two steps, everything should work.
add_action( 'current_screen', 'misha_redirects' ); function misha_redirects(){ // do nothing if we are on another page $screen = get_current_screen(); if( $screen->id !== 'sites_page_mishapage-network' ) { return; } // $id is a blog ID $id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; if ( ! $id ) { wp_die( __('Invalid site ID.') ); } $details = get_site( $id ); if ( ! $details ) { wp_die( __( 'The requested site does not exist.' ) ); } //if ( ! can_edit_network( $details->site_id ) ) { // wp_die( __( 'Sorry, you are not allowed to access this page.' ), 403 ); //} }
This code through the error like below in case the $_GET variable doesn’t exist or doesn’t contain a correct blog ID. I commented the capability validation, because it has been also performed in Step 2, line 10.

That’s it! Read more Multisite stuff here or by the links below. Do not forget to leave a commend down below if you have a question about this tut 👇