Menu Search
Jump to the content X X
Smashing Conf Barcelona

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

WordPress Multisite: Practical Functions And Methods

Multisite is a powerful new feature that arrived with the release of WordPress 3.0. It allows website managers to host multiple independent websites with a single installation of WordPress. Although each “website” in a network is independent, there are many ways to share settings, code and content throughout the entire network.

WordPress Multisite

Since the beginning of the year, I’ve been developing themes and plugins for a WordPress Multisite-powered content network. During that time I’ve learned many powerful tips and tricks unique to Multisite. This guide will introduce you to a few Multisite-specific functions, along with real-world programming examples that you can begin using today. Hopefully, it will open your eyes to a few of the new possibilities available in Multisite.

Further Reading on SmashingMag: Link

Why Use Multisite? Link

Multisite is a great option for freelancers, businesses and organizations that manage multiple WordPress websites. Whether you’re a freelancer who wants to provide hosting and maintenance to clients, a college organization looking to centralize the management of your websites, or a large news publisher trying to isolate silos for different departments, Multisite is the answer.

Managing multiple websites with a single installation of WordPress enables you to easily upgrade the core, plugins and themes for every website in a network. You can share functionality across multiple websites with network plugins, as well as standardize design elements across multiple websites using a parent theme.

Overview of Benefits Link

  • Users are able to easily access and manage multiple websites with a single user account and profile.
  • Users can access a particular website or every website using the same account.
  • Information from one website can be completely isolated from others.
  • Information from one website can be easily shared with others.
  • Theme functionality can be shared across multiple websites using a parent-child theme relationship5 or a functionality plugin6.
  • Updates and upgrades can be rolled out across multiple websites in less time, reducing overhead and maintenance costs.
  • Customizations to WordPress can be efficiently distributed in a centralized, cascading method using network-wide plugins.

I won’t explain how to install and configure Multisite7. If you need help, plenty of great articles are available in the WordPress Codex8.

Working With Multisite Functions Link

Multisite-enabled WordPress installations contain additional functions and features that theme developers can use to improve the experience of a website. If you find yourself developing themes and plugins for WordPress Multisite, consider the following tips to customize and improve the connectivity of the network.

Displaying Information About a Network Link

You might find yourself in a situation where you would like to display the number of websites or users in your network. Providing a link to the network’s primary website would also be nice, so that visitors can learn more about your organization.

Multisite stores global options in the wp_sitemeta database table, such as the network’s name (site_name), the administrator’s email address (admin_email) and the primary website’s URL (siteurl). To access these options, you can use the get_site_option() function.

In this example, I’ve used the get_site_option() function along with get_blog_count() and get_user_count() to display a sentence with details about a network.

<?php if( is_multisite() ): ?>

   The <?php echo esc_html( get_site_option( 'site_name' ) ); ?> network9 currently powers <?php echo get_blog_count(); ?> websites and <?php echo get_user_count(); ?> users.

<?php endif; ?>

This small snippet of code will display the following HTML:

The Smashing Magazine network10 currently powers 52 websites and 262 users.

Many useful Multisite functions can be found in the /wp-includes/ms-functions.php11 file. I highly suggest browsing the Trac project12 yourself. It’s a great way to find new functions and to become familiar with WordPress coding standards13.

Build a Network Navigation Menu Link

Many networks have consistent dynamic navigation that appears on all websites, making it easy for visitors to browse the network. Using the $wpdb database class1914, along with the get_site_url(), home_url(), get_current_blog_id(), switch_to_blog() and restore_current_blog() functions, we can create a fully dynamic network menu, including a class (.current-site-item) to highlight the current website.

The SQL query we’ve created in this example has the potential to become very large, possibly causing performance issues. For this reason, we’ll use the Transients API2015, which enables us to temporarily store a cached version of the results as network website “transients” in the sitemeta table using the set_site_transient() and get_site_transient() functions.

