Site-Specific Settings in Multisite Sites > Info Page

When working with WordPress Multisite you have plenty of ways how you can add settings. You can add some custom options network-wide or you can still use classic options pages for each site of the network. For classic options pages I can recommend a superb plugin (it is me, who developed it).

But what if for your actual goal the best way to add settings is to Sites > Info pages of each site? Just like on the screenshot below:

In this tutorial I will show you how you can do that:

Site-specific settings in WordPress Multisite

First things first, you have to add a custom tab into each site settings page. It quite easy to do, I mean you just have to use network_edit_site_nav_links for that purpose.

add_filter( 'network_edit_site_nav_links', 'rudr_new_siteinfo_tab' );

function rudr_new_siteinfo_tab( $tabs ){

	$tabs[ 'site-misha' ] = array(
		'label' => 'Misha',
		// 'url' => 'sites.php?page=mishapage',
		'url' => add_query_arg( 'page', 'mishapage', 'sites.php' ), 
		'cap' => 'manage_sites'
	return $tabs;


Let’s figure it out step by step how is this code meant to work:

Removing or renaming the default tabs

Action hook network_edit_site_nav_links can be used not only for creating a custom tab but also in order to edit the default ones. It is as simple as working with arrays in PHP. Let’s do some stuff now.

add_filter( 'network_edit_site_nav_links', function( $tabs ) {
	unset( $tabs[ 'site-themes' ] ); // site-users, site-info, site-settings
	$tabs[ 'site-users' ][ 'label' ] = 'Humans';
	return $tabs;
} );

And the result of this step:

WordPress Multisite custom settings
Do not try to click on “Misha” tab, because at this moment it will give you an error “Sorry, you are not allowed to access this page.” but we are going to resolve this moment very soon.

2. Add a Page with Settings

The interesting part here is that network_edit_site_nav_links hook is not supposed to be used for creating new tabs with some custom content. Even if you look at the URLs of the other tabs, like Info, Users etc, you will see that the tabs are linked directly to php files.

Create a hidden admin submenu page

The trick here is to create a regular network admin submenu page but without connection to any parent menu. So, in order to do that, we just have to pass null as a first argument of add_submenu_page() function.

Everything else is almost as usual, but remember that mishapage slug should match to the slug you used in network_edit_site_nav_links before.

add_action( 'network_admin_menu', 'rudr_new_page' );

function rudr_new_page(){
	add_submenu_page( '', 'Edit site', 'Edit site', 'manage_network_options', 'mishapage', 'rudr_page_callback' );

function rudr_page_callback(){

	// do not worry about that, we will check it too
	$id = absint( $_REQUEST[ 'id' ] );

	$site = get_site( $id );

		<div class="wrap">
			<h1 id="edit-site">Edit Site: <?php echo $site->blogname ?></h1>
			<p class="edit-site-actions">
				<a href="<?php echo esc_url( get_home_url( $id, '/' ) ) ?>">Visit</a> | <a href="<?php echo esc_url( get_admin_url( $id ) ) ?>">Dashboard</a>
				// navigation tabs
						'blog_id'  => $id,
						'selected' => 'site-misha' // current tab
			<form method="post" action="edit.php?action=mishaupdate">
				<?php wp_nonce_field( 'misha-check' . $id ); ?>
				<input type="hidden" name="id" value="<?php echo $id ?>" />
				<table class="form-table">
						<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="<?php echo esc_attr( get_blog_option( $id, 'some_field') ) ?>" /></td>
				<?php submit_button(); ?>

Save page settings

add_action( 'network_admin_edit_mishaupdate',  'rudr_save' );
function rudr_save() {

	$id = absint( $_POST[ 'id' ] );

	check_admin_referer( 'misha-check' . $id ); // nonce check

	update_blog_option( $id, 'some_field', sanitize_text_field( $_POST[ 'some_field' ] ) );
	// redirect to /wp-admin/sites.php?page=mishapage&blog_id=ID&updated=true
				'page' => 'mishapage',
				'id' => $id,
				'updated' => 'true'
			network_admin_url( 'sites.php' )


Just a reminder – never forget about proper data sanitization and escaping.


add_action( 'network_admin_notices', 'rudr_notice' );
function rudr_notice() {

	if( isset( $_GET[ 'updated' ] ) && isset( $_GET[ 'page' ] ) && 'mishapage' === $_GET[ 'page' ] ) {

			<div id="message" class="updated notice is-dismissible">
				<button type="button" class="notice-dismiss">
					<span class="screen-reader-text">Dismiss this notice.</span>




Site-specific settings in WordPress Multisite

Page title

Almost forgot about it. In case you don’t want a PHP notice “Deprecated: strip_tags(): Passing null to parameter #1 ($string) of type string is deprecated” to appear at the top of your newly created page, you will have to think about its title as well (I mean what is inside <title> tag).

I found the only way to set it – with current_screen hook. Which is actually not that bad for this purpose.

add_action( 'current_screen', 'rudr_page_title' );

function rudr_page_title( $current_screen ) {
	global $title;

	if( 'sites_page_mishapage-network' === $current_screen->id && isset( $_GET[ 'id' ] ) && $_GET[ 'id' ] ) {
		$blog_details = get_blog_details( array( 'blog_id' => $_GET[ 'id' ] ) );
		$title = __( 'Edit Site:' ) . ' ' . $blog_details->blogname;

Site ID validation

This part of the tutorial is not 100% necessary, but it allows to avoid some errors when someone may try to access our options page directly without providing site ID to it as a URL parameter.

add_action( 'current_screen', 'rudr_double_check' );
function rudr_double_check(){

	// do nothing if we are on another page
	$screen = get_current_screen();
	if( 'sites_page_mishapage-network' !== $screen->id ) {

	// $id is a blog ID
	$id = isset( $_REQUEST[ 'id' ] ) ? absint( $_REQUEST[ 'id' ] ) : 0;

	if ( ! $id ) {
		wp_die( __( 'Incorrect site ID.' ) );

	if ( ! get_site( $id ) ) {
		wp_die( __( 'The requested site does not exist.' ) );

	//if ( ! can_edit_network( $id ) ) {
	//	wp_die( __( 'Sorry, you are not allowed to access this page.' ), 403 );


So, instead of receiving PHP warnings and fatal errors your lost users will get this notice:

the requested site does not exist error
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X