Multisite (Network, MU, MultiUser) Search

SearchWP is able to perform cross-site Multisite/Network searches. There are a number of things to mention/consider regarding the implementation.

Note: There are important limitations to be aware of!


Cross-site searches are possible in SearchWP. Any Engine from any site can be used for a cross-site search.

Note: SearchWP’s Engines control what is indexed on each sub-site. If the Engine you are using to perform the search has different Sources/Attributes/Rules than the Engine(s) on the sub-sites you are searching the results may not be accurate.

For example: if Posts have been added to the Engine you are using for the search, but a sub-site does not have an Engine with Posts enabled, that sub-site will not return Posts.

For a comprehensive cross-site search, ensure that all sites share a similar configuration and applicable Engine.

Implementation Details

In a Multisite environment, SearchWP builds a comprehensive search index for the entirety of the network. By default SearchWP will limit results to Entries of the current site.

You can utilize \SearchWP\Query to execute searches across multiple sites within the network by customizing the site parameter to contain either:

  • Array of site IDs (or a comma separated string of site IDs)
  • 'all' to search all sites on the network

Important: the primary concept to understand is that SearchWP’s Engines determine what content is indexed for all sites within a network.

This is significant because \SearchWP\Query uses an Engine from the current site to perform the search. Let’s say the Default Engine of the current site (SITE A) has the following Sources and nothing else:

  • Posts
  • Pages

Within this example network there is a second site (SITE B) that has a Default Engine with the following Sources and nothing else:

  • Posts

If you are performing a \SearchWP\Query in SITE A using its Default Engine, Pages from SITE B will not be returned because Pages were not added to the Default Engine of SITE B.

This may seem counter-intuitive because Pages are added to the Default Engine of SITE A, but the Engines of SITE B define and control what is indexed for that site.

Recommended Engine configuration

Given the above, you can utilize a Supplemental Engine on each network site that shares a common configuration.

Alternatively, you can ensure that your network is set up in such a way that the Engine used for a \SearchWP\Query is the least common denominator for all sites within the network.

Handling Multisite search results

\SearchWP\Query allows you to specify how results are returned using the fields parameter.

By default results are returned as an array of objects each with the following properties: 'id', 'source', 'site', 'relevance'

This format is most useful for Multisite search results because it gives you the ability to switch_to_blog() when necessary so as to retrieve accurate details for each result.

If however you choose to use another supported format it may not include critical site ID information so please keep that in mind as you structure your Multisite searches.

Note: This applies specifically when setting fields to 'all' which will return each result in its native format (e.g. as \WP_Post). Any assumptions made in your results output (e.g. use of get_permalink()) are based on the current site, not necessarily the site from which the result was returned!

Given that, it is always recommended to use the default value for fields and manually switch_to_blog() (and subsequently restore_current_blog()) when necessary!


There are two primary ways to implement a cross-site Multisite search:

  • Override the native/default search on a site within the network
  • Set up a purpose-built cross-site Multisite search

The former is likely more common, but is slightly more complex than setting up a more direct, purpose built search. Both options are covered here. ?

Overriding Default

If you would like to override the default search behavior of a Multisite site to search across a network, we can override the Native site arguments to do just that.

Example: There is a university that has a ‘main’ site, with many Multisite sub-sites for various schools/departments/etc. and we want to override the search on the main site to search the entire Multisite installation. All Default Engines are the same across the entire Multisite network.

There are two steps to take:

  1. Filter the arguments sent to SearchWP to search the whole network
  2. Update the search results template to account for site changes

Filter arguments for main site search

Because we are customizing the native search results page, we’ll use the searchwp\native\args hook to tell SearchWP we want to search across the entire Multisite network:

All hooks should be added to your custom SearchWP Customizations Plugin.

