To test a plugin on multisite properly you need a real network, not a single site, and you need to test it in four states: network activated, activated on one subsite, active when a new subsite is created afterwards, and uninstalled. Most multisite bug reports come from exactly one of those four, and it is almost never the first one.
The advice you usually find is "check if the plugin says it is multisite compatible" or "test it before you deploy". That is not a routine. This is one, along with the specific places in the code where multisite bugs actually live.
Why a single-site test tells you almost nothing
On a single site there is one activation path, one options table, one admin, one uninstall. On a network there are at least three activation states, two options scopes, two admin screens, and an unbounded number of sites that did not exist when you activated.
None of that machinery exists on a single install, so a plugin can pass every single-site test you have and still be broken on a network. That is why the WordPress.org support forums are full of "works fine normally, breaks on multisite" threads.
The three activation states are worth naming because people conflate them:
- Network activated. Active on every site at once. Site administrators see it as "Network Active" and, per the multisite administration handbook, cannot deactivate it.
- Activated on one site. A site admin turns it on for their site only, and only if the Network Admin has allowed the plugins menu for site admins at all.
- A must-use plugin. Files in
mu-pluginsrun automatically. The handbook's line is blunt: "if they exist, they are used." They never appear in a site's plugin list.
The three places multisite bugs actually live
1. The activation routine runs once, not once per site
This is the big one. register_activation_hook() passes your callback a $network_wide boolean, and when the plugin is network activated the hook fires once for the whole network, not once per site.
So if your activation routine creates a table, seeds an option, or adds a capability, on a network activation it does that for the site the super admin happened to be on. Every other site in the network gets nothing.
Felix Arntz, a WordPress core committer, writes up the correct pattern: split the per-site work into its own function, check $network_wide, and loop the sites with switch_to_blog() when it is true. His post also flags the scale trap, which is that a site query has a default limit of 100, so a plugin that loops sites on activation quietly stops working on a network of 300.
2. The site created next week
A network activation loop covers the sites that exist at the moment you activate. It says nothing about the subsite someone creates a month later.
That site boots with your plugin network-active and none of your setup done. The fix is to hook wp_initialize_site (the modern replacement for wpmu_new_blog) and run the same per-site routine there.
This is the single most common multisite bug we see described as "it just stopped working on one site". It did not stop working. It never started.
3. Options, and which table they landed in
get_option() reads the current site's options table. get_site_option() reads the network's. A licence key almost certainly belongs on the network; a per-site display setting almost certainly does not.
Getting this wrong is rarely a crash. It is a settings page that silently forgets its values on every site except the one where you saved them, which is much harder to diagnose than a fatal.
While you are in there, check where the settings page renders. A network-wide plugin whose only settings screen is under a subsite's Settings menu is unusable for a super admin, and is_multisite() is the branch that decides.
One more trap worth knowing before you write the loop: switch_to_blog() switches the database context and nothing else. The reference is explicit that "PHP code loaded with the originally requested site, such as code from a plugin or theme, does not switch." So switching to a site whose theme defines a function does not give you that function.
The routine
You need a real subdirectory network with more than one subsite. A sandbox is a good fit here because you will want to throw it away and start clean between the four states, and a network you have already poked at is not a clean test.
Create a network. In SandyWP, tick Multisite when you create a sandbox and you get a real subdirectory network. Be aware it provisions cold rather than from the warm pool, so it takes longer than a normal sandbox to come up.
Add subsites. Over SSH or with the CLI, WP-CLI does it in one line each:
wp site create --slug=alpha --title="Alpha"
wp site create --slug=beta --title="Beta"
State one: network activated. Activate across the network, then check your setup ran on every site, not just the one you were on.
wp plugin activate my-plugin --network
wp option get my_plugin_version --url=example.com/alpha
wp option get my_plugin_version --url=example.com/beta
If the second command comes back empty, you have found bug number one from the list above, and you found it in about forty seconds.
State two: a site created after activation. Create a third subsite with the plugin still network active, then run the same check against it.
wp site create --slug=gamma --title="Gamma"
wp option get my_plugin_version --url=example.com/gamma
State three: activated on one subsite only. Start from a fresh sandbox for this one, because a network activation has already written state you do not want to inherit.
wp plugin activate my-plugin --url=example.com/alpha
Then confirm the plugin does nothing on the other subsites, and that its settings page appears under the subsite, not in Network Admin.
State four: uninstall. uninstall.php gets no $network_wide parameter, because by then the plugin files are being deleted. You check is_multisite() and loop.
Delete the plugin, then look for leftovers on every subsite. Orphaned tables and options are the multisite bug nobody notices until a client asks why the database has 400 rows of a plugin they removed last year.
Turn debug mode on before you start any of this. A network activation that half fails often produces a notice rather than a white screen, and you will never see it with WP_DEBUG off.
Subdirectory or subdomain?
Test on the one your users run. If you support both, the differences that matter for a plugin are cookie scope, home and siteurl shapes, and anything you do with URL rewriting.
SandyWP creates subdirectory networks only. Subdomain networks need wildcard DNS and per-subsite TLS, which do not fit one host per sandbox, so we do not pretend to offer them. The same applies to importing: a subdirectory network clones into a sandbox fine, and a subdomain network is rejected rather than silently converted.
If your plugin does subdomain-specific work, a subdirectory network will still catch the activation, options and uninstall bugs above, which is most of them. For the cookie and rewrite work you need a real subdomain network somewhere else.
What a sandbox will not tell you
Worth being straight about this, because a test that gives false confidence is worse than no test.
A sandbox will not reproduce scale. A network of five subsites will not surface the 100-site query limit, the activation routine that times out at 800 sites, or the network-wide cron that piles up. If you support large networks, you have to build a large network at least once.
It will not reproduce the host layer: object caching, edge caching, a WAF, open_basedir, or a stale OPcache serving your old file after a deploy.
It will not reproduce anything bound to a domain: licence checks tied to a customer's hostname, real payment gateways, OAuth callbacks, or email deliverability.
And on a SandyWP multisite sandbox specifically, the one-click reset to a clean install is not available. Delete it and create a new one instead, which is the honest workflow for the four-state routine anyway.
The short version
Network activation is not "activation, but everywhere". It is a different code path with a different hook signature, a different options scope, a different admin screen, and a set of sites that will exist in the future.
Test the four states, on a network you are willing to destroy between them. If you build plugins for a living, the plugin developer workflow covers the rest of the matrix: WordPress versions, PHP versions, and clean installs on demand.
