WordPress Multisite Architecture
WordPress Multisite enables running multiple sites from a single WordPress installation. This document covers the core architecture, terminology, and fundamental concepts.
Core Concepts
Network
A Network (historically called "site") is the top-level container that holds multiple sites. A WordPress installation can have one or multiple networks (multi-network setup).
Key properties:
id– Unique network identifierdomain– Network’s primary domainpath– Network’s path (usually/)site_name– Human-readable network nameblog_id– ID of the network’s main site
Class: WP_Network
Table: wp_site
Site
A Site (historically called "blog") is an individual WordPress site within a network. Each site has its own content, settings, and users with roles.
Key properties:
blog_id/id– Unique site identifiersite_id/network_id– Parent network IDdomain– Site’s domainpath– Site’s pathregistered– Registration timestamplast_updated– Last content update timestamppublic– Visibility (1 = public, 0 = private)archived– Archive statusmature– Mature content flagspam– Spam flagdeleted– Deletion flaglang_id– Language identifier
Class: WP_Site
Table: wp_blogs
Installation Types
Subdomain Installation
Each site gets its own subdomain:
- Main site:
example.com - Child sites:
blog1.example.com,blog2.example.com
define( 'SUBDOMAIN_INSTALL', true );Subdirectory Installation
Each site gets its own path:
- Main site:
example.com - Child sites:
example.com/blog1/,example.com/blog2/
define( 'SUBDOMAIN_INSTALL', false );Check installation type:
if ( is_subdomain_install() ) {
// Subdomain installation
}Database Structure
Global Tables (Network-wide)
These tables are shared across all sites:
| Table | Purpose |
|---|---|
wp_site | Networks |
wp_sitemeta | Network metadata |
wp_blogs | Sites |
wp_blogmeta | Site metadata |
wp_users | All users |
wp_usermeta | User metadata |
wp_signups | Pending registrations |
wp_registration_log | Registration log |
Per-Site Tables
Each site gets its own prefixed tables:
- Main site:
wp_posts,wp_options, etc. - Site 2:
wp_2_posts,wp_2_options, etc. - Site N:
wp_N_posts,wp_N_options, etc.
Global Variables
$current_site
The current WP_Network object. Access via get_current_site() or get_network().
$current_blog
The current WP_Site object. Access via get_site().
$blog_id
Current site ID. Access via get_current_blog_id().
$_wp_switched_stack
Stack of site IDs when using switch_to_blog().
$switched
Boolean indicating if currently switched to another blog.
Site Switching
The site switching mechanism allows code to operate in the context of different sites:
// Switch to site 5
switch_to_blog( 5 );
// Do work in site 5's context
$posts = get_posts();
$option = get_option( 'blogname' );
// Switch back
restore_current_blog();What switches:
- Database table prefix (
$wpdb->prefix) - Object cache context
- User capabilities (after
init) - Current blog global (
$blog_id)
What doesn’t switch:
- Loaded plugins
- Active theme
- Loaded PHP files
Nested Switching
Switches can be nested; restore_current_blog() returns to the previous context:
switch_to_blog( 2 );
switch_to_blog( 3 );
// Now in site 3
restore_current_blog(); // Back to site 2
restore_current_blog(); // Back to originalCheck switch status:
if ( ms_is_switched() ) {
// Currently switched to another blog
}Site Lifecycle
1. Signup (Optional)
User requests a new site via wp-signup.php:
wpmu_signup_blog( $domain, $path, $title, $user, $user_email, $meta );Creates pending record in wp_signups.
2. Activation
Signup is activated:
wpmu_activate_signup( $key );3. Site Creation
Site is created in the database:
$blog_id = wpmu_create_blog( $domain, $path, $title, $user_id, $options, $network_id );
// Or directly:
$site_id = wp_insert_site( $data );4. Initialization
Database tables are created and populated:
wp_initialize_site( $site_id, $args );5. Update
Site properties are updated:
wp_update_site( $site_id, $data );
update_blog_status( $blog_id, 'public', 0 );6. Deletion
Site is deleted:
wp_delete_site( $site_id );This triggers wp_uninitialize_site() to drop tables and remove files.
Site Status Flags
Sites can have various status flags:
| Flag | Meaning |
|---|---|
public | Site is publicly visible |
archived | Site is archived (read-only) |
mature | Site contains mature content |
spam | Site is marked as spam |
deleted | Site is deleted (soft delete) |
Each flag triggers corresponding hooks when changed:
make_spam_blog/make_ham_blogarchive_blog/unarchive_blogmature_blog/unmature_blogmake_delete_blog/make_undelete_blog
User Management
Network Users
Users are global across the network but have roles per-site.
Add user to a site:
add_user_to_blog( $blog_id, $user_id, $role );Remove user from a site:
remove_user_from_blog( $user_id, $blog_id, $reassign );Get user’s blogs:
$blogs = get_blogs_of_user( $user_id );Primary Blog
Each user has a primary blog stored in user meta:
$primary = get_user_meta( $user_id, 'primary_blog', true );Super Admins
Network administrators with full access to all sites:
if ( is_super_admin() ) {
// Has network-wide privileges
}Constants
Required Constants
define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', false ); // or true
define( 'DOMAIN_CURRENT_SITE', 'example.com' );
define( 'PATH_CURRENT_SITE', '/' );
define( 'SITE_ID_CURRENT_SITE', 1 );
define( 'BLOG_ID_CURRENT_SITE', 1 );Upload Constants (Legacy)
define( 'UPLOADBLOGSDIR', 'wp-content/blogs.dir' ); // Legacy
define( 'UPLOADS', 'wp-content/blogs.dir/2/files/' ); // Per-siteCookie Constants
Set by ms_cookie_constants():
define( 'COOKIEPATH', '/' );
define( 'SITECOOKIEPATH', '/' );
define( 'ADMIN_COOKIE_PATH', '/wp-admin' );
define( 'COOKIE_DOMAIN', '.example.com' ); // Subdomain onlyFile Upload Constants
define( 'WPMU_SENDFILE', false ); // X-Sendfile header
define( 'WPMU_ACCEL_REDIRECT', false ); // X-Accel-Redirect headerLoading Sequence
ms-load.php– Core loading functionsms-default-constants.php– Define multisite constantsms-settings.php– Bootstrap network and sitems-blogs.php– Blog/site functionsms-site.php– Site API (CRUD)ms-network.php– Network APIms-functions.php– General multisite functionsms-default-filters.php– Default hooks
Query Classes
WP_Site_Query
Query sites with flexible parameters:
$sites = get_sites( array(
'network_id' => 1,
'public' => 1,
'archived' => 0,
'spam' => 0,
'deleted' => 0,
'number' => 100,
'orderby' => 'last_updated',
'order' => 'DESC',
) );WP_Network_Query
Query networks:
$networks = get_networks( array(
'number' => 10,
) );Caching
Site Cache
Sites are cached in the sites cache group:
$site = wp_cache_get( $blog_id, 'sites' );Clear site cache:
clean_blog_cache( $blog_id );Network Cache
Networks are cached in the networks cache group:
$network = wp_cache_get( $network_id, 'networks' );Clear network cache:
clean_network_cache( $network_id );Global Cache Groups
These cache groups are shared network-wide:
sites,site-details,site-optionsnetworks,blog-details,blog-lookupusers,usermeta,userlogins,useremail,userslugssite-transient,global-posts
Large Network Considerations
Networks with >10,000 sites or users are considered "large":
if ( wp_is_large_network( 'sites' ) ) {
// Optimize for large site count
}
if ( wp_is_large_network( 'users' ) ) {
// Optimize for large user count
}Live counts are disabled for large networks to improve performance. Counts are updated via scheduled events instead.
Error Handling
Site Validation
Before creating sites, validate the domain/path:
if ( domain_exists( $domain, $path, $network_id ) ) {
// Site already exists
}Site Status Checks
The ms_site_check() function validates site status during bootstrap:
- Returns drop-in file path for deleted/inactive/suspended sites
- Super admins bypass these checks
Database Errors
Site creation can fail:
$result = wp_insert_site( $data );
if ( is_wp_error( $result ) ) {
// Handle: db_insert_error, site_taken, etc.
}Network Options vs Site Options
Network Options (Shared)
Stored in wp_sitemeta, shared across all sites:
get_site_option( 'admin_email' );
update_site_option( 'registration', 'user' );
get_network_option( $network_id, 'option_name' );Site Options (Per-Site)
Stored in each site’s wp_N_options:
get_option( 'blogname' );
get_blog_option( $blog_id, 'option_name' );Site Meta
Site metadata (stored in wp_blogmeta):
add_site_meta( $site_id, 'key', 'value' );
get_site_meta( $site_id, 'key', true );
update_site_meta( $site_id, 'key', 'new_value' );
delete_site_meta( $site_id, 'key' );Check if site meta is supported:
if ( is_site_meta_supported() ) {
// wp_blogmeta table exists
}