Transients provide a simple and standardized way to store cached data in the database for a set period of time, after which the data expires and is deleted. It’s very similar to storing information with the Options API16, except that it has the added value of an expiration time. Transients are also sped up by caching plugins, whereas normal options aren’t. Due to the nature of the expiration process, never assume that a transient is in the database when writing code.

The SQL query will run every two hours, and the actual data will be returned from the transient, making things much more efficient. I’ve included two parameters, $size and $expires, allowing you to control the number of posts returned and the expiration time for the transient.

One of the most powerful elements of this example is the use of switch_to_blog() and restore_current_blog(). These two Multisite functions enable us to temporarily switch to another website (by ID), gather information or content, and then switch back to the original website.

Add the following to your theme’s functions.php file:

 * Build a list of all websites in a network
function wp_list_sites( $expires = 7200 ) {
   if( !is_multisite() ) return false;
   // Because the get_blog_list() function is currently flagged as deprecated 
   // due to the potential for high consumption of resources, we'll use
   // $wpdb to roll out our own SQL query instead. Because the query can be
   // memory-intensive, we'll store the results using the Transients API
   if ( false === ( $site_list = get_transient( 'multisite_site_list' ) ) ) {
      global $wpdb;
      $site_list = $wpdb->get_results( $wpdb->prepare('SELECT * FROM wp_blogs ORDER BY blog_id') );
      // Set the Transient cache to expire every two hours
      set_site_transient( 'multisite_site_list', $site_list, $expires );
   $current_site_url = get_site_url( get_current_blog_id() );
   $html = '
      ‘ . “n”; foreach ( $site_list as $site ) { switch_to_blog( $site->blog_id ); $class = ( home_url() == $current_site_url ) ? ‘ class=”current-site-item”‘ : ”; $html .= “t” . ‘

    • blog_id . ‘”‘ . $class . ‘>‘ . get_bloginfo(‘name’) . ‘17

‘ . “n”; restore_current_blog(); } $html .= ‘

‘ . “nn”; return $html; }

(Please note: The get_blog_list() function is currently deprecated due to the potential for a high consumption of resources if a network contains more than 1000 websites. Currently, there is no replacement function, which is why I have used a custom $wpdb query in its place. In future, WordPress developers will probably release a better alternative. I suggest checking for a replacement18 before implementing this example on an actual network.)

This function first verifies that Multisite is enabled and, if it’s not, returns false. First, we gather a list of IDs of all websites in the network, sorting them in ascending order using our custom $wpdb query. Next, we iterate through each website in the list, using switch_to_blog() to check whether it is the current website, and adding the .current-site-item class if it is. Then, we use the name and link for that website to create a list item for our menu, returning to the original website using restore_current_blog(). When the loop is complete, we return the complete unordered list to be outputted in our theme. It’s that simple.

To use this in your theme, call the wp_list_sites() function where you want the network menu to be displayed. Because the function first checks for a Multisite-enabled installation, you should verify that the returned value is not false before displaying the corresponding HTML.

// Multisite Network Menu
$network_menu = wp_list_sites();
if( $network_menu ): 
<?php echo $network_menu; ?>

<?php endif; ?>

List Recent Posts Across an Entire Network Link

If the websites in your network share similar topics, you may want to create a list of the most recent posts across all websites. Unfortunately, WordPress does not have a built-in function to do this, but with a little help from the $wpdb database class1914, you can create a custom database query of the latest posts across your network.

This SQL query also has the potential to become very large. For this reason, we’ll use the Transients API2015 again in a method very similar to what is used in the wp_list_sites() function.

Start by adding the wp_recent_across_network() function to your theme’s functions.php file.

 * List recent posts across a Multisite network
 * @uses get_blog_list(), get_blog_permalink()
 * @param int $size The number of results to retrieve
 * @param int $expires Seconds until the transient cache expires
 * @return object Contains the blog_id, post_id, post_date and post_title
function wp_recent_across_network( $size = 10, $expires = 7200 ) {
   if( !is_multisite() ) return false;
   // Cache the results with the WordPress Transients API
   // Get any existing copy of our transient data
   if ( ( $recent_across_network = get_site_transient( 'recent_across_network' ) ) === false ) {
      // No transient found, regenerate the data and save a new transient
      // Prepare the SQL query with $wpdb
      global $wpdb;
      $base_prefix = $wpdb->get_blog_prefix(0);
      $base_prefix = str_replace( '1_', '' , $base_prefix );

      // Because the get_blog_list() function is currently flagged as deprecated 
      // due to the potential for high consumption of resources, we'll use
      // $wpdb to roll out our own SQL query instead. Because the query can be
      // memory-intensive, we'll store the results using the Transients API
      if ( false === ( $site_list = get_site_transient( 'multisite_site_list' ) ) ) {
         global $wpdb;
         $site_list = $wpdb->get_results( $wpdb->prepare('SELECT * FROM wp_blogs ORDER BY blog_id') );
         set_site_transient( 'multisite_site_list', $site_list, $expires );

      $limit = absint($size);

      // Merge the wp_posts results from all Multisite websites into a single result with MySQL "UNION"
      foreach ( $site_list as $site ) {
         if( $site == $site_list[0] ) {
            $posts_table = $base_prefix . "posts";
         } else {
            $posts_table = $base_prefix . $site->blog_id . "_posts";

         $posts_table = esc_sql( $posts_table );
         $blogs_table = esc_sql( $base_prefix . 'blogs' );

         $query .= "(SELECT $posts_table.ID, $posts_table.post_title, $posts_table.post_date, $blogs_table.blog_id FROM $posts_table, $blogs_tablen";
         $query .= "tWHERE $posts_table.post_type = 'post'n";
         $query .= "tAND $posts_table.post_status = 'publish'n";
         $query .= "tAND $blogs_table.blog_id = {$site->blog_id})n";

         if( $site !== end($site_list) ) 
            $query .= "UNIONn";
            $query .= "ORDER BY post_date DESC LIMIT 0, $limit";

      // Sanitize and run the query
      $query = $wpdb->prepare($query);
      $recent_across_network = $wpdb->get_results( $query );

      // Set the Transients cache to expire every two hours
      set_site_transient( 'recent_across_network', $recent_across_network, 60*60*2 );

   // Format the HTML output
   $html = '

‘; } $html .= ‘

‘; return $html; }

Using this function in your theme is simple. Be certain to check the return value before outputting HTML to avoid conflicts with non-Multisite installations.

// Display recent posts across the entire network
$recent_network_posts = wp_recent_across_network();
if( $recent_network_posts ):

<?php echo $recent_network_posts; ?>

<?php endif; ?>

Retrieve a Single Post from Another Website in the Network Link

In certain situations, you may find it useful to refer to a single page, post or post type from another website in your network. The get_blog_post() function makes this process simple.

For example, you may want to display the_content() from an “About” page on the primary website in your network.

// Display "About" page content from the network's primary website
$about_page = get_blog_post( 1, 317 );
if( $about_page ): 

<?php echo $about_page->post_content; ?>

<?php endif; ?>

Did you notice that the entire $post object is returned? In this example, we’ve used only the_content(), but far more information is available for other circumstances.

Set Up Global Variables Across a Network Link

Starting any WordPress project in a solid local development environment21 is always important. You might find it handy to have a global variable that determines whether a website is “live” or “staging.” In Multisite, you can achieve this using a network-activated plugin that contains the following handy function, assuming that your local host contains localhost in the URL:

 * Define network globals
function ms_define_globals() {
   global $blog_id;
   $GLOBALS['staging'] = ( strstr( $_SERVER['SERVER_NAME'], 'localhost' ) ) ? true : false;
add_action( 'init', 'ms_define_globals', 1 );

When would you use this $staging variable? I use it to display development-related messages, notifications and information to improve my workflow.

Display the Page Request Information in a Local Environment Link

I use the $staging global variable to display the number of queries and page-request speed for every page across a network in my local environment.

 * Display page request info
 * @requires $staging
function wp_page_request_info() {
   global $staging;
   if ( $staging ): ?>
      <?php echo get_num_queries(); ?> queries in <?php timer_stop(1); ?> seconds.
   <?php endif;
add_action( 'wp_footer', 'wp_page_request_info', 1000 );

This is only one of many ways you can use the ms_define_globals() function. I’ve used it to define, find and replace URLs in the content delivery network, to detect mobile devices and user agents, and to filter local attachment URLs.

Conclusion Link

There is tremendous value in the simplicity of managing multiple websites in a single installation of WordPress. Leveraging WordPress Multisite is quickly becoming a requisite skill among WordPress developers. These techniques should provide a solid foundation for you to build on, so that you can be the next WordPress Multisite theme rock star!

Other Resources Link


Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27

↑ Back to top Tweet itShare on Facebook

Kevin Leary is a Web Developer in Boston, Massachusetts. He specializes in customized WordPress setups for businesses and is an Interaction Designer at OpenView Venture Partners.

  1. 1

    Actually, it’s not a good idea to use switch in the front end like that, in your nav menus example. It’s a resource intensive function intended for admin area usage. might be a better way to go.

    Oh hey look – a whole blog dedicated to multisite functionality. ;)

    • 2

      That’s a good point Andrea,

      If performance is an issue you could move the Transient API functionality up a level to store the value of $html rather than just $site_list to ensure that switch_to_blog() is only run periodically. This may actually be a better approach all around come to think of it.

      I believe that would make it a viable option for the front-end, it has so many possible uses I would hate to rule it out entirely.

  2. 3

    Nice useful article.Thank you for sharing.

  3. 4

    Great article! Thnx.

  4. 5

    Hi, I have a comment to not just this but all articles on SM. Can you include more screen shots that illustrate how the functionality will look like on front or back end of the CMS? For example Multisite Network Menu – I would love to see an example of implementation of this menu in back end. That helps me to see right away if this is a solution that my client will be happy with.


    • 6

      If you mean the plugin I linked to above – it looks the same in the admin area as the custom menu screen does, because that’s what it uses.

      There’s no way to tell from the admin area that it is a global menu – unless you label it for the client as such.

  5. 7


  6. 8

    Stephen Lovell

    November 18, 2011 2:11 pm

    Nice bits of information, but I feel like you should have also addressed the various down-sides to this approach.

    For example from a security perspective it can pose several issues, because of all of the sites sharing the same Database.

    I feel that some users may use this article as a means by which to go down this road without a proper warning into the Cons, having been only provided the Pros.

    • 9

      There’s extra issues to think about with multisite, but I hope people reading your comment don’t think it is *less* secure. In many ways it is more secure.

      The down side to one install is, yes it;s still one install. Very easy to topple if you have no idea what you’re doing.

  7. 10

    Nice and useful articles, I think its more informational, if you list example websites

  8. 11

    Hi Kevin

    Nice post. I maintain few wordpress multi-site and these post very useful to improve the look of my blogs.

    Thanks for share.

  9. 12

    but…only my wpmultisite is getting an error for the the wp_recent_across_network() function?

    Invalid argument supplied for foreach()

    also in a vanilla twentyeleven theme.

  10. 13

    We’ve been using WP 3.x Mutlisite (formerly WPMU) for about 2.5 years now on an Alexa top 10,000 site with over 400,000 posts (excluding aggregated content) across 200 blogs/sites on one server. Here are my tips/critiques from actual experiences learned:

    We use an external cron job that runs every five minutes to refresh cached content. The cron job executes a script that calls a script on the web server that does the actual refresh because we need access to the WP multisite functions. This approach also keeps expensive queries from being run on the frontend that users use, which could easily bring a server to its knees at the moment when the content expires. The cron approach also creates slightly faster frontend load times, which means slightly better SEO. cron is a versatile solution for multisite problems. Note that I consider a two hour cache as unacceptable for large multisite installs like ours that is constantly changing – even five minutes is too long for some people.

    One really important thing you need to keep in mind is that WP already likes to cache EVERYTHING in RAM. Particularly problematic are permalink calculations across multiple blogs. WP will very quickly chug 128MB+ when calculating permalinks in a multisite environment due to its overly aggressive caching behavior. So, one of the things you might want to consider doing is writing a plugin that saves the full permalink for each post into the postmeta table and then write a function that first looks for a cached permalink for a post before doing the full calculation. To avoid doing the calculation during cron, have the plugin calculate the permalink while publishing/saving the post but don’t do the calculation during a post preview (post preview generates a bogus permalink). We had to do this here because WP kept using a ton of RAM during cron runs due to permalink calculations. After implementing this solution, WP went from running out of RAM to only consuming 10MB more beyond its base load during cron runs.

    Changing gears slightly, contrary to what is said on the WP site regarding plugins, plugin authors that fully understand the nuances between an ‘admin’ and a ‘site admin’ are few and far between. I’m of the opinion that there are NO published, free plugins truly compatible with both WP and WP Multisite since I’ve had to author most of our plugins from scratch and of those that I didn’t author, I still had to go in and modify the code. Therefore, knowing how to author plugins is an absolute must if you go the multisite route. I view managing Multisite as being significantly more technically challenging than plain WP.

    • 14

      Dunno where you’re looking for plugins, as there is plenty. :)

      I will say that someone who wrote a plugin for single WP without knowing a thing about multisite cannot expect it to work – depending on what the plugin does.

      But there are many plugins out there for multisite and in the plugin repo. I know because we write a lot of them.

  11. 16

    Nice and Useful article.Thank to share

  12. 17

    In line 30: “SELECT * FROM wp_blogs ” –> why you don’t use $base_prefix from line 22 for a general solution? –> “SELECT * FROM “. $base_prefix.”blogs “

  13. 18

    Just tried the “List Recent Posts Across an Entire Network” but didnt get any content. I’m using WP 3.3.

    didnt get any error, just an empty space.

    here is what i did:
    1. put the first code in the function file
    2. put the second (small) code in my index.php file.
    3. loaded the site, but nothing there…

    I’m no pro, so I’m probably missing some important stuff, so could anyone help me? Just want to build my own “Recent blog posts” on my front page :)

    • 19

      I have the post Expirator installed, and all is wnroikg fine.I would like to display my posts by the expiration date, which then sorts the posts, which are events, into their correct order, which isn’t post date.I’m having trouble getting the query in my theme page to work, and wondered if you can help.Here’s the current query, that is sorting by menu order (wnroikg with a post e-order plugin, that I’d like to get rid of):`$querystr = SELECT DISTINCT * FROM $wpdb->posts LEFT JOIN $wpdb->term_relationships ON($wpdb->posts.ID = $wpdb->term_relationships.object_id) LEFT JOIN $wpdb->term_taxonomy ON($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id) WHERE $wpdb->posts.post_type = event’ AND $wpdb->posts.post_status = publish’ AND $wpdb->term_taxonomy.taxonomy = event_cat’ AND ($subquery) GROUP BY $wpdb->posts.ID ORDER BY $wpdb->posts.menu_order ASC ;`Can you possibly suggest what I need to change/add?Many thanks for any help offered.

    • 20

      Err – yeah. The code hasn’t quite shown up right here, so returns a bunch of PHP errors – I tried fixing it, and got it to work fine, except for the last bit:

      `blog_id, $post->ID`

      I have no idea how he gets the permalink out of that.

  14. 21

    Useful article. Thank you for it. But I have some questions, the most important is that I would like to have a list of recent articles with blog author names (user_nicenames). Any hints?
    I tried to use your script but as a newbie, I’m lost in it.

  15. 22

    This could be useful. But I’m trying to figure out how to get the links to the posts in wp_recent_across_network. The code is all messed up in the article (even in the source code) and I just can’t seem to figure it out on my own. Does anyone know?

  16. 23

    Just like get some =thing clear about multisite:

    1. I am using single site at moment, and want to have seperate sites for a directory, makretplace, job board, etc. Will this work in single site, or will need to install ms?

    2. On each individual site, like, I have tried to install buddypress, but it goes to install bp on whole site. Is this where i need to install multisite, so i can not only have different sub-sites, but also install different plugins on separate sub-urls?

    3. for security, how is for multisite? will it make wp-ms as secure as single site?

  17. 24

    Thanks Kevin

    I´m just a beginner in wordpress blogging, so I have jet not tried this new functionality. Seems very useful and productive to manage multisites.

  18. 25

    Jason Newington

    May 23, 2012 2:27 am

    I’m looking at multisite but I’m worried about the ability to take the entire network down with one bad update . . . which let’s face it does happen albeit rarely.

    I also like the compartmentalism that comes separate installs. Knowing that each site can be added/removed/upgraded without impacting anything else is a plus.

    That said the savings in admin time alone makes multisite attractive.

    Tough decision.

  19. 26

    Justin Forrest

    June 7, 2012 2:40 pm

    Are there any benefits or downsides to seo using the multisite?

  20. 27

    I would really appreciate help in this-
    I want to provide hosting and maintenance to clients, for a few specific functions.
    How can I build a multisite that will have the option:
    1. for clients to open a new private site of their own from the regular main site without going into the dashboard. (To get a username and password from my site and that will open a platform for them to put input
    2. I want each site that will open to be in a fixed platform that will enable them to put in specific input and info according to what I create and determine ahead.

    Is that possible?

  21. 28

    Thanks Kevin

  22. 29

    Thanks very useful for getting up to speed on a multisite project.

  23. 30

    Thanks for the useful snippets…

    As of WP 3.5 two arguments are required in $wpdb->prepare ( causing a PHP warning: Missing argument 2 for wpdb::prepare() as called in List Recent Posts Across An Entire Network above.

    Any ideas on how to pass the second argument in that code?

    • 31

      I was able to fix the argument errors by replacing blog_id in line 30 with id = %d, $id.

      But I then get Warning: Invalid argument supplied for foreach() in /wp-content/themes/functions.php on line 77.

      Any ideas on how to fix this one?

  24. 32


    Could you help me with this task, please?

    I would like to modify my multi-site’s, ‘New Site’ registration options so that:-

    Only sites containing a ‘hyphene’ and/or ‘numbers’ can be registered!

    i.e, no single-word site addresses, only addresses that contain a (-) or numbers, or both.

    Thanking you in advance.



  25. 33

    I was recently introduced to wordpress MU and started digging into that system and it’s logic but one thing I couldn’t handle and it made me crazy so I had to give up this MU thing:
    “Featured Image” – there is not enough information about how to retrieve “featured image” from a post in a sub-site and display it in the main site.
    I tried millions of forums, codes, plugins and couldn’t manage to find even one decent solution!!

    there is no simple solution for that whatsoever.. even worse:
    when I found a plugin called MU sitewide tags pages
    which is not perfect but did some of the work..
    I had to twick it a little and somehow found a way of displaying “Featured Images” from sub-site in the main website.. but it was “working the way around it” and not the best solution , so when I had to use the very famous “Re-generate thumbnails” plugin it didnt work of course because it is not a wordpress native code.

    too bad there is not enough information and tutorials and every simple thing you do in a regular wordpress becomes a very big deal in wordpress MU


↑ Back to top