<!-- https://minecraftdocs.dev/systems/commands/permissions — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# Permissions

> Verified against **Minecraft 26.3** · Part XIII · You are a level-four operator, you type `/msg`, and the server's own answer to *may this player send a chat command* is **no** — because an operator's permission set grants exactly one thing that is not a command level.

Op yourself to four, the highest rung there is, and ask the server whether
you hold `Permissions.CHAT_SEND_COMMANDS` — one of five named capabilities
the class `Permissions` holds, each of them a `Permission.Atom`. It says no.
Not because you are restricted — because the set you were given is a
`LevelBasedPermissionSet`, and its answer to any permission that is not a
command level is *false*, with one hard-coded exception. The chat atoms
live only in the *client's* set, which the server never sees and never
sends. There are two permission universes in this game, and they overlap only where the client runs the server's checks, its rungs and its entity-selector atom, against its copy of your op level.

That is the shape of the model, and the thing to unlearn: **a permission is not an integer.** The five levels are there, numbered nought to four and stored as integers in *ops.json*, but a command node does not ask for a number. It asks a
`PermissionSet` a question, and a set is free to answer however it likes.

## The cast

| class | what it decides | where it lives |
|---|---|---|
| `Permission` | *what is being asked for*: a `Permission.Atom` (a named capability with an `Identifier`) or a `Permission.HasCommandLevel` (a rung) | both jars |
| `Permissions` | nine permissions as constants — four rungs and five atoms; the client mints one more of its own. The plural class is the catalogue; the singular one is the shape | both |
| `PermissionLevel` | the five rungs — *all*, *moderators*, *gamemasters*, *admins*, *owners* — and `PermissionLevel.isEqualOrHigherThan` | both |
| `PermissionSet` | *the answer*. An interface with one abstract method, `PermissionSet.hasPermission` | both |
| `LevelBasedPermissionSet` | the ordinary answer: an interface with five constants, one rung each | both |
| `PermissionSetUnion` | the OR of several sets, and the rule that a union may not contain a union | both |
| `PermissionCheck` | *what a node demands*: `PermissionCheck.Require` or `PermissionCheck.AlwaysPass` | both |
| `PermissionProviderCheck` | the `Predicate` Brigadier holds, over anything implementing `PermissionSetSupplier` | both |
| `ChatAbilities` | holds the client's own set, built by **subtraction** from local reasons | client only |

`net/minecraft/server/permissions` holds every row above but `ChatAbilities`, which is the client's and sits with the client's chat code: a small package that changes how everything above it is written.

## A question, an answer, and a check

Three types do three jobs that a single integer used to do, and keeping them
apart is what makes the rest legible.

```mermaid
classDiagram
    class PermissionProviderCheck {
        PermissionCheck test
    }
    class PermissionCheck {
        <<interface>>
        boolean check(PermissionSet)
    }
    class Require["PermissionCheck.Require"] {
        Permission permission
    }
    class AlwaysPass["PermissionCheck.AlwaysPass"]
    class PermissionSet {
        <<interface>>
        boolean hasPermission(Permission)
    }
    class Permission {
        <<interface>>
    }
    class Atom["Permission.Atom"] {
        Identifier id
    }
    class HasCommandLevel["Permission.HasCommandLevel"] {
        PermissionLevel level
    }
    class LevelBasedPermissionSet {
        <<interface>>
    }
    class ChatAbilities

    PermissionProviderCheck --> PermissionCheck : the check
    PermissionCheck <|.. Require
    PermissionCheck <|.. AlwaysPass
    Require ..> PermissionSet : asks the source's set
    Require --> Permission : the question
    Permission <|.. Atom
    Permission <|.. HasCommandLevel
    PermissionSet <|-- LevelBasedPermissionSet
    PermissionSet <|.. PermissionSetUnion
    ChatAbilities --> PermissionSet : holds one
```

*The three roles as types — the check a node holds at the top, the question it asks, and the set that answers; a hollow arrowhead reads as "is a kind of", a solid arrow as "holds", and the one dotted arrow without a hollow head is the single call that joins them.*

`Permissions` holds nine, and the split is four to five:
four `Permission.HasCommandLevel` constants — one per rung above zero — and
five `Permission.Atom`s, of which one is the entity-selector gate and the
other four are the chat abilities below.

