wppaste
WordPress

switch_to_blog( int $new_blog_id, bool $deprecated = null ): true

Since
MU (3.0.0)
Source
wp-includes/ms-blogs.php:500

Repoints WordPress's active site context, including $wpdb's table prefix and the object cache group, to the site identified by $new_blog_id. It only works on a multisite network and only affects data-layer globals for the rest of the request, not PHP code (plugins or theme files) already loaded for the originally requested site. Always pair a call with restore_current_blog() so later code doesn't keep running against the wrong site. The $deprecated parameter is accepted for backward compatibility but ignored.

Switches the current blog.

Description

This function is useful if you need to pull posts, or other information, from other blogs. You can switch back afterwards using restore_current_blog().

PHP code loaded with the originally requested site, such as code from a plugin or theme, does not switch. See #14941.

Compatibility

WordPress
since MU (3.0.0)
PHP
7.4–8.6-dev
  • 6.7.7
  • 6.8.8
  • 6.9.7
  • 7.0.4
  • 7.1.0

Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.

Parameters

$new_blog_idint
The ID of the blog to switch to. Default: current blog.
$deprecatedbooloptional
Not used.Default: null

Return value

true
Always returns true.

Code examples

Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.

Read the published post count from another site in the network

Switch into a second site long enough to read its post counts, then restore the original site.

if ( ! is_multisite() ) {
	echo esc_html( 'This example requires a WordPress multisite network.' );
} else {
	$site_ids = get_sites( array( 'fields' => 'ids', 'number' => 2 ) );
	if ( count( $site_ids ) < 2 ) {
		echo esc_html( 'Need at least two sites in the network for this example.' );
	} else {
		$target_site_id = $site_ids[1];
		switch_to_blog( $target_site_id );
		$counts = wp_count_posts( 'post' );
		printf( 'Site %d has %d published posts.', absint( $target_site_id ), (int) $counts->publish );
		restore_current_blog();
	}
}

switch_to_blog() only exists when is_multisite() is true; calling it on a single-site install fatals.

Total published posts across every site on the network

Loop over every registered site, switch into each one just long enough to read its post count, then move on.

if ( ! is_multisite() ) {
	echo esc_html( 'This example requires a WordPress multisite network.' );
} else {
	$total = 0;
	foreach ( get_sites( array( 'fields' => 'ids' ) ) as $site_id ) {
		switch_to_blog( $site_id );
		$total += (int) wp_count_posts( 'post' )->publish;
		restore_current_blog();
	}
	printf( 'Total published posts across the network: %d', $total );
}

Each iteration restores before switching to the next site so the stack never grows deeper than one level.

Common problems and fixes · 4

Why does calling switch_to_blog() throw a fatal 'call to undefined function' error?

This function lives in the multisite-only ms-blog.php file, so it isn't defined unless the install is actually running as a multisite network. Calling it on a normal single-site install fails.

Why do $post, $wp_query, and other globals still show the old site's data after switching?

The source only reassigns $wpdb's blog id, $GLOBALS['table_prefix'], $GLOBALS['blog_id'], and the cache group via wp_cache_switch_to_blog(). It never touches query or post-related globals, so anything set before the switch keeps its old values until you explicitly re-query.

Why is the site still wrong after I finished my code?

Every call pushes the previous blog ID onto $GLOBALS['_wp_switched_stack'] but never pops it automatically. If you forget restore_current_blog(), the rest of the request keeps running against the switched-to site.

Why does the switch_blog action fire even when I pass the current site's ID?

The source has an early-exit branch for $new_blog_id === $prev_blog_id that skips the $wpdb and cache changes but still calls do_action( 'switch_blog', $new_blog_id, $prev_blog_id, 'switch' ) and sets $GLOBALS['switched'] = true. Hooked callbacks run even though nothing about the environment actually changed.

Alternatives and related functions

restore_current_blog
When you're done working on the switched-to site and need to pop the switch stack and put $wpdb, table_prefix, and blog_id back to what they were.
get_sites
When you need the list of site IDs in the network before deciding which ones to switch into.
ms_is_switched
When you need to check whether the current request is presently inside a switch_to_blog() call before running site-sensitive logic.
get_current_blog_id
When you only need to know which site is currently active without changing anything.

Performance profile

How much work a call to switch_to_blog() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.

Cost class
Light

