AgentStack
SKILL verified MIT Self-run

Wp Multisite

skill-mralaminahamed-wp-dev-skills-wp-multisite · by mralaminahamed

Use 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 sites with get_sites() + switch_to_blog() / restore_current_blog(), super admin capabilities (is_super_admin(), manage…

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-mralaminahamed-wp-dev-skills-wp-multisite

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Wp Multisite? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

WordPress Multisite Plugin Development

> 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.

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.

When to use

  • "Make my plugin multisite compatible", "support network activation".
  • "Add a network admin settings page", "store a network-wide option".
  • "Loop over all sites and do X", "run a task on every blog".
  • "Why does my plugin break on multisite?", "fix table prefix issues".
  • "Check if a user is super admin", "restrict to network admin only".

Not for: WP-CLI multisite operations — use wp-wpcli-and-ops (official skill). General plugin architecture — use wp-plugin-development.

Method

1. Detection and guarding

// Is this a multisite network?
if ( is_multisite() ) { ... }

// Is the current screen the network admin?
if ( is_network_admin() ) { ... }

// Is the plugin network-activated?
if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) { ... }

// Is the user a super admin?
if ( current_user_can( 'manage_network' ) ) { ... }  // preferred
if ( is_super_admin() ) { ... }                       // also fine

Never assume is_multisite() is false — always write code that handles both cases unless the plugin explicitly requires multisite.

2. Activation scope

A plugin can be:

  • Site-activated — active on one site, hooks run only on that site.
  • Network-activated — active on all sites, activation hook runs once on the network.
register_activation_hook( __FILE__, 'my_plugin_activate' );

function my_plugin_activate( $network_wide ) {
    if ( $network_wide && is_multisite() ) {
        // Run setup for every existing site
        $sites = get_sites( [ 'number' => 0, 'fields' => 'ids' ] );
        foreach ( $sites as $site_id ) {
            switch_to_blog( $site_id );
            my_plugin_setup_site();
            restore_current_blog();
        }
    } else {
        my_plugin_setup_site();
    }
}

// Also run setup when a new site is created (for network-activated plugins)
add_action( 'wp_initialize_site', function( WP_Site $new_site ) {
    if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) {
        switch_to_blog( $new_site->blog_id );
        my_plugin_setup_site();
        restore_current_blog();
    }
} );

3. Option storage: site vs network

| Function | Scope | Storage | |---|---|---| | get_option() / update_option() | Current site | {prefix}options (per site) | | get_network_option() / update_network_option() | Entire network | {main_prefix}sitemeta | | get_site_meta() / update_site_meta() | Per-site record | {main_prefix}blogmeta |

// Network-wide setting (same value for all sites)
$api_key = get_network_option( null, 'my_plugin_api_key' );
update_network_option( null, 'my_plugin_api_key', sanitize_text_field( $key ) );

// Per-site setting (different value per site)
$setting = get_option( 'my_plugin_site_setting', 'default' );
update_option( 'my_plugin_site_setting', $value );

// Per-site metadata on the site object
$site_note = get_site_meta( get_current_blog_id(), 'my_plugin_note', true );
update_site_meta( get_current_blog_id(), 'my_plugin_note', sanitize_textarea_field( $note ) );

4. Network admin settings page

// Register under network admin menu
add_action( 'network_admin_menu', function() {
    add_menu_page(
        __( 'My Plugin Network', 'my-plugin' ),
        __( 'My Plugin', 'my-plugin' ),
        'manage_network',             // super admin only
        'my-plugin-network',
        'my_plugin_render_network_page'
    );
} );

// Network admin settings must use wp_redirect — Settings API not available in network admin
add_action( 'network_admin_edit_my_plugin_network_settings', function() {
    check_admin_referer( 'my_plugin_network_settings' );
    if ( ! current_user_can( 'manage_network' ) ) wp_die( -1 );

    update_network_option( null, 'my_plugin_api_key',
        sanitize_text_field( wp_unslash( $_POST['api_key'] ?? '' ) )
    );

    wp_redirect( add_query_arg( [ 'updated' => 'true' ], network_admin_url( 'settings.php?page=my-plugin-network' ) ) );
    exit;
} );

