Moderation
Bans and kicks with a reason, an end, and a screen the player actually reads — one list, not two.
This page is older than Keystone 0.2.8, released 18 October 2026. Something may have changed since.
On this page 6 parts
Moderation
Bans and kicks that carry a reason, an end, and a screen the player actually
reads. All of it in moderation.toml and on the Players screen in the editor.
The commands
#/keystone ban <player> [for how long] [reason]
/keystone unban <player>
/keystone kick <player> [reason]
/keystone bans
/keystone history <player>
The length is an ordinary duration — 7d, 12h, 30m. Without one the ban
does not end. The reason is what the player is shown, so it is worth writing for
them rather than for you.
/keystone bans lists what still stands. /keystone history <player> lists
everything this server has ever done to somebody, including what has expired.
The permissions are keystone.ban and keystone.kick, separately. They apply to
the game's own /kick, /ban, /pardon and /banlist too when Keystone takes
them over (see below). A moderator who is not an operator cannot kick or ban an
operator, or anybody in a heavier group than their own.
The player really reads the reason. Every kick, ban, maintenance close,
restart and failed sign-in now reads the client dry first, sends the reason and
only then closes the connection. Otherwise Linux resets the connection and the
player reads Connection reset by peer instead of why. A Discord ban no longer
disconnects somebody off the server thread.
The game's own /ban and /kick
#moderation.take-over-vanilla = true
On (the default): the commands people's fingers already know write into this ledger, end on their own date, and show the screen you wrote.
Off: they are the game's again — which means a ban typed as /ban is
invisible to /keystone bans, never expires, and shows "You are banned from this
server." Leaving this off is choosing to have two ban lists.
The screen a player is shown
#Three of them belong here, and all are overridable in operations.toml under
screens:
| Screen | When |
|---|---|
screens.banned |
A ban with no end |
screens.banned-until |
A ban with a date |
screens.kicked |
A kick |
The text takes {reason}, {by}, {until}, {left}, {player} and
{appeal}. Blank lines are allowed and worth using — this is the one thing that
person is reading at that moment.
screens.appeal is where to appeal, and it fills in on every screen that
carries {appeal}. A ban with nowhere to write to is a ban you never find out
was wrong.
Discord
#With the bridge on, a ban can mean it in both places:
discord.ban.game-to-discord— a ban in game bans on Discord too,discord.ban.discord-to-game— and the other way,discord.ban.who-may— whose bans on Discord are allowed to reach the game.
Both directions are off by default, because it is a decision rather than a convenience. In detail on the Discord page.
What is not here yet
#There are no warnings in the mod yet, and a muted player can still write on signs and in books.
Mutes
#From Keystone 0.2.4.
/mute <player> <time> [reason] 30m, 12h, 7d, or forever
/unmute <player>
/mutes
Also as /keystone mute, /keystone unmute and /keystone mutes (where another mod owns /mute). Permission keystone.mute, asked again every time the command runs.
- Into the same ledger as bans:
/keystone historyshows every mute with its reason, author and end;GET /v1/mutes(players.read) lists those that stand. It ends on its own date — nothing has to remember to lift it — and survives a restart and a reconnect. - What it closes: the player's chat, the commands in
mute.blocked-commands(by default/msg,/tell,/w,/whisper,/me,/r,/reply,/teammsg,/tm— add another mod's mail or party chat by name), and what their linked Discord account writes into the bridged channel. Each try tells them the reason and how long is left. The Discord bridge never hears a muted line. - Never above you: nobody mutes somebody in a heavier group or an operator (unless they are one), nor anybody with
keystone.mute.exempt. A mute somebody above you gave is theirs to lift:/unmuterefuses you and says who. Nobody lifts their own mute. The console may do anything. - A second
/mutereplaces the first; it does not add up. mute.enabled = falseswitches all of it off (after a restart).
Did this page help?
Opens the feedback panel with this page attached. It lands in the same queue as everything else.