0+
May 27, 2026
May 11, 2026
On a large multisite network, granting a group of people the same role across many sites usually means adding each user to each site by hand — and remembering to remove them everywhere when they leave. User Teams turns that into:
User Teams is multisite-only by design — on a single-site install, native WordPress roles and capabilities already cover the whole feature set, so WordPress won’t offer it for activation there.
user_has_cap filter, so removing a user from a team drops their access on the next request — no stale capabilities usermeta to clean up.wp_users rows (“team accounts”), so WP_Users_List_Table, is_user_member_of_blog(), and every column-adding plugin “just work”.Team-derived capabilities are merged in at runtime, never written into the member user’s own meta:
user_has_cap — adds the team role’s capabilities to the user’s effective caps, plus role-{slug} for plugins that check by role.get_blogs_of_user — adds team-linked sites to the user’s “My Sites” navigation.get_user_metadata — returns an empty-array {prefix}{blog_id}_capabilities for team members so is_user_member_of_blog() returns true without stamping real capabilities on the user. Workaround for Core #65096.The user’s actual {prefix}capabilities usermeta is never modified. That’s what makes team-based access instantly revocable.
manage_network_users — super admin only.promote_users, the same cap as inviting an individual user there.Team management is restricted to super admins so a compromised single-site admin can’t grant themselves access across the network.
user_teams_team_caps_for_user( $caps, $user_id, $blog_id ) — modify the capability map computed from a user’s teams.user_teams_team_applies_to_site( $applies, $team_id, $blog_id, $team ) — gate coverage (e.g. pause a team during a freeze).user_teams_team_save_data( $data, $team_id_or_null, $op ) — filter sanitised input on create/update.No installation instructions provided.
No. On a single-site install, native roles and capabilities already cover the whole feature set, so the plugin is marked Network: true and WordPress won’t offer it for activation.
Nothing is persisted on the user, so removing them from the team drops the team-derived capabilities on the next request. The user’s own native role on each site is untouched.
Each team is a real wp_users row (a “team account”) marked with user_teams_is_team meta. Per-site role grants use native wp_{blog_id}_capabilities meta on that row. Memberships are on the member user’s own user_teams meta. No custom tables.
use dd32\WordPress\UserTeams\Plugin;
$id = Plugin::create_team( 'Meta Team', 'meta-team', 'editor' );
Plugin::add_team_to_site( $id, 1, 'editor' );
Plugin::add_team_to_site( $id, 4, 'author' );
Plugin::add_user_to_team( $user_id, $id );
Checking access uses the normal cap system — team-derived caps participate via user_has_cap:
if ( user_can( $user_id, 'edit_others_posts' ) ) {
// ...
}
On GitHub at dd32/wp-user-teams. That’s where the source, issues, pull requests, and release tags live.
File it on GitHub. General support questions belong in the WordPress.org Support Forums.
Please report security issues privately via GitHub Security Advisories. Do not file public issues or post to the support forums for security reports.
pre_user_query team-member injection to the admin Users screens so a public WP_User_Query (e.g. the REST users collection) can’t be broadened by ?team=N.