$npx -y skills add mralaminahamed/wp-dev-skills --skill wp-multisiteUse when building or adapting a WordPress plugin for Multisite/Network — network activation (register_activation_hook with $network_wide), network admin pages (network_admin_menu), per-site vs network-wide options (get_option / get_network_option / update_network_option), looping
| 1 | # WordPress Multisite Plugin Development |
| 2 | |
| 3 | > **Model note:** Adapting an existing plugin for multisite involves scattered conditional changes — requires reading across many files to find all `get_option`/`update_option` and activation hooks. Use `sonnet`; `haiku` may miss indirect callers. New builds with multisite in mind from the start are straightforward and can use `haiku`. |
| 4 | |
| 5 | Adapt and build plugins that work correctly on WordPress multisite networks. Covers activation scope, option storage, network admin UI, capability model, and safe site-switching patterns. |
| 6 | |
| 7 | ## When to use |
| 8 | |
| 9 | - "Make my plugin multisite compatible", "support network activation". |
| 10 | - "Add a network admin settings page", "store a network-wide option". |
| 11 | - "Loop over all sites and do X", "run a task on every blog". |
| 12 | - "Why does my plugin break on multisite?", "fix table prefix issues". |
| 13 | - "Check if a user is super admin", "restrict to network admin only". |
| 14 | |
| 15 | **Not for:** WP-CLI multisite operations — use `wp-wpcli-and-ops` (official skill). General plugin architecture — use `wp-plugin-development`. |
| 16 | |
| 17 | ## Method |
| 18 | |
| 19 | ### 1. Detection and guarding |
| 20 | |
| 21 | ```php |
| 22 | // Is this a multisite network? |
| 23 | if ( is_multisite() ) { ... } |
| 24 | |
| 25 | // Is the current screen the network admin? |
| 26 | if ( is_network_admin() ) { ... } |
| 27 | |
| 28 | // Is the plugin network-activated? |
| 29 | if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) { ... } |
| 30 | |
| 31 | // Is the user a super admin? |
| 32 | if ( current_user_can( 'manage_network' ) ) { ... } // preferred |
| 33 | if ( is_super_admin() ) { ... } // also fine |
| 34 | ``` |
| 35 | |
| 36 | Never assume `is_multisite()` is false — always write code that handles both cases unless the plugin explicitly requires multisite. |
| 37 | |
| 38 | ### 2. Activation scope |
| 39 | |
| 40 | A plugin can be: |
| 41 | - **Site-activated** — active on one site, hooks run only on that site. |
| 42 | - **Network-activated** — active on all sites, activation hook runs once on the network. |
| 43 | |
| 44 | ```php |
| 45 | register_activation_hook( __FILE__, 'my_plugin_activate' ); |
| 46 | |
| 47 | function my_plugin_activate( $network_wide ) { |
| 48 | if ( $network_wide && is_multisite() ) { |
| 49 | // Run setup for every existing site |
| 50 | $sites = get_sites( [ 'number' => 0, 'fields' => 'ids' ] ); |
| 51 | foreach ( $sites as $site_id ) { |
| 52 | switch_to_blog( $site_id ); |
| 53 | my_plugin_setup_site(); |
| 54 | restore_current_blog(); |
| 55 | } |
| 56 | } else { |
| 57 | my_plugin_setup_site(); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // Also run setup when a new site is created (for network-activated plugins) |
| 62 | add_action( 'wp_initialize_site', function( WP_Site $new_site ) { |
| 63 | if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) { |
| 64 | switch_to_blog( $new_site->blog_id ); |
| 65 | my_plugin_setup_site(); |
| 66 | restore_current_blog(); |
| 67 | } |
| 68 | } ); |
| 69 | ``` |
| 70 | |
| 71 | ### 3. Option storage: site vs network |
| 72 | |
| 73 | | Function | Scope | Storage | |
| 74 | |---|---|---| |
| 75 | | `get_option()` / `update_option()` | Current site | `{prefix}options` (per site) | |
| 76 | | `get_network_option()` / `update_network_option()` | Entire network | `{main_prefix}sitemeta` | |
| 77 | | `get_site_meta()` / `update_site_meta()` | Per-site record | `{main_prefix}blogmeta` | |
| 78 | |
| 79 | ```php |
| 80 | // Network-wide setting (same value for all sites) |
| 81 | $api_key = get_network_option( null, 'my_plugin_api_key' ); |
| 82 | update_network_option( null, 'my_plugin_api_key', sanitize_text_field( $key ) ); |
| 83 | |
| 84 | // Per-site setting (different value per site) |
| 85 | $setting = get_option( 'my_plugin_site_setting', 'default' ); |
| 86 | update_option( 'my_plugin_site_setting', $value ); |
| 87 | |
| 88 | // Per-site metadata on the site object |
| 89 | $site_note = get_site_meta( get_current_blog_id(), 'my_plugin_note', true ); |
| 90 | update_site_meta( get_current_blog_id(), 'my_plugin_note', sanitiz |