# Voice groups

Named voice channels players join from the in-game menu, their three types, config, permissions and commands

Source: https://socketsvc.vercel.app/server/groups
A voice group is a named channel. Everyone in it hears everyone else in it at full volume, with no distance, no world check and no visibility filter — you can be in the Nether and still be in the same group as someone in the Overworld. What changes between the three **types** is only what members hear of the players *around* them, and what those players hear of the group.

<Callout title="Off by default">
  Groups are disabled until you turn them on. `groups.enabled` is `false` in a fresh config, and while it is false the client is told there is no groups capability at all — the menu says so instead of offering a list nobody can join. See [The handshake](#the-handshake).
</Callout>

## Turning them on

```yaml title="plugins/SocketSVC/config.yml"
groups:
  enabled: true
```

Then `/ssvc-server reload`. Routing changes take effect at once; the capability itself is negotiated per connection, so players who were already connected pick it up when they next reconnect — which a reload that changes the listener address or public URL does for them, and which happens on their own next join otherwise.

## Types

| Type | Members hear nearby players who are in no group | Those players hear the group |
| --- | --- | --- |
| `isolated` | No | No |
| `normal` | Yes | No |
| `open` | Yes | Yes |

`normal` is the default and is deliberately one-way: "I can hear the room but the room cannot hear me" is what a commentary booth or a staff channel that still follows the game actually needs, and it is not something a single on/off switch can express.

Two **different** groups never hear each other, whatever their types. Joining a group is also a statement about who you are not talking to.

The type is a property of the group, not of the player, and it can be changed while people are in it — by its creator, by anyone with `socketsvc.groups.admin`, or with `/ssvc-server groups type`. The change is audible on the next relayed frame.

### What a group does not bypass

- **Deafen.** A player who has deafened themselves still hears nothing.
- **Permissions.** `socketsvc.speak` and `socketsvc.listen` are checked exactly as they are outside a group.
- **Spatial speakers.** A speaker carrying a player's microphone through the [spatial audio API](/server/spatial-audio-api) is filtered by that player's group too, so an isolated group's member speaking into a world speaker is not heard by the room the speaker stands in.
- **The soundboard.** A clip follows its player's audibility, group included.

## Config

| Field | Default | Behaviour |
| --- | --- | --- |
| `groups.enabled` | `false` | The master switch. False means no capability is negotiated, no command is accepted and routing is exactly what it was before groups existed |
| `groups.max-groups` | `32` | How many groups may exist at once, server-wide (clamped to 1–256) |
| `groups.max-members` | `16` | How many players may be in one group (clamped to 2–256) |
| `groups.allow-player-created` | `true` | Whether players may create groups. False still lets addons, the console and `/ssvc-server groups create` make them, so a server can offer a fixed set of channels |
| `groups.allow-passwords` | `true` | Whether a *player-created* group may carry a join password |
| `groups.allow-persistent` | `false` | Whether a *player-created* group may outlive its last member |
| `groups.allow-invites` | `true` | Whether a member may invite another connected player into their group. Off hides the button on every client |
| `groups.invite-expiry-seconds` | `120` | How long an unanswered invitation stands, clamped to 15–900 |
| `groups.default-type` | `normal` | The type a new group gets when its creator does not pick one; one of `isolated`, `normal`, `open` |

All of them support `/ssvc-server reload`. An unknown `default-type`, a non-boolean switch or a non-integer limit is rejected like any other invalid value, leaving the running settings untouched — see [Hot reload](/server/configuration#hot-reload).

**Turning groups off disbands every live group.** Leaving them standing would keep routing audio through a feature you have just switched off, and a client reconnecting afterwards no longer negotiates the capability, so it would have no way to leave the group it was still in.

## Invitations

A member of a group may invite any other player who is connected to voice. The invitation appears at the top of the invitee's groups menu with a countdown, and as a chat line so it is seen by somebody who was not looking at the menu.

**Accepting bypasses the group's password.** That is deliberate and is the reason the feature exists: without it, a private group is only private until its password has been pasted into chat once. An invitation is consent from inside the group, granted per person and revocable by the group emptying or being disbanded.

What bounds it:

- Only a **member** may invite, and only into the group they are in. Someone in no group has nobody to invite anyone to.
- **Nothing happens to the invitee until they accept.** Declining tells nobody. This is what separates it from the moderator-only `move`, which does not ask.
- A second invitation to the same group is **refused, not stacked**, and one inbox holds at most **8** unanswered invitations — so being invitable cannot become a way to fill somebody's screen. Neither is configurable; `allow-invites` is the switch.
- Invitations die with their group, including the quiet case where a non-persistent group empties out, and an invitee who disconnects loses their inbox.
- Every other rule still applies on accept. A group that filled up or was disbanded while the invitation stood refuses it exactly as it would refuse anyone.

<Callout type="warn" title="The handshake version moved">
  Invitations changed the groups wire format, so `X-SocketSVC-Groups` is now version `2`. The server only negotiates the capability on an exact match, which means a client from before invitations — and a newer client against an older server — sees "this server has voice groups switched off" rather than a broken menu. Update both sides together.
</Callout>

## Permissions

| Node | Default | Allows |
| --- | --- | --- |
| `socketsvc.groups` | everyone | Seeing the menu, joining and leaving groups |
| `socketsvc.groups.create` | everyone | Creating a group |
| `socketsvc.groups.admin` | op | Joining a password-protected group without the password, managing anyone's group (rename, type, password, persistence, kick, hand on, disband), and moving other players between groups from the in-game menu |

A group's **owner** — the player who created it, or whoever it was handed to — may manage that one group without the admin node: rename it, change its type and password, choose whether it stays open, kick members, hand it to another member and disband it. What an owner cannot do is pull anybody in; moving players is the admin node's alone. Handing a group on moves those rights with it, and the previous owner keeps only their seat. The `allow-passwords` and `allow-persistent` switches apply to an owner's edits exactly as they do to creating a group.

Leaving a group is exempt from `socketsvc.groups`, and has to be: a player who was already in one when the node is taken away would otherwise be stuck there, since the permission that is gone is the one that would let them ask to leave. Their menu keeps the Leave button and nothing else.

Permissions are read once per tick into the same cache the relay reads, not per audio frame. `socketsvc.groups` and `socketsvc.groups.create` fail open for a player the cache has not seen yet (mid-join); `socketsvc.groups.admin` fails closed, because an unticked player must never briefly hold administrative reach over other people's groups.

The server tells each client what that particular player may do as part of the group snapshot, so the menu hides the buttons that would be refused rather than offering them and failing. That is a courtesy, not the enforcement: every request is checked again on arrival.

On Fabric/Quilt these nodes go through [fabric-permissions-api](https://github.com/lucko/fabric-permissions-api) (LuckPerms and friends); on NeoForge/Forge through their own `PermissionAPI`. Without a permission mod installed, `socketsvc.groups.admin` falls back to operator level 2 — the same people who can run `/ssvc-server`.

## Commands

`/ssvc-server groups` manages groups from the console or in game. It needs `socketsvc.admin`, like every other `/ssvc-server` subcommand.

| Command | Purpose |
| --- | --- |
| `/ssvc-server groups` or `groups list` | Every live group with its type, flags, member count and members |
| `/ssvc-server groups create <name> [type] [password]` | Create an empty group. Console-created groups are persistent, since an empty non-persistent group would be disbanded the moment it was made |
| `/ssvc-server groups remove <group>` | Disband a group and return everyone in it to proximity chat |
| `/ssvc-server groups type <group> <isolated\|normal\|open>` | Change a live group's type |
| `/ssvc-server groups add <player> <group>` | Put a player into a group without asking for its password |
| `/ssvc-server groups kick <player>` | Take a player out of whatever group they are in |

`<group>` accepts either the name people actually use (case-insensitively) or the id the list prints, for the case where two names differ only in case. Tab completion offers the actions, then online player names, group names or types depending on which action it is.

```text title="Example output"
> /ssvc-server groups
SocketSVC voice groups: 2/32 group(s), 3 player(s) in one
  Commentary - normal, 1/16 member(s)
    id 3f2b1c8e-2b3a-4d51-9a77-1c0e4d9f2a61
    Alex
  Staff - isolated, password, persistent, 2/16 member(s)
    id 9c41d20a-51b7-4f7e-8c2a-77b0ee31b5d4
    Steve, Zuri
```

`/ssvc-server status` also reports the group count, how many players are in one, and the default type — or `disabled`.

## The handshake

The groups capability is negotiated on the WebSocket upgrade, like every other SocketSVC extension:

```text
X-SocketSVC-Groups: 2
```

The server echoes that header **only while `groups.enabled` is true** and only on a connection that also negotiated `X-SocketSVC-Status: 1`, which carries the text channel these messages travel on. That is what lets a client tell "this server does not do groups" apart from "this server has not replied yet", and it is why the menu can say so in one sentence rather than letting someone fill in a form whose request would be dropped on arrival.

Managing a group — renaming, passwords, persistence, kicking and handing it on — is a second, optional header beside it:

```text
X-SocketSVC-Group-Edit: 1
```

It is echoed only alongside `X-SocketSVC-Groups`, and only to a client that sent it. It is a header of its own rather than a new groups version because the snapshot did not change, and a version bump would have cost every mixed client/server pair its groups entirely; an older client simply never sends the new requests, and a newer client against an older server keeps its management screen to what that server can do.

The server does not rely on that politeness. A group message arriving on a connection that did not negotiate the capability is closed as an unsupported control message, exactly as it was before groups existed. See [Voice protocol](/server/voice-protocol#groups-extension-v1) for the message format.

<Callout type="warn" title="Reverse proxies">
  A proxy that strips custom upgrade headers turns groups off for everyone behind it, with no error — the client simply never sees the capability.
</Callout>

## For addons

`SocketSvcApi` (Paper) and `SocketSvcModApi` (server mods) both carry the group methods as of API version 2. They read and write the same registry the relay itself reads, so an addon and the audio path can never hold two different ideas of who is in what.

```java
SocketSvcApi api = Bukkit.getServicesManager().load(SocketSvcApi.class);
if (api != null && api.isVoiceGroupsEnabled()) {
    UUID staff = api.createVoiceGroup("Staff", null, VoiceGroupType.ISOLATED);
    api.addToVoiceGroup(player.getUniqueId(), staff);
}
```

- A group an addon creates is persistent and has no owner: it survives having nobody in it, and the `allow-player-created` / `allow-passwords` / `allow-persistent` switches do not apply, because those exist to restrain players rather than the operator.
- `addToVoiceGroup` never asks for the password, and takes the player out of whichever group they were in.
- Everything is safe to call from any thread, and answers as though groups were off while a reload is swapping the listener.
- The password itself never leaves the server: [`VoiceGroupInfo`](/api/shared/VoiceGroupInfo) carries only whether there is one.

See [`SocketSvcApi`](/api/paper/SocketSvcApi), [`SocketSvcModApi`](/api/server-mod/SocketSvcModApi), [`VoiceGroupInfo`](/api/shared/VoiceGroupInfo) and [`VoiceGroupType`](/api/shared/VoiceGroupType) for the full reference.

## Lifetime

- A group with nobody left in it is disbanded, unless it is persistent.
- Leaving the game leaves the group. Nobody can see a disconnected player in a member list to remove them, and a non-persistent group they were alone in should end with them.
- Nothing is written to disk. A group is a conversation, not a setting; a restart starts the server with none, and the ones an addon or a startup script creates come back because something recreated them.