function my_plugin_render_network_page() {
    if ( isset( $_GET['updated'] ) ) {
        echo '' . esc_html__( 'Settings saved.', 'my-plugin' ) . '';
    }
    $api_key = get_network_option( null, 'my_plugin_api_key', '' );
    ?>
    
        
        ">
            
            
                
                    
                    " class="regular-text" />
                
            
            
        
    
     0,     // no limit — consider chunking for large networks
        'fields'     => 'ids',
        'archived'   => 0,
        'deleted'    => 0,
        'spam'       => 0,
    ] );

    foreach ( $sites as $site_id ) {
        switch_to_blog( $site_id );
        try {
            $callback( $site_id );
        } finally {
            restore_current_blog(); // always restore, even on exception
        }
    }
}

// Usage
my_plugin_run_for_all_sites( function( $site_id ) {
    update_option( 'my_plugin_version', MY_PLUGIN_VERSION );
} );

Chunked loop for large networks:

$page  = 1;
$limit = 100;
do {
    $sites = get_sites( [ 'number' => $limit, 'offset' => ( $page - 1 ) * $limit, 'fields' => 'ids' ] );
    foreach ( $sites as $site_id ) {
        switch_to_blog( $site_id );
        my_plugin_process_site();
        restore_current_blog();
    }
    $page++;
} while ( count( $sites ) === $limit );

6. Custom DB tables on multisite

Each site has its own table prefix. Create per-site tables in the setup function (called per-site in activation):

function my_plugin_setup_site() {
    global $wpdb;
    $table_name      = $wpdb->prefix . 'my_plugin_data'; // $wpdb->prefix is site-specific
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        data longtext NOT NULL,
        PRIMARY KEY (id)
    ) {$charset_collate};";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
}

For network-wide tables (one table shared by all sites), use $wpdb->base_prefix:

$network_table = $wpdb->base_prefix . 'my_plugin_network_log';

7. Capabilities on multisite

// Super admin (network administrator) check
if ( current_user_can( 'manage_network' ) ) { ... }

// Site admin on the CURRENT site
if ( current_user_can( 'manage_options' ) ) { ... }

// Grant a cap only to super admins (can't remove built-in super admin caps)
add_filter( 'user_has_cap', function( $caps, $cap_to_check, $args, $user ) {
    if ( 'manage_network_plugins' === $cap_to_check && ! is_super_admin( $user->ID ) ) {
        $caps['manage_network_plugins'] = false;
    }
    return $caps;
}, 10, 4 );

Notes

  • switch_to_blog() is expensive — it changes $wpdb->prefix, flushes object cache per-site, and swaps several globals. Minimise calls; batch work per site.
  • Always wrap switch_to_blog() / restore_current_blog() in try/finally to guarantee restoration even on errors.
  • Avoid BLOG_ID_CURRENT_SITE constant — use get_current_blog_id() and get_main_site_id() instead.
  • Network admin pages can't use the WordPress Settings API (register_setting, add_settings_section) — handle saves via network_admin_edit_{action} hooks with manual wp_redirect.
  • On large networks (1000+ sites), avoid get_sites( [ 'number' => 0 ] ) — chunk with number/offset or use Action Scheduler (wp-background-processing) to process sites asynchronously.

References

  • references/multisite-patterns.md — detection helpers, site switching, options API, get_sites() params, table prefix, capabilities, activation hooks.
  • references/network-admin-patterns.md — network admin menu, network settings save via networkadminedit_{action}, network option storage, network-wide transients.
  • references/multisite-testing.md — PHPUnit multisite bootstrap, data-isolation tests, network-activation tests, new-site hook tests, gotchas.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.