Commands
Two commands of SnBans are player-facing - /helpop and /report, whose nodes default to true - and every other one is a staff command whose node defaults to op. Granting snbans.admin alone grants all of them, because its children map is exhaustive. There is no snbans.use node: each root carries its own leaf node instead. See Permissions for the whole tree.
/snbans is the admin root, and the only command that takes configurable aliases, listed under command.aliases in config.yml. That list is authoritative: the names you write there are the complete set, and /snbans keeps working alongside them. The fifteen flat roots (/ban, /history, /report, and the rest) take no configurable aliases. On Paper the alias list is re-read on /snbans reload. On Velocity a reload re-registers the aliases too, but the proxy does not resend its command tree to players who are already connected, so a new alias only tab-completes for them after they reconnect. Typed in full it works right away.
Every command exists on both platforms with the same syntax, permissions and messages, except where a row or a hint below says otherwise. Usage lines render under the label you actually typed, so /punish rollback ... is what an alias shows you. Run /snbans with no arguments for the help listing: SnLib generates it on Paper, and the proxy shell renders its own on Velocity.
Every command is reachable twice: as its own root (/ban Notch hacks) and as a subcommand of the admin root (/snbans ban Notch hacks). The two are the same command - same permission, same arguments, same messages - and the second form is what lets /snbans help list the whole surface rather than only the admin subcommands. The listing is permission-filtered and paged, so /snbans help 2 shows the rest.
Duration tokens are 30s, 5m, 2h, 7d, and the literal permanent. No duration and no matching template means permanent. A token that reads as nothing, such as 5x, is refused rather than stored as a permanent punishment.
| Command | Permission | Description |
|---|---|---|
/ban <player> [time] <reason...> | snbans.ban | Bans a player |
/ipban <player> [time] <reason...> | snbans.ipban | Bans a player and their last known IP |
/unban <player> [-s|-p] | snbans.unban | Removes an active ban |
/mute <player> [time] <reason...> | snbans.mute | Mutes a player |
/ipmute <player> [time] <reason...> | snbans.ipmute | Mutes a player and their last known IP |
/unmute <player> [-s|-p] | snbans.unmute | Removes an active mute |
/blacklist <player> <reason...> | snbans.blacklist | Blacklists a player permanently (account and IP) |
/unblacklist <player> [-s|-p] | snbans.unblacklist | Removes a blacklist (console only) |
/kick <player> <reason...> | snbans.kick | Kicks a player from the server; stores nothing |
/ipkick <player> <reason...> | snbans.ipkick | Kicks every account connected from the player's IP |
/alts <player> | snbans.alts | Lists the accounts sharing the target's current IP |
/history <player> [page]<br>alias /hist | snbans.history | Shows a player's punishment history |
/staffhistory <staff> [page] | snbans.staffhistory | Shows the punishments issued by a staff member |
/helpop <message...> | snbans.helpop | Players. Asks the staff team for help |
/report <player> <message...> | snbans.report | Players. Reports a player to the staff team |
/snbans | snbans.admin | Main admin command of SnBans; a bare call shows the help listing |
/snbans match <player> <other> | snbans.admin.match | Lists the IPs two accounts have ever shared |
/snbans rollback <staff> <time> [confirm] | snbans.admin.rollback | Reverts the punishments a staff member issued in a window |
/snbans import <source> <host:port> <database> <user> <password> [prefix] [confirm] | snbans.admin.import | Imports punishments from another plugin's database |
/snbans wipe <ban|mute|blacklist|all> [confirm] | snbans.admin.wipe | Erases every active punishment of a kind (console only) |
/snbans reload | snbans.admin.reload | Reloads the plugin configuration: config, lang, templates and webhooks |
/snbans help [page] | snbans.admin | Shows the available commands |
/snbans debug | snbans.admin.debug | Toggles runtime debug output (Paper only) |
The -s (silent) and -p (public) flags work on every punishment and every revert, and both need snbans.silent. On an issuing command you type the flag inside the reason (/ban Notch -s hacks), anywhere in it, and the last flag wins. On a revert it is an explicit trailing token. Without a flag the per-type silent-by-default key decides. A silent punishment still reaches every holder of snbans.notify.
A kick stores nothing. /kick and /ipkick disconnect the player, announce the action and post their webhook, and write no row at all: no id, no expiry, nothing to un-kick, nothing in /history or /staffhistory, and no contribution to a template ladder. They still obey the staff weight check, the -s / -p flags and snbans.noreason, and a kick that reaches nobody answers messages.kick.not-online instead of announcing a kick that never happened.
Because there is no row, a kick cannot travel between servers the way a ban does. On a multi-backend Paper install /kick only reaches players connected to the server it was run on. On a Velocity install it is network-wide, because the proxy holds every player.
/ipkick clears the target's LAST KNOWN address, so it reaches their online alts even when the target themselves is offline - which is what makes it useful against ban evasion. A kick takes no duration, so its whole tail is the reason and a reason may start with a digit here where a /ban reason may not.
When the reason matches a template id in templates.yml, case-insensitively and as the full reason, the duration comes from that template's ladder and the STORED reason becomes that template's reason: text. The step is chosen by how many punishments of that template the player already collected, and the last step repeats. The template type must match the command's type, so /ban Notch spam against a type: mute template gets no ladder at all. A free reason is a manual punishment.
A punishment command with no reason answers its usage line. Holders of snbans.noreason may run /ban Notch bare instead, and the row records the messages.format.no-reason text ("No reason" by default). The console always may. The node is separate from snbans.ban on purpose: the reason is what a history line, an appeal and a disconnect screen are read from, so the default stays "type one".
/unblacklist is console-only at runtime. Holding snbans.unblacklist makes the command grantable and tab-visible, but the flow refuses any non-console sender before the target name is even looked up. Tab completion therefore offers a player nothing for either of its arguments.
/history and /staffhistory page in chat at history.page-size entries per page, clamped to 1-50. A page past the end renders the last real page instead of an empty listing. The footer arrows are clickable and jump between pages.
/alts asks about the target's current IP; /snbans match asks which IPs two accounts have ever shared. Both are read-only, both are subject to hierarchy.applies-to-alts, and /snbans match refuses two names that resolve to the same account.
Naming your own account is allowed wherever it only affects you: /alts <you>, /snbans match <you> <other> and lifting your own punishment with /unban or /unmute. The staff weight check never refuses you your own account, because no rank can outrank itself. What stays refused is issuing a punishment on yourself - that would lock you out of the server you are moderating - and /snbans rollback <you>, which is the one way to erase a colleague's work to reach your own punishment. Both refusals hold whether or not LuckPerms is installed.
Both alt surfaces are network-wide on a shared MySQL. Login history carries no server column, so an alt that has only ever played on one backend is listed by an /alts run on another. Since 1.6.0 the Online marker is network-wide too: each server publishes its connected players into snbans_presence, so an account connected elsewhere renders as online rather than offline. A single-server SQLite install writes nothing to that table and needs no configuration for any of it.
Accounts listed in alts.hidden never appear in an alt scan shown to a player, in either direction - see alts.hidden. Enforcement is untouched, and /snbans match is not filtered.
One platform difference. /snbans debug exists on Paper only, because SnLib injects it there and the proxy has no counterpart; on Velocity the debug channel is the proxy logger's own level. Everything else, /snbans help included, lists the same commands on both sides.
Staff requests: /helpop and /report
The two commands here a player runs. /helpop <message> asks staff to look at the sender, /report <player> <message> asks them to look at somebody else, and both reach the console and every snbans.requests.receive holder on every server of the network:
[HelpOp] Snopeyy (Dev): my chest got griefed
[Report] Snopeyy reported Alex on Dev: flying in spawnThey are cross-server, and that is the feature. Several servers sharing one MySQL means a report filed on Survival reaches the moderators standing on Skyblock, on the lobby and on the proxy within a few seconds. A single-server SQLite install has no peers, so a request is delivered to that server's staff and nowhere else - there is no key for it, because an install that cannot have peers has nothing to configure about them.
| Behaviour | Detail |
|---|---|
| The server named | {server} is where the player is. On a backend install that is server-name; on a proxy install it is the backend the sender is standing on, not the proxy - a proxy that reported every helpop as coming from "Proxy" would be telling staff nothing. |
| The target of a report | Resolved against the login history like every other named account, so a typo answers the unknown-player line instead of filing a report about nobody, and the notice carries the stored casing of the name. Reporting your own account is refused, because that is what somebody types when they meant /helpop. |
| The throttle | staff-requests.cooldown-seconds, per player and per kind. A player inside their window is told how long is left - unlike an attempt notice, this throttles a command somebody deliberately typed, and silence there reads as a broken server. |
| Typed text | Sanitized exactly like a punishment reason: a typed <click:run_command:...> or %placeholder% reaches staff chat as text. It matters more here than anywhere else, since this is the one player-typed value that reaches every moderator of the network at once. |
| Muted players | Can still use both by default - "why am I muted" is the most common legitimate helpop there is. Add helpop and report to mute.blocked-commands to change that. |
snbans.requests.receive is a node of its own rather than part of snbans.notify: that one is for watching what SnBans does while nobody typed a command, and these are the opposite. Answering helpops is also plausibly a job for a rank with no business reading silent punishment notices.
There is deliberately no queue to read back: no /reports listing, no claiming, no Discord webhook. The rows are a delivery envelope that a sweep drops after a few minutes, not a ticket system. Switching a kind off in staff-requests answers the player with messages.request.disabled rather than unregistering the command, so if another plugin on your network owns /helpop or /report, deciding which one answers is your server's job.
Tab completion
Both platforms suggest real values as you type:
- Online player names for every target and staff argument.
- Nothing at all for either argument of
/unblacklistwhen a player types it. -sand-pon a revert, but only for holders ofsnbans.silent.- The duration examples
30m,1d,1h,30s, and5mfor the/snbans rollbackwindow. - The literal
confirmfor the rollback and wipe confirm tokens, and nothing else. ban,mute,blacklistandallfor the/snbans wipetarget, and only when the console is typing: completing them for a player would promise a command they can never run.- The subcommand names of
/snbans, alphabetically and permission-filtered. - The template ids of the command's own type on a reason argument, so
/ban Notch <TAB>lists the ban ladders and/mute Notch <TAB>the mute ones. - An angle-bracket hint such as
<reason>,<message>or<page>for free-form arguments.
Suggestions are a convenience only. Any name is accepted and resolved against the login table, so offline accounts and accounts this server has never seen stay typable, and a name the network has never seen is answered with the unknown-player line. At most 100 names are offered: Paper caps the online list before filtering it, while Velocity filters first and caps the matches.
/snbans rollback <staff> <time> is destructive in bulk and cannot be undone: it deletes every punishment that staff member issued inside the window. The rows are erased rather than marked as lifted, so they leave the sanctioned player's history entirely and can never push a template ladder up a step. It is a dry run by default, so the first call only counts the matches and prints the confirm command to run. Setting rollback.require-confirm to false removes the dry run and makes the first call destructive. A rollback takes no -s / -p flag, so broadcasts.rollback alone decides who sees it, and a window matching more than 5000 punishments is refused with a request to narrow it.
On a multi-server MySQL install, a rollback is the one action peers do not learn about through the sync poller: the poller reads rows whose removal was just recorded, and a deleted row is in no feed. The server that ran the sweep lifts its own mutes at once; other backends keep an erased mute in force until the player next connects there, at which point the login check finds nothing and the mute is gone. A single-server (SQLite) install has no peer and is unaffected.
Wiping punishments in bulk
/snbans wipe <ban|mute|blacklist|all> [confirm] erases every punishment of that kind that is still in force. It is the bulk amnesty: /snbans wipe ban confirm is the /unban of everyone at once, mute and blacklist do the same for theirs, and all does the three together. Use it for an amnesty, a season reset, or to undo a bad mass-ban without running one /unban per player.
Without confirm it is a dry run: it counts what a real run would erase, writes nothing, and prints the exact command that would do it.
> snbans wipe ban
----------------------------------------
WIPE | 412 bans are currently in force.
This will erase them permanently; they will not appear in /history.
Run /snbans wipe ban confirm to wipe them.
----------------------------------------
> snbans wipe ban confirm
Wiped 412 bans.This command is console only and cannot be granted around. A player who runs it is answered messages.console-only whatever permissions they hold; snbans.admin.wipe only makes the subcommand grantable and visible in /snbans help. The reasoning is that a permission node can be granted by mistake and being the console cannot, and the smallest thing this command does is lift every ban on the network at once.
Unlike rollback.require-confirm, there is no configuration key that skips the dry run. A key whose only purpose is to remove the guard on the most destructive command in the plugin is not one this plugin ships.
Like a rollback, a wipe deletes its rows rather than marking them lifted. Wiped punishments leave the sanctioned player's /history entirely and can never push a template ladder up a step. There is no way back - take a database backup first if that history matters to you.
Only what is still in force is erased. A sanction a player already served (expired), or one a staff member already lifted, is a record of something that happened and stays in /history untouched - which is what makes this the bulk /unban it is advertised as rather than a history purge.
On a multi-server MySQL install, a wipe reaches peers the same way a rollback does and with the same gap: a deleted row is in no sync feed, so the server that ran the wipe drops its own cached mutes at once while other backends keep an erased mute in force until the affected player next connects there. Bans and blacklists need no such handling at all - they are read from the punishment table on every login, and the wipe has already emptied it. A single-server (SQLite) install has no peer and is unaffected.
A wipe is announced like any other event, through broadcasts.wipe, the messages.wipe lang block and the wipe block of webhooks.yml. It takes no -s / -p flag, so broadcasts.wipe alone decides who sees it. A wipe that erased nothing announces nothing.
Importing from LiteBans
/snbans import litebans <host:port> <database> <user> <password> [prefix] [confirm] reads a LiteBans database and writes its punishments into SnBans, so a network switching over keeps its history instead of starting blank. prefix defaults to litebans_, and a bare host without a port is read as port 3306.
Without confirm it is a dry run: it connects, counts what a real run would write, reports the mapping decisions, and writes nothing. Run it that way first and check the numbers against what LiteBans reports.
Run it from the console, and only once. The password is a command argument, so it is written to the server log either way - a player running it also puts it in chat. Treat those credentials as disclosed afterwards.
A successful run records itself in the snbans_imports table, and every later run refuses. That guard is not a convenience: importing twice would insert a second copy of every punishment, and the copies are indistinguishable from the originals afterwards. A run that fails partway records nothing and can be retried, but whatever it already wrote is still there - check the tables before retrying.
What is imported
| Source | Becomes | Notes |
|---|---|---|
litebans_bans | BAN punishments | Removals, expiry and silence carried over |
litebans_mutes | MUTE punishments | Same |
litebans_history | snbans_logins rows | Collapsed to one row per (player, address) pair |
litebans_kicks | nothing | A SnBans kick stores no row, so there is nowhere for them to go |
litebans_warnings | nothing | SnBans has no warning type |
Importing the login history is what makes /history, /alts, /snbans match and name resolution work from the first boot: without it SnBans cannot turn a typed name into an account. Pairs whose last connection is already older than retention.days are skipped, because the next daily purge would delete them anyway; the dry run reports how many that is.
Mapping decisions
These are the places where LiteBans records something SnBans models differently. Each one is a deliberate choice, not a limitation of the reader.
- Scope follows LiteBans' own IP flag. A punishment it recorded as an IP ban becomes one here. Be aware that a LiteBans install running
punish_ip: truesets that flag on every punishment it ever wrote, so on such a network every imported row covers the address as well - which is exactly what that network is already enforcing today, but it is worth knowing before you import. - Expired is not the same as lifted. LiteBans stamps a removal date on a punishment that merely ran out as well as on one a staff member reverted. Only rows with a recorded remover are imported as lifted; the rest keep their original expiry and read as Expired in
/history, with no reverter attributed to them. - Templates are not carried over. LiteBans template numbers are integers and SnBans template ids are
templates.ymlstrings, so there is no mapping between them. Imported punishments carry no template and therefore count towards no escalation ladder - a player's next templated punishment starts at step one. - Console punishments keep their name and carry no staff UUID, which is how SnBans already represents the console.
- Rows with no usable address. LiteBans writes
#where it knew no address. Those punishments import as account-only, and on the login side they are dropped entirely: treating that placeholder as an address would put every account behind it on one "shared IP" and make each of them look like an alt of every other. - Accounts the source never recorded a name for are stored as
Unknown-<uuid prefix>, which keeps them distinct from one another in/historyand in name resolution.
While it runs
A large history takes several minutes. The command answers immediately, reports progress as it goes, and posts a summary at the end with the same counts the dry run showed. Do not restart the server during it: a partial import leaves rows behind and records no guard row.
The new snbans_imports table is created on boot if missing, on both platforms, so an existing install gains it with no migration step.