WordPress Multisite

Decide when a network of sites is the right tool, set one up, and write plugins that behave correctly across every site.

  • Intermediate
  • 9 min read
  • Updated 17 Oct 2026

What you will learn

  • Set up a network
  • Write Multisite-aware code
  • Decide when Multisite fits and when separate installs are better
  • Switch between sites safely with switch_to_blog

Before you start

Multisite turns one WordPress installation into a network of sites that share the same code, users table and database. It is powerful for the right job and a burden for the wrong one. As a plugin developer you need to understand it even if you never run a network, because your code may be installed on one.

When Multisite fits

All sites in a network share core, plugins and themes. A plugin is installed once and either activated per site or network-activated for all. Users exist once and can be members of several sites.

Good fits:

  • Many similar sites run by one organisation: university departments, franchise locations, regional or language sites.
  • A platform where users create their own sites from a fixed set of themes and plugins.
  • Sites that genuinely need shared users and central updates.

Poor fits:

  • Unrelated client sites on one server. One faulty plugin update or one bad query affects every site, and you cannot move a single site to another host as easily.
  • Sites that need very different plugins or different PHP versions.
  • Sites with different owners who each need full administrator control. On a network, only super admins can install plugins and themes.

Multisite is not the only way to manage many sites. Separate installs managed with WP-CLI and a shared deployment pipeline are often simpler and isolate failures better.

How a network is organised

TermMeaning
NetworkThe whole installation. Code calls it a "site" in older APIs, such as get_site_option().
SiteOne website in the network. Older APIs call it a "blog", as in switch_to_blog().
Super adminA user who manages the network: sites, plugins, themes, network settings.
Network adminThe admin area at /wp-admin/network/.

Subdirectories or subdomains

You choose once, at setup:

  • Subdirectories: example.com/shop, example.com/news.
  • Subdomains: shop.example.com, news.example.com. This needs wildcard DNS and a certificate that covers the subdomains.

Individual sites can also use their own custom domain, set in the site's URL in the network admin, provided DNS and certificates point to the server.

The database

Global tables are shared: wp_users, wp_usermeta, wp_site, wp_sitemeta, wp_blogs and a few others. The first site uses the normal tables (wp_posts, wp_options). Each new site gets its own set with its ID in the name: wp_2_posts, wp_2_options, and so on. Uploads for site 2 go to wp-content/uploads/sites/2/.

This is why code must use $wpdb->posts and $wpdb->prefix, never hard-coded table names. $wpdb->prefix changes to wp_2_ when site 2 is active, while $wpdb->base_prefix stays wp_.

Setting up a network

Start from a working single site, with a backup.

  1. Allow Multisite in wp-config.php, above the "That's all, stop editing!" line:
PHP
define( 'WP_ALLOW_MULTISITE', true );
  1. Deactivate all plugins, then go to Tools > Network Setup, choose subdirectories or subdomains and install.
  2. WordPress shows the constants to add to wp-config.php and the rewrite rules for .htaccess or your Nginx configuration. They look like this:
PHP
define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', false );
define( 'DOMAIN_CURRENT_SITE', 'example.com' );
define( 'PATH_CURRENT_SITE', '/' );
define( 'SITE_ID_CURRENT_SITE', 1 );
define( 'BLOG_ID_CURRENT_SITE', 1 );
  1. Log in again and you will find My Sites > Network Admin in the admin bar.

WP-CLI can do the conversion and manage sites:

Shell
wp core multisite-convert
wp site create --slug=news --title="News"
wp site list --fields=blog_id,url
wp plugin activate myplugin --network
wp super-admin list

Most WP-CLI commands act on the main site unless you pass --url:

Shell
wp --url=example.com/news option get blogname
wp site list --field=url | xargs -I {} wp --url={} cache flush

Writing Multisite-aware code

Detecting the environment

PHP
if ( is_multisite() ) {
    $site_id = get_current_blog_id();
}

Site options versus network options

get_option() and update_option() read and write the current site's options table. For settings that apply to the whole network, use network options, stored in wp_sitemeta:

PHP
$api_key = get_site_option( 'myplugin_api_key', '' );
update_site_option( 'myplugin_api_key', $new_key );

