 Hey there, and welcome to Learn WordPress. In this tutorial, you'll learn about things to consider when developing themes or plugins for a WordPress multisite network. You'll learn about useful multisite-specific functions and hooks, and what to consider when developing themes and plugins to work on a multisite network. You'll also learn where to find more information on developing for multisite. Before we get started, it's worth noting that there are a couple of different naming conventions used in the WordPress codebase when it comes to a multisite network. WordPress Multisite was originally known as WordPress MU or WordPress Multi-User, and many multisite-related functions and hooks still use the WP-MU prefix. Additionally, some functions are named based on the old terminology, which described multiple blogs on a site. This has since been updated to describe multiple sites on a network instead, but some old terminology still lives on in some function names. When you're developing a product to support a multisite network, there are some useful internal functions and hooks worth knowing about. The first is the isMultisite function. This function will return true if multisite is enabled, and is probably the most widely used function related to multisite. If you do a search through the WordPress codebase for all the users of the isMultisite function, you'll see that it's used in a number of places, to either perform specific tasks in the context of a multisite network, or to restrict functionality only to multisite networks. There are also some common functions that are useful when developing administration interfaces for the multisite network. IsSuperAdmin can be used to check if the currently logged in user is a network administrator on the network. IsNetworkAdmin is the multisite equivalent of the isAdmin function, and determines whether the current request is for the network administrative interface. NetworkAdminUrl is the multisite equivalent of the admin URL function, and allows you to create URLs relative to the admin area of the network. This is useful for redirecting to different areas of the network admin dashboard. When working with site content, there are some functions that are widely used. IsMainSite determines whether the current site is the main site of the network or not. Next, there is the GetSites function, which will return a list of sites matching requested arguments. Then there is the SwitchToBlog function, which allows you to switch to a different site on the network. RestoreCurrentBlog, which restores the current site after you've switched to a different site. And GetCurrentBlogId, which returns the ID of the current site. Using these functions, you can perform actions across the network. For example, let's say you wanted to create a function that updated an option on a specific site on the network. Here, you're using the SwitchToBlog function to switch to that site by its ID. Then using the UpdateOption function to update the option by name, passing in the value. And then finally restoring to the current site on the network. However, if you wanted to extend that to update the same option across all sites, you could use the GetSites function and loop through all the sites on the network. So we could start by removing the site ID parameter, then get a list of sites using the GetSites function, and then loop through all the sites in a forEach loop. We can move this code into the loop and update the site by the BlogId property on the site object. You could also use the UpdateBlogOption function to update an option on a specific site without having to switch to that site. So instead of doing the switching, we can use UpdateBlogOption and we pass it in the site ID option name and value. In developing multi-site plugins, there is the isNetworkOnlyPlugin function. This is a plugin-specific function that checks for the network true value in the plugin header to see if this should be activated only as a network-wide plugin. This is useful if you want to restrict a plugin to only be activated on the network and not on individual sites. There are also a couple of useful hooks that you can use when developing for multi-site. The first is the NetworkAdminMenu hook, which allows you to add a menu item to the NetworkAdmin dashboard. The second is the NetworkAdminNotices hook, which allows you to add notices to the NetworkAdmin dashboard. This is the multi-site equivalent of the AdminNotices hook that is used for single-site notices. The SignUpBlogFormFolter is a filter that allows you to modify the SignUp form for new sites. You can use this to add additional fields to the SignUp form. Finally, WPInitializedSite is an action that is fired when a new site is created on the network. This is useful if you want to perform actions when a new site is created, for example if you wanted to assign a custom top-level domain to a new single site. When you are rendering any content in the scope of a site on the network, WordPress Core is clever enough to know that you are working inside the scope of that site. This means that any functions that you use to retrieve information, or any functions that you might use to add or update information, will get, add or update the correct tables for the site that you are currently working with. Additionally, if you use functions like RegisterPostType or RegisterTactonomy, these will also be registered for the current site only. Generally, themes and child themes work exactly the same on a multi-site network as they do on a single site. Once a theme or child theme is network activated, it can be activated on any single site on the network. All specific functionality that you may want to code in a functions.php file will work in the scope of the current theme. For example, if you wanted to display the site name in the footer of a theme, you could use the standard getBlogInfo function to retrieve the site name. So inside of our child themes functions.php, we can say getSiteName and then just return the name from the blogInfo. Then, anywhere where we want to get that name, for example in the footer pattern, we can simply use that function call and the site name will appear in the footer. If we test this on our sites, at the bottom of the BobPress site, we can see proudly powered by BobPress. At the bottom of the multi-press main site, we can see proudly powered by multi-press. However, let's say you wanted to include the main site name in the footer of the theme, regardless of which site was currently being viewed. You could use the switchToBlog function to switch to the main site, retrieve that site name, and then restore the current site to get the current site name. If we go back to our function, we can say current site name is going to be getBlogInfoName and then we could switch to blog and because we want to work with the main site, that'll be ID1. If we know that ID and then we can get the main site name using the same getBlogInfo call because we're now in the scope of the main site and then use restore current blog to restore back to the main site. And then we can do something like return site name and because that's part of the main site name network. And now that should appear in the footer of both sites. So let's test that. Let's check out the multi-press site. Proudly powered by multi-press, part of the multi-press network. And BobPress, proudly powered by BobPress, part of the multi-side network. Taking this one step further, perhaps you want to exclude the main site only from this custom functionality. You could use the isMainSite function to check whether the current site is the main site and if so, just return that site name. Back in our function, right at the top of the function, we could say if isMainSite then just return getBlogInfoName. Otherwise, if it's any other site on the network, build the custom name. And so if we test that on the multi-press site, the main site just says proudly powered by multi-press, but on the BobPress site, it includes the custom functionality. And all of this is possible just within one functions.php file inside of our child theme. As discussed, most plug-in functionality will work the same on a single site as well as multi-site. Functions like registerPostType or getPosts will function in the same way, just in the scope of the specific site in question. However, there are two things to consider when developing plugins for multi-site. Plugins will often have a settings page, which is usually accessible from the admin dashboard. This is fine for single site plugins, but on a multi-site network, you need to consider where the settings page should be located. Should it be in the network admin dashboard or on the individual site dashboard? If you need to have the settings page on the network admin dashboard, you need to use the network admin menu hook to add the menu item to the dashboard. If you need to have a settings page on the individual site dashboard, you use the regular admin menu hook to hook the menu item to the individual site dashboard. The other option to consider is whether or not your plugin uses any kind of custom tables or custom data. If you use something like WPDB prefix variable to prefix your table names, you'll end up with a table name that is prefixed with a site ID on a multi-site network. So if you need to have a custom table for this functionality on a per site basis, you need to plan for it. Let's look at an example. Here we have the plugin from the introduction to securely developing plugins tutorial. It has a form submissions table being created when the plugin is activated, which is used to store the form submissions. If you look at the code, you'll see that the table name is prefixed with the prefix property from the global WPDB object. In a single site install, this means that it will create one table using the prefix that is defined in the WPDB config file. In this example, that will be WP underscore form underscore submissions. However, on a multi-site network, depending on how the plugin is installed, it will create different tables. If the plugin is activated on a single site on the network, the table prefix will include the site ID. So for site ID 2, the table name will be WP2 form submissions. However, if the plugin is network activated, the activation process is running in the scope of the main site, and it creates the same table as if it's activated on a single site install. So for a network activation, the table name is WP form submissions. The problem comes in when you look at the code that stores the form submissions. Because this uses the same prefix property from the global WPDB object, when this code is run in the scope of the main site, it will look for the WP form submissions table to store the data. But for example, when it's run in the scope of site 2 on the network, it will look for WP2 form submissions in the database to store the data, which does not exist. To fix this, we need to update the plugin activation routine to take this into account. To start with, it's a good idea to move the table creation routine into a separate function, and then call this function from the activation hook. We'll take all of this code and create a new function, which just creates the table. In the notes in the doc for the register activation hook, you'll notice that the callback function accepts a network-wide parameter. This is a Boolean value that is passed to the activation hook callback, and indicates whether the plugin is being activated network-wide or not. You can then update the callback to first check if the site is a multi-site network, and if the plugin is being activated network-wide. So we'll pass in the network-wide parameter to receive either true or false. And then we'll check if is multi-site. In other words, this is a multi-site network, and whether this is being activated network-wide or not. If these two checks are true, fetch and loop through all the sites on the network, switch to each site in turn, and create the table for each site. So we can use the get-sites function that we used earlier to get all the sites on the network, set up a for-each loop to loop through the array of sites returned, and fetch each individual site as an object, then use the switch to blog function to switch to the site in question, and the property on the site object is blog ID to get the ID. Then we can run wp-learn-create-table to create the table in the context of the specific site in question, and then use the restore-current-blog function to restore back to the last blog. Alternatively, if the plugin is not being activated network-wide or not on a multi-site network, you can just create the table for the current single site. So here we'll do an else, and just call the create-table function. Place the site by activating this plugin on the network. You should see all the write tables being created. So let's network activate this plugin, and then if we refresh the database, there is wp-to-form-submissions, and there is the regular wp-form-submissions. But what happens when a new site is created? In this case, you'll need to use a hook-like wp-initialized site to create the table for any new sites on the network. So let's set up that functionality. We'll say add action, pass in the wp-initialized site hook, and then register our new site function, create the callback function. It receives a site object, and then we can use the switch-to-blog create table and restore-current-blog, the only difference being that the site ID is inside of the site object in this case as site ID. And the reason we need to do this is the user is probably registering the new site from the main site, so we need to make sure we switch to the new site ID that's just been created, create the table, and restore the blog. Test that out by creating a new site. It should create the new table for that site's form submissions in the database. So to do this, we need to make sure that we have allowed sites and user accounts to be registered, save those changes, and then if we log out and register on the network, and then say give me a site, we now have a new site created, and once the site has been activated, if we check the database, the table has been created for the new site. By making these changes, you allow your plugin to work on a multi-site network, both when it's activated for the first time on the network, taking into account any existing sites, but also future-proofing for any new sites. Besides the documentation on creating a network and things to consider before creating a network, there's not a lot of developer-focused documentation specific to developing for multi-site. However, it is possible to view a list of all multi-site-related functionality by browsing to the multi-site package URL section of the WordPress code reference. From there, you can filter by classes, functions, hooks, and class methods. Happy coding!