// @link
// Tell SearchWP to search the entire Multisite network when searching on the main site.
add_filter( 'searchwp\query\args', function( $args, $query ) {
// If this is not site 1, bail out.
if ( 1 !== get_current_blog_id() ) {
return $args;
// Search sites with ID 1, 2, 3.
$args['site'] = [1,2,3];
// Retain site info in results.
$args['fields'] = 'default';
return $args;
}, 10, 2 );

Note that we’re also telling SearchWP to return default results (instead of WP_Posts) — which brings us to step 2:

Update search results template

In step 1 we told SearchWP to search the entire Multisite network and not format results as WP_Posts but instead keep the results in their original form, which contains the site info we need to generate proper results at runtime.

We need to update The Loop of our output in the search.php Search Results template of our theme to account for the SearchWP-formatted results:

// @link
global $post;
$current_blog_id = get_current_blog_id();
if ( have_posts() ) {
while ( have_posts() ) {
// Search Results may be formatted as SearchWP results
// because we're searching cross-site on the main site.
if ( 1 === $current_blog_id ) {
if ( $current_blog_id !== $post->site ) {
switch_to_blog( $post->site );
$post = get_post( $post->id );
get_template_part( 'search-result' );
} else {
$post = get_post( $post->id );
get_template_part( 'search-result' );
} else {
get_template_part( 'search-result' );
} else {
get_template_part( 'template-parts/content/content-none' );
view raw search.php hosted with ❤ by GitHub

Note that the results template is still based on The Loop, but we are customizing behavior if we’re outputting results on site 1 (the main site) to account for potential site changes.

Also note that a template part is used to output results, but in the case where a result is returned from a different site the template is included before we restore_current_blog().

This is necessary so as to ensure that function calls such as get_permalink() execute in the proper context before switching back to the main site to output the next result.

Using a Supplemental Engine

When implementing a purpose-built Multisite cross-site search, you can follow the same instructions to create a Supplemental Engine.

With your Supplemental Engine set up, we can first customize the execution of the search to search the entire Multisite network:

// ... this overrides the \SearchWP\Query instantiation found
// @link
// e.g. $search_page is defined in the full snippet, this solely
// replaces the query to search across sites and return 'default' results.
$searchwp_query = new \SearchWP\Query( $search_query, [
'engine' => 'supplemental', // The Engine name.
'fields' => 'default', // Retain site ID info with results.
'site' => 'all', // Search all sites.
'page' => $search_page,
] );

The two edits made are:

  1. Define fields to return the default fields (which contain site ID info)
  2. Specify that we should search all sites

With that change made to the Supplemental Engine implementation, SearchWP will then search across the entire Multisite network and return default results, so we will need to update the output of the results to check each result for a different site:

Note: For brevity, this snippet updates only the results output of the full Supplemental Engine implementation example.

// ... this snippet overrides the results output found
// @link
// so as to account for results that are from a different Multisite site.
// This is not a full and complete example, it shows only the customization
// to the results output to account for site changes.
// Track the current blog ID.
$current_blog_id = get_current_blog_id();
<?php if ( ! empty( $search_query ) && ! empty( $search_results ) ) : ?>
<?php foreach ( $search_results as $search_result ) : ?>
<article class="page hentry search-result">
// Track whether we switched sites for this result.
$switched_site = false;
// Do we need to switch to the proper site for this result?
if ( $current_blog_id !== $search_result->site ) {
switch_to_blog( $search_result->site );
$switched_site = true;
// Output the result based on SearchWP Source.
switch( $search_result->source ) {
// Posts and Pages.
case 'post' . SEARCHWP_SEPARATOR . 'post':
case 'post' . SEARCHWP_SEPARATOR . 'page':
$post = get_post( $search_result->id );
<header class="entry-header"><h2 class="entry-title">
<a href="<?php echo get_permalink(); ?>"><?php the_title(); ?></a>
<div class="entry-summary"><?php the_excerpt(); ?></div>
// Users.
case 'user':
<header class="entry-header"><h2 class="entry-title">
<a href="<?php echo get_author_posts_url( $search_result->id ); ?>">
<?php echo esc_html( get_the_author_meta( 'display_name', $search_result->id ) ); ?>
<div class="entry-summary">
<?php echo wp_kses_post( get_the_author_meta( 'description', $search_result->id ) ); ?>
// If we switched sites, switch back!
if ( $switched_site ) {
<?php endforeach; ?>
<?php endif; ?>

In this snippet segment we can see that we’re now paying attention both to the results format returned by SearchWP (default instead of the native results objects) which contain the information we need about the origin site of each result.

As we iterate over the results we switch_to_blog() when necessary, and we can still output results from any SearchWP Source by utilizing the source, site, and id of each result.

Keep in mind that you should (and in most cases will need to) output all relevant data for each result before calling restore_current_blog() because many functions (e.g. get_permalink()) need to run in the correct site context, so calling restore_current_blog() (when applicable) as late as possible is always the best choice.