On a single site these fall back to normal options, so the same code works everywhere. Decide deliberately which settings are per site and which are per network.

Capabilities

On a network, super admins pass every capability check on every site. Some capabilities, such as manage_network_options, manage_network_plugins and manage_sites, only make sense for super admins. Check capabilities as usual; do not test roles. If a screen belongs in the network admin, register it on the network_admin_menu action instead of admin_menu.

Switching sites

switch_to_blog() changes the current site for the database and options, so you can read or write another site's content. Every call must be paired with restore_current_blog().

PHP
/**
 * Collect the latest post title from every public site in the network.
 */
function myplugin_latest_titles(): array {
    $titles = array();

    $site_ids = get_sites(
        array(
            'fields'   => 'ids',
            'public'   => 1,
            'archived' => 0,
            'deleted'  => 0,
            'number'   => 50,
        )
    );

    foreach ( $site_ids as $site_id ) {
        switch_to_blog( $site_id );

        $posts = get_posts(
            array(
                'numberposts'   => 1,
                'no_found_rows' => true,
            )
        );

        if ( $posts ) {
            $titles[ $site_id ] = get_the_title( $posts[0] );
        }

        restore_current_blog();
    }

    return $titles;
}

What switching does not do matters as much:

  • It does not load the other site's plugins or theme. If a custom post type is registered only on site 5, it is not registered while you are switched to site 5 from site 1. get_posts() still works on its rows, but templates, filters and settings from that site's plugins are absent.
  • It is not free. Each switch changes cache context. Looping over hundreds of sites in a page request is slow; cache the combined result, or build it in a scheduled job.
  • Nesting needs matching restores. Two switches need two restores. A missed restore leaves the rest of the request on the wrong site, which is a confusing bug.

For network-wide data you will read often, consider maintaining it in one place (a network option or a table using $wpdb->base_prefix) instead of switching on every request.

Network activation

A plugin can be activated on one site or for the whole network. The activation hook receives a flag telling you which:

PHP
register_activation_hook( __FILE__, 'myplugin_activate' );

function myplugin_activate( bool $network_wide ): void {
    if ( is_multisite() && $network_wide ) {
        // Store network-level defaults only. Per-site setup runs lazily.
        add_site_option( 'myplugin_network_version', '1.0.0' );
        return;
    }

    myplugin_setup_site();
}

function myplugin_setup_site(): void {
    add_option( 'myplugin_settings', array( 'enabled' => true ) );
}

Avoid looping over every site in the activation hook. On a large network it can time out halfway. Instead, check per site when it is first used (for example on admin_init, comparing a stored version) and run setup then, or queue the work with WP-Cron.

To force network activation only, add Network: true to the plugin header.

New sites

When a site is created, the wp_initialize_site action fires with the new WP_Site object. Core uses this hook at priority 10 to create the site's tables, so hook in later:

PHP
add_action( 'wp_initialize_site', 'myplugin_new_site', 20, 1 );

function myplugin_new_site( WP_Site $site ): void {
    // This code lives in the main plugin file, so __FILE__ is the plugin file.
    if ( ! is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) {
        return;
    }

    switch_to_blog( (int) $site->blog_id );
    myplugin_setup_site();
    restore_current_blog();
}

is_plugin_active_for_network() is defined in wp-admin/includes/plugin.php. It is available during site creation in the admin; if you call it elsewhere, require_once ABSPATH . 'wp-admin/includes/plugin.php'; first.

Uninstalling cleanly

uninstall.php runs once, in the context of the main site. To remove per-site data from every site, loop through sites with switch_to_blog() there, and delete network options with delete_site_option().

Summary

  • Multisite shares code, users and one database across many sites; it suits related sites run centrally, not unrelated client sites.
  • Enable it with WP_ALLOW_MULTISITE, run Network Setup, then add the generated constants and rewrite rules.
  • Each site has its own tables (wp_2_posts); always use $wpdb->prefix and core table properties.
  • Use get_site_option() for network settings and get_option() for per-site settings.
  • Pair every switch_to_blog() with restore_current_blog(), remember it does not load the other site's plugins, and avoid switching across many sites per request.
  • Handle network activation and wp_initialize_site without looping over every site at once.

Further reading