Only touches the object cache, via wp_cache_switch_to_blog().

Scaling
Constant

No loop in the body: the same number of instructions runs whatever you pass in.

Instructions
22–58

Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 75.

Plugin surface
1 hook

Third-party callbacks on 'switch_blog' run inside this call, and their cost is not bounded by anything here.

Called by
37

37 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • hookthird-party callbacksdo_action()called directly
  • cacheobject cachewp_cache_switch_to_blog()called directly

What one call costs · 4 distinct outcomes

One number would be a lie: the work depends on which branch runs. These are every distinct cost switch_to_blog() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
$new_blog_id === false22–23get_current_blog_id(), do_action()
$new_blog_id !== false && function_exists()39–40get_current_blog_id(), ->set_blog_id(), ->get_blog_prefix(), function_exists(), wp_cache_switch_to_blog(), do_action()
$new_blog_id !== false && !function_exists()45–49get_current_blog_id(), ->set_blog_id(), ->get_blog_prefix(), function_exists(), wp_cache_init(), function_exists(), do_action()
$new_blog_id !== false53–58get_current_blog_id(), ->set_blog_id(), ->get_blog_prefix(), function_exists(), wp_cache_init(), function_exists(), wp_cache_add_global_groups(), wp_cache_add_non_persistent_groups(), do_action()

Across PHP versions

PHPCompiledExecutedBranchesNotes
8.6-dev7522–587
8.57522–587
8.47522–587
8.37522–587
8.27522–587
8.17522–5875 fewer instructions than PHP 7.4
7.48024–627

An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.

Hooks and filters fired · 2

2 hooks fire while switch_to_blog() runs, in this order:

  1. do_action( switch_blog )actionline 527 (+27 into the body)

    Fires when the blog is switched.

  2. do_action( switch_blog )actionline 587 (+87 into the body)

    Fires when the blog is switched.

Uses · 6

Used by · 37

Show all 37

Source code

function switch_to_blog( $new_blog_id, $deprecated = null ) {	global $wpdb; 	$prev_blog_id = get_current_blog_id();	if ( empty( $new_blog_id ) ) {		$new_blog_id = $prev_blog_id;	} 	$GLOBALS['_wp_switched_stack'][] = $prev_blog_id; 	/*	 * If we're switching to the same blog id that we're on,	 * set the right vars, do the associated actions, but skip	 * the extra unnecessary work	 */	if ( $new_blog_id === $prev_blog_id ) {		/**		 * Fires when the blog is switched.		 *		 * @since MU (3.0.0)		 * @since 5.4.0 The `$context` parameter was added.		 *		 * @param int    $new_blog_id  New blog ID.		 * @param int    $prev_blog_id Previous blog ID.		 * @param string $context      Additional context. Accepts 'switch' when called from switch_to_blog()		 *                             or 'restore' when called from restore_current_blog().		 */		do_action( 'switch_blog', $new_blog_id, $prev_blog_id, 'switch' ); 		$GLOBALS['switched'] = true; 		return true;	} 	$wpdb->set_blog_id( $new_blog_id );	$GLOBALS['table_prefix'] = $wpdb->get_blog_prefix();	$GLOBALS['blog_id']      = $new_blog_id; 	if ( function_exists( 'wp_cache_switch_to_blog' ) ) {		wp_cache_switch_to_blog( $new_blog_id );	} else {		global $wp_object_cache; 		if ( is_object( $wp_object_cache ) && isset( $wp_object_cache->global_groups ) ) {			$global_groups = $wp_object_cache->global_groups;		} else {			$global_groups = false;		} 		wp_cache_init(); 		if ( function_exists( 'wp_cache_add_global_groups' ) ) {			if ( is_array( $global_groups ) ) {				wp_cache_add_global_groups( $global_groups );			} else {				wp_cache_add_global_groups(					array(						'blog-details',						'blog-id-cache',						'blog-lookup',						'blog_meta',						'global-posts',						'image_editor',						'networks',						'network-queries',						'sites',						'site-details',						'site-options',						'site-queries',						'site-transient',						'theme_files',						'rss',						'users',						'user-queries',						'user_meta',						'useremail',						'userlogins',						'userslugs',					)				);

Changelog

Introduced in MU (3.0.0). Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

About this page

Parsed data
Generated from the wordpress-develop 6.9.7 tag, from src/wp-includes/ms-blogs.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.