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 original
Check 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-site
Cookie Constants
Set by ms_cookie_constants():
define( 'COOKIEPATH', '/' );
define( 'SITECOOKIEPATH', '/' );
define( 'ADMIN_COOKIE_PATH', '/wp-admin' );
define( 'COOKIE_DOMAIN', '.example.com' ); // Subdomain only
File Upload Constants
define( 'WPMU_SENDFILE', false ); // X-Sendfile header
define( 'WPMU_ACCEL_REDIRECT', false ); // X-Accel-Redirect header
Loading 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
}