The **question** is data: both shapes are records, both are codec-dispatched
over `BuiltInRegistries.PERMISSION_TYPE`, and `Permission.CODEC` accepts an
atom written as a bare identifier as well as the full dispatched form. The **answer** is behaviour: `PermissionSet` has one abstract method, so every set in the game above is a lambda or a small object, and there is no set-of-permissions data structure anywhere but `Permissions.CHAT_PERMISSIONS` and the client's chat code that copies it into `ChatAbilities`. The **check** is what
a command node carries: `Commands.hasPermission` wraps a `PermissionCheck`
in a `PermissionProviderCheck`, and that predicate is what Brigadier
consults.

**Ninety-seven call sites** of `Commands.hasPermission`, which is every requirement predicate on every command node in the game. Ninety-six are server-side
command registrations; the ninety-seventh is on the *client*, in
`ClientPacketListener`'s node builder.

Two things about `LevelBasedPermissionSet` decide most of this page. It is
an **interface with five constants**, not a class carrying a level, so
`LevelBasedPermissionSet.GAMEMASTER` is a singleton and comparing two sets
is comparing two references. And its `LevelBasedPermissionSet.hasPermission`
tests a `Permission.HasCommandLevel` against its own rung, grants
`Permissions.COMMANDS_ENTITY_SELECTORS` from gamemaster upward as a special
case written into the method, and returns **false to every other atom**.
An operator does not have everything; an operator has a number and one
exception.

