# Wp Multisite

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

- **Type:** Skill
- **Install:** `agentstack add skill-mralaminahamed-wp-dev-skills-wp-multisite`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [mralaminahamed](https://agentstack.voostack.com/s/mralaminahamed)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [mralaminahamed](https://github.com/mralaminahamed)
- **Source:** https://github.com/mralaminahamed/wp-dev-skills/tree/trunk/skills/wp-multisite
- **Website:** https://github.com/mralaminahamed/wp-dev-skills/blob/trunk/README.md

## Install

```sh
agentstack add skill-mralaminahamed-wp-dev-skills-wp-multisite
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

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

```php
// 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.

```php
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` |

```php
// 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

```php
// 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:**
```php
$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):

```php
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`:
```php
$network_table = $wpdb->base_prefix . 'my_plugin_network_log';
```

### 7. Capabilities on multisite

```php
// 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 network_admin_edit_{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.

- **Author:** [mralaminahamed](https://github.com/mralaminahamed)
- **Source:** [mralaminahamed/wp-dev-skills](https://github.com/mralaminahamed/wp-dev-skills)
- **License:** MIT
- **Homepage:** https://github.com/mralaminahamed/wp-dev-skills/blob/trunk/README.md

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

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-mralaminahamed-wp-dev-skills-wp-multisite
- Seller: https://agentstack.voostack.com/s/mralaminahamed
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