Union is where that bites, and the rule is what its name says:
`PermissionSet.union` falls through to `PermissionSetUnion`, which ORs its
operands, so a set holding an atom and a set holding a rung together satisfy
both. The exception is the case the server uses.
`LevelBasedPermissionSet.union` of two *level-based* sets never builds a
`PermissionSetUnion` at all, and it does not take the higher of the two: both
branches of the override return the **lower**-levelled set, so it is a
minimum. `CommandSourceStack.withMaximumPermission` is that union, which for these sets takes the minimum and so works as the ceiling its name promises — which is how a function body is held down to gamemaster however high the caller sits
([functions and
macros](functions-and-macros#the-two-permission-verbs)).

## Where a set comes from

`MinecraftServer.getProfilePermissions` is the whole of it — in
singleplayer through its override, `IntegratedServer.getProfilePermissions`
— and it returns a `LevelBasedPermissionSet`, never a union, never an atom
set. It is consulted afresh every time `ServerPlayer.permissions` is called;
nothing is cached on the player.

Its cascade is short. Not on the operator list at all, and you get
`LevelBasedPermissionSet.ALL` — rung zero, and marked deprecated where it is declared. On the
list, and the entry's own set wins — `ServerOpListEntry` holds a
`LevelBasedPermissionSet`, built when the file was read from the integer the file stores, which for an entry `/op` wrote on a dedicated server is the *op-permission-level* property as it stood then (the management API's operator methods can write any level). Singleplayer's override answers
first. The owner gets `LevelBasedPermissionSet.OWNER` when the world allows
cheats and rung zero when it does not; any other player gets
`LevelBasedPermissionSet.GAMEMASTER` when the world allows cheats and the
host has switched on *Command Access* for other players, rung zero when
cheats are on without it, and the cascade above when cheats are off.

The integer survives at every edge of that model, which is worth knowing
before you go looking for a permissions file. *ops.json* stores a number.
*server.properties* stores a number. The op level reaches the client as one
of five values on `ClientboundEntityEventPacket`. And
`PermissionLevel.byId` **clamps** rather than failing, so an *ops.json*
hand-edited to level 9 is an owner and level −1 is rung zero. What does not
exist anywhere is a data pack that grants a permission:
`PermissionCheck.CODEC` has exactly one consumer in the whole game,
`ArgumentUtils.serializeNodeToJson` writing the generated command report,
and both type registries are bootstrapped in code by `PermissionTypes` and
`PermissionCheckTypes`.

## The requirement is consulted inside the parse

This is the design decision a server administrator feels most often, and it
is not a message-formatting choice. Brigadier evaluates a node's requirement
predicate *while walking the tree*, so a node you may not use is a node that
is not there. `Commands.getParseException` then reports an empty parse range
as an **unknown command** and a non-empty one as an unknown *argument*.

An unopped player typing `/give` is told there is no such command. The
server cannot tell them otherwise without a second parse, and it does not
do one.

One permission is not settled by the parse alone.
`Permissions.COMMANDS_ENTITY_SELECTORS` is tested at parse time *and* again
when the selector is resolved against the world, because a parsed selector
outlives its parse — which is exactly what an `/execute` chain relies on, and
why [entity selectors](entity-selectors#one-permission-checked-in-two-places-for-two-different-reasons)
owns the two checks and the route that reaches only the second.

Two more consequences worth naming. `Commands.LEVEL_MODERATORS` gates
**nothing**: the rung exists, is settable and is stored, and no vanilla
command asks for it. And the ninety-seven gates divide four ways. Ninety-three
name a level constant outright — sixty-eight gamemaster, sixteen admin, nine
owner. Two are the ternary in `SeedCommand` and `VersionCommand`, which drop
to `Commands.LEVEL_ALL` when the server is the integrated one, and those are
the only two appearances of that constant in the game. The last two name a
`PermissionCheck` rather than a rung: `GameModeCommand.PERMISSION_CHECK`,
which the end of this page is about, and the client's own synthetic
restricted-command check.

## What the client is allowed to believe

The client has permissions of its own, and they are not a copy of the
server's — no packet carries a `PermissionSet`. It has four sources of
belief, and they behave differently enough to be worth separating. Three of
them the server sent or the machine decided; the fourth is the server's own constants, which the two sides literally share.

**The op level**, which arrives on an entity event and is mapped by
`LocalPlayer.handleEntityEvent` onto one of the five sets — on an
integrated server too, where the host's own `LocalPlayer` learns its level
the same way. Note the bottom
rung: level zero maps to `PermissionSet.NO_PERMISSIONS`, not to
`LevelBasedPermissionSet.ALL`. The two are indistinguishable in practice —
rung zero satisfies no level and no atom either — but the client's copy is a
different object from the server's.

**The command tree**, whose nodes were filtered for this player before being
sent ([Brigadier and
commands](brigadier-and-commands#the-tree-on-the-wire) for what the packet
carries). Two elisions ride that packet and they mean different things.
A node whose requirement the player failed is simply **absent**, and the
filter is recursive, so a gated literal takes its whole subtree with it. A
node that a *no-permission* source would fail is **flagged**
(`ClientboundCommandsPacket.FLAG_RESTRICTED`) — "this needs some
permission", asserted independently of you. `Commands` computes that flag
against a source it builds once from `PermissionSet.NO_PERMISSIONS`, and
only for nodes that already survived your own filter.

**Its own atoms.** `ClientPacketListener` mints a single synthetic
`Permission.Atom` for restricted commands and keeps two sources over its one
dispatcher: the ordinary one, whose set is the player's own **OR-ed with**
that atom, so restricted nodes still highlight and complete; and a
no-permission one. `ChatAbilities` holds the other client-only set, and it is built the opposite way round from everything else here — it starts from all
four chat atoms *granted* and lets each `ChatRestriction` remove some. The
four are what this client will do at all: send messages, send commands,
accept messages from other players, and accept system messages; a client with
the first taken away refuses before the server is ever asked ([chat and
signing](../networking/chat-and-signing#questions-players-ask)). Three of the four `ChatRestriction`s that can remove them are decisions the machine you are sitting at makes — two settings of the chat option and a launcher flag — and the fourth, `ChatRestriction.DISABLED_BY_PROFILE`, comes
from the account service: a user flag fetched with your profile. The
client's chat permissions are never granted by the *game* server; they are
only ever taken away, and only ever from outside it.

**The server's own constants, read locally.** The client runs *server*
permission checks against its own set in several places —
`WorldOptionsScreen` gates *Allow Cheats* in a hardcore world on `Permissions.COMMANDS_OWNER` and its game-rule, game-mode and difficulty buttons on `Permissions.COMMANDS_GAMEMASTER`, and `KeyboardHandler` gates two debug keys and reads a third — and almost all of those are the
client asking its own copy a question the server will ask again later. One
is shared outright. `GameModeCommand.PERMISSION_CHECK`, a
`PermissionCheck.Require` for gamemaster exported from the command class, is
read by its own command registration, twice by `KeyboardHandler` — the second is why F3+F4 refuses to open the switcher at all, with *debug.gamemodes.error*, rather than opening a greyed-out one — once by `GameModeSwitcherScreen` against `LocalPlayer`'s own set when it sends the switch, and once more by `ServerGamePacketListenerImpl` when the packet arrives. One constant, five references in four classes, two sides of the network: the one `PermissionCheck` both sides read; the other permissions they share are the rungs and the entity-selector atom.

## Asking a question the client cannot answer

Put those two client sources together and you get the one thing the client
*can* diagnose: it can tell "you would need permission for this" apart from
"that is a typo", for a command it was asked to send on your behalf. A
dialog button and a chat click event both route through
`ClientPacketListener.sendUnattendedCommand`, whose two callers are
`Screen.clickCommandAction` and an adapter `ClientPacketListener` builds for
itself. A **sign does not**. A sign's click command reaches every client that loads the sign, as part of its text, but no client runs it: `SignBlockEntity` runs it on the server when the sign is used, and only when the sign's *allow_op_features* flag is set,
through a `CommandSourceStack` it builds itself at a
hard-coded `LevelBasedPermissionSet.GAMEMASTER`. There is nothing for the client to vet, because the client never sends it. The
figure is the route the other two take.

```mermaid
flowchart TD
    IN["an unattended command — a dialog button or a chat click"]
    IN --> P1{"parses with your set, restricted atom added?"}
    P1 -- no --> E1["PARSE_ERRORS"]
    P1 -- yes --> P2{"any signable argument?"}
    P2 -- yes --> E2["SIGNATURE_REQUIRED"]
    P2 -- no --> P3{"parses with NO_PERMISSIONS too?"}
    P3 -- no --> E3["PERMISSIONS_REQUIRED"]
    P3 -- yes --> OK["NO_ISSUES — sent, no screen"]
    E1 --> RUN["ConfirmScreen — its button sends it"]
    E3 --> RUN
    E2 --> SUG["ConfirmScreen — its button puts it in the chat box, or copies it"]
```

*`ClientPacketListener.verifyCommand`'s three tests in their order, and where each answer leads — only the clean command leaves with no screen, and the signed one is never sent from here at all.*

`ClientPacketListener.verifyCommand` parses the same string against both
sources and reads the *difference*. Succeeding with the ordinary source and failing with the no-permission one means some node on the path was gated, or an argument used a selector — which is as much as the
client can ever know, because it was never told which permission or whose.
Between the two parses sits a test that has nothing to do with permissions:
`SignableCommand.hasSignableArguments`. An unattended command cannot be
signed, so one that needs a signature is refused outright — its
`ConfirmScreen` offers to put the command in the chat box for you to send yourself,
or, when a screen is to follow or chat commands are off, to copy it.
Three of the four outcomes pop a confirmation screen; the fourth,
`ClientPacketListener.CommandCheckResult` *NO_ISSUES*, sends with no screen at all. So an unattended command that is
merely *unusual* is always shown to you first — but a clean one goes
straight out, and a sign's command never came this way to begin with.

Which answers the operator at the top of this page. Their `/msg` goes
through, and it goes through because nothing on the server's side of the
wire ever asks for `Permissions.CHAT_SEND_COMMANDS`: the `/msg` node carries
no requirement at all, the atom lives only in this client's `ChatAbilities`,
and the one participant that consults it is the machine the player is
sitting at. The server's *no* is real and it is never asked. That is what two permission universes buy — and where they overlap, at the rungs and the selector atom, is *The server's own constants, read locally* above.

> **For a 1.21-era reader.** *PlayerList.setAllowCommandsForAllPlayers* is gone:
> singleplayer answers through its own override, `IntegratedServer.getProfilePermissions`,
> which gives a guest gamemaster behind the host's `IntegratedServer.setGuestCommandAccess`
> toggle. And the host's `LocalPlayer` is no longer set directly by
> `IntegratedServer.publishServer`: it learns its level from the entity event like any
> other player.

## Where to look

`PermissionSet` first — seventeen lines, and the whole model is in them.
Then `LevelBasedPermissionSet` for the two special cases that decide
everything, `Permissions` for its nine, and `Commands.hasPermission` for the one idiom every requirement in a command registration uses.
`MinecraftServer.getProfilePermissions` for where a player's set is
decided, and `ClientPacketListener.verifyCommand` for the only place the client reasons about a permission it was never told about.

---

*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
