Compatibility
Minecraft: Java Edition
Platforms
Links
Tags
Creators
Details
Smart, automatic chat moderation — spam, ads, toxicity, threats and profanity, handled for you
Spigot · Paper · Purpur · Folia | 1.20 – 1.21.x | Java 17+ | One jar, no dependencies
ChatCop reads every message your players send — in chat, in private messages, on signs, in books and in anvil renames — and acts on it in real time.
The difference is the detection. Most chat plugins match words. ChatCop normalises the message first, so leet speak, symbol substitution, dots between letters, spaced-out words, repeated characters and invisible unicode all collapse down to the real text before anything is checked.
Demonstrated on a harmless word: sp4mm3r, s.p.a.m, #s#p#a#m, s p a m and example (dot) com are all caught by the same rule that catches the plain spelling. The built-in lists those rules run against cover slurs, hate speech, threats and profanity.
Everything runs in memory, async where it matters, with zero heavy dependencies.
What's new in 1.1.0
- Private messages and commands are filtered — without this, every filter was one
/msgaway from being bypassed - Signs, books and anvil renames are filtered too
- Raid detection — catches the same line arriving from many accounts at once, which per-player tracking cannot see
- Chat control — slowmode, a global chat lock, and a first-join chat delay that shuts down bot raids
- Offline mutes — mute and unmute players who are not on the server
- Points, warnings and history now survive restarts
- Per-filter bypass permissions, plus mute-exempt and mute-override
- Sexual content detection, reported separately from slurs
- Discord queue — a spam wave can no longer trip your webhook's rate limit, and
@everyonein a flagged message can never ping your server - Built-in update checker and optional bStats metrics
Upgrading from 1.0.x? Just replace the jar. Every new option has a built-in default, so your existing
config.ymlkeeps working — add only the keys you want to change.
Features
Five filters, four ways to respond, and a points system that escalates punishments on its own.
The five filters
| Filter | What it catches |
|---|---|
| Spam | Flood detection, duplicate and near-duplicate messages (similarity based, not exact match), excessive caps, repeated characters, cross-player raid detection |
| Advertising | IPs, URLs, domains, Minecraft server addresses and Discord invites, including spelled-out forms. IP and URL blocking toggle independently, with a domain whitelist for your own addresses |
| Toxicity | Built-in slur and hate speech list, built-in sexual content list, plus your own phrases |
| Threats | Death threats, "kys", doxxing, DDoS and swat threats, plus your own phrases |
| Profanity | Censor mode (****) or block mode, with a per-message limit before the whole message is dropped |
Detection that doesn't get bypassed
Shown with a harmless word — the built-in lists cover the real ones.
- Leet speak —
sp4mm3r,h4ck3r,n00b - Separators —
s.p.a.m,#s#p#a#m,s-p-a-m - Letter spacing —
s p a m - Repeated characters —
spaaaaam - Zero-width and invisible unicode
- Whole-word matching for your custom phrases, so
negadoes not flagnegative— with regex still available when you want partial matching - A whitelist per filter for the terms your server uses legitimately
Four ways to respond
| Action | Behaviour |
|---|---|
ALLOW |
Let it through |
CENSOR |
Replace the word and send the rest |
BLOCK |
Stop the message |
SHADOW |
Ghost punishment. The message is hidden from everyone except the sender, so they think it sent fine and never learn what your filter catches |
Punishments that escalate on their own
- Every violation awards points, and points decay over time for good behaviour
- Crossing a threshold runs the commands you configure — works with LiteBans, EssentialsX, AdvancedBan or anything else that has a command
- Per-filter commands on top of that, for an instant reaction to a specific offence
- A per-player cooldown, so one spammer cannot trigger thirty punishments in three seconds
- Built-in mutes and warnings — temporary or permanent, offline players included, persisted to disk
Chat control
- Slowmode — a minimum wait between messages
- Chat lock — freeze chat instantly, staff can still talk
- First-join delay — new connections must wait before their first message. Extremely effective against bot raids
Alerts and integrations
- Discord webhooks — rich embeds, per-filter colour and toggle, manual staff actions, rate-limit aware queue
- In-game staff alerts with a configurable format and sound, optional console mirroring
- PlaceholderAPI — 15 placeholders for points, warnings, mute status, slowmode, chat lock and server-wide statistics
- bStats metrics (optional, and off until you set a service id)
- Update checker — Spigot or Modrinth, notifies admins on join. It only reads a version number, it never downloads anything
Built for real servers
- Folia-ready — the same jar runs on Spigot, Paper, Purpur and Folia
- Per-world disable for staff and creative worlds (mutes stay enforced)
- Hex colour support (
&#RRGGBB) everywhere, andFish & Chipssurvives unmangled - Rotating log file and persistent statistics
- Every player-facing message is editable
- Tab completion on every command
Requirements
- Server: Spigot, Paper, Purpur or Folia 1.20+ (tested through 1.21.x)
- Java: 17 or newer
- PlaceholderAPI: optional
Installation
- Drop
ChatCop-1.1.0.jarinto yourplugins/folder - Restart the server
- Edit
plugins/ChatCop/config.ymland run/chatcop reload
That is it — the defaults are production-ready out of the box.
Commands
Main command: /chatcop — aliases /cc and /cop
| Command | Description | Permission |
|---|---|---|
/chatcop help |
Command overview | chatcop.admin |
/chatcop reload |
Reload the configuration | chatcop.admin |
/chatcop stats |
Moderation statistics | chatcop.stats |
/chatcop history <player> |
A player's violation history (works offline) | chatcop.admin |
/chatcop check <player> |
Points, warnings and mute status at a glance | chatcop.admin |
/chatcop test <message> |
See what a message would trigger, without sending it (console too) | chatcop.admin |
/chatcop clear <player> |
Reset a player's points and history | chatcop.admin |
/chatcop mutelist |
List active mutes | chatcop.mute |
/chatcop slowmode <seconds> |
Set slowmode, 0 to disable | chatcop.admin |
/chatcop lock · /chatcop unlock |
Lock or unlock chat | chatcop.admin |
/ccmute <player> [duration] [reason] |
Mute, online or offline | chatcop.mute |
/ccunmute <player> |
Unmute, online or offline | chatcop.mute |
/ccwarn <player> [reason] |
Warn a player | chatcop.warn |
Durations: s, m, h, d, w — combinable, e.g. 1h30m — or perm for permanent.
Example: /ccmute Steve 2d Advertising
Permissions
| Permission | Description | Default |
|---|---|---|
chatcop.admin |
Full access | op |
chatcop.mute |
Mute and unmute | op |
chatcop.mute.exempt |
Cannot be muted by other staff | false |
chatcop.mute.override |
Can mute exempt players | false |
chatcop.warn |
Warn players | op |
chatcop.notify |
Receive in-game alerts | op |
chatcop.stats |
View statistics | op |
chatcop.chatlock.bypass |
Talk while chat is locked | op |
chatcop.bypass |
Bypass all filters | false |
chatcop.bypass.<filter> |
Bypass one filter: spam, advertising, toxicity, threats, profanity |
false |
chatcop.bypass.slowmode |
Bypass slowmode | op |
chatcop.bypass.joindelay |
Bypass the first-join delay | op |
chatcop.bypassskips the filters. It does not let a muted player talk — mutes are always enforced.
PlaceholderAPI
Player: %chatcop_points% · %chatcop_warns% · %chatcop_muted% · %chatcop_mute_time% · %chatcop_mute_reason% · %chatcop_bypass%
Server: %chatcop_total_messages% · %chatcop_total_blocked% · %chatcop_total_censored% · %chatcop_total_mutes% · %chatcop_total_warns% · %chatcop_block_rate% · %chatcop_active_mutes% · %chatcop_slowmode% · %chatcop_chat_locked%
Configuration
Every option is commented in the file, and every option has a built-in default — so an older config keeps working after an update.
Placeholders for punishment commands:
| Token | Meaning | Token | Meaning |
|---|---|---|---|
%player% |
Offending player | %punisher% |
Who triggered it |
%uuid% |
Offending player's UUID | %reason% |
Why it was flagged |
%message% |
The offending message | %duration% |
Punishment duration |
%filter% |
Filter name | %world% |
World name |
In the messages section, both {name} and %name% forms work.
config.yml (click to expand)
general:
# Enable debug mode - logs filter decisions to console (disable on production)
debug: false
# Save all blocked messages to a log file
log-to-file: true
# Path to the log file (relative to the ChatCop plugin folder)
log-file: "logs/chatcop.log"
# Rotate the log once it passes this size, in KB. 0 disables rotation.
log-max-size-kb: 5120
# Silent mode - block messages without telling the sender
silent-block: false
# Worlds where chat filtering is skipped (e.g. staff worlds).
# Mutes are still enforced in these worlds.
disabled-worlds: []
# Checks for a newer version on startup and tells you about it. It only
# READS the latest version number - it never downloads or installs anything.
# Runs fully async and fails silently if offline.
update-checker:
enabled: true
# Also notify players with chatcop.admin when they join, if an update exists
notify-admins: true
# Where to check for the latest version: "spigot" or "modrinth"
source: "spigot"
# Anonymous usage statistics (bStats). Register your plugin at bstats.org,
# then paste the service id below. Left at 0, no metrics are sent at all.
metrics:
enabled: true
service-id: 0
# Shown in all messages. Supports & color codes and &#RRGGBB hex.
# Write && for a literal ampersand.
prefix: "&8[&bChat&3Cop&8] "
# Every filter awards points. Points decay over time. When a player total
# crosses a threshold, that threshold commands run. This is SEPARATE from
# the per-filter punishment commands, which run instantly.
punishments:
# How many points a player loses per minute of good behaviour
point-decay-per-minute: 2
# Minimum seconds between punishment commands for the same player.
# Stops a spammer from triggering dozens of punishments in seconds.
punishment-cooldown-seconds: 3
thresholds:
15:
commands:
- "ccwarn %player% Repeated chat violations"
30:
commands:
- "tempmute %player% 10m Repeated chat violations"
50:
commands:
- "tempmute %player% 1h Excessive chat violations"
# Server-wide controls that run before the filters.
chat-control:
# Seconds a player must wait between messages. 0 disables it.
# Bypass: chatcop.bypass.slowmode
slowmode-seconds: 0
# Seconds a player must be connected before they may chat.
# Very effective against bot raids. 0 disables it.
# Bypass: chatcop.bypass.joindelay
first-join-delay-seconds: 0
# Start with chat locked (staff with chatcop.chatlock.bypass can still talk)
locked-on-start: false
# Without this, every filter is one /msg away from being bypassed.
command-filter:
enabled: true
# Commands whose text is run through the chat filters.
# Format "<command>:<args-before-message>" - /msg <player> <message>
# has one argument before the message, /me <message> has none.
filtered:
- "msg:1"
- "tell:1"
- "whisper:1"
- "w:1"
- "m:1"
- "t:1"
- "r:0"
- "reply:0"
- "me:0"
- "say:0"
- "mail:1"
- "broadcast:0"
# Commands a muted player may not use at all.
blocked-when-muted:
- "msg"
- "tell"
- "whisper"
- "w"
- "m"
- "r"
- "reply"
- "me"
- "mail"
- "say"
- "broadcast"
# The other places players can write text.
extras:
filter-signs: true
filter-books: true
filter-anvil: true
# Mute storage.
mutes:
# Keep mutes across restarts
persistent: true
# Where mutes are stored (relative to the ChatCop plugin folder)
storage-file: "data/mutes.yml"
# Violation points, warning counts and history.
player-data:
# Keep them across restarts. Turn off to make them session-only.
persistent: true
filters:
# -- SPAM FILTER --------------------------------------------
spam:
enabled: true
# Violation points awarded when this filter is triggered
points: 4
# How many seconds a message is remembered for duplicate checks
duplicate-content-expiry: 8
# How many similar messages are allowed before blocking
duplicate-threshold: 4
# Max number of messages allowed within the flood window
flood-messages: 6
# Time window in seconds to track message flooding
flood-window: 3
# How similar two messages must be to count as duplicate (0.0 - 1.0)
# 1.0 = identical only, 0.8 = 80% similar
similarity-threshold: 0.80
# Percentage of capital letters to trigger caps warning (0 - 100)
caps-threshold: 70
# Minimum message length before the caps check applies
caps-min-length: 12
# Points awarded for excessive caps (usually lower than main points)
caps-points: 2
# Block messages with a character repeated too many times ("heeellooo")
repeated-chars: true
repeated-chars-threshold: 10
# Catches the same line arriving from several accounts at once, which is
# what a bot raid looks like. Per-player tracking cannot see this, because
# each account only sends the message once.
cross-player:
enabled: true
# How many different players must send it within the window
player-threshold: 4
# Window in seconds
window: 10
# How similar the messages must be (0.0 - 1.0)
similarity: 0.90
points: 6
punishment:
# Commands to run when this filter is triggered (leave empty [] for none)
commands: []
# -- ADVERTISING FILTER -------------------------------------
advertising:
enabled: true
points: 10
# Block raw IP addresses (e.g. 192.168.1.1)
block-ips: true
# Block URLs, domains and Discord invites
block-urls: true
# Extra domains to block. A plain word is matched as a whole word;
# anything containing regex characters is treated as a regex.
blocked-domains: []
# Domains that are always allowed (put your own server domains here)
whitelist-domains: []
punishment:
commands: []
# -- TOXICITY FILTER ----------------------------------------
toxicity:
enabled: true
points: 8
# Ghost/shadow mode: flagged messages are hidden from everyone EXCEPT the
# sender, so they think it sent fine and don't try to bypass.
shadow-mode: false
# Built-in list of racial and identity slurs
block-slurs: true
# Built-in list of sexual content (reported separately from slurs)
block-sexual-content: true
# Smart detection sees through leet speak, symbols and letter spacing
smart-detection: true
# Your own phrases. A plain word matches as a whole word, so "nega" will
# NOT flag "negative". Use regex if you want partial matching.
blocked-phrases: []
# Phrases that should never be flagged by this filter
whitelisted-phrases: []
punishment:
commands: []
# -- THREAT FILTER ------------------------------------------
threats:
enabled: true
points: 12
shadow-mode: false
blocked-phrases:
- "i will leak your ip"
- "i will ddos"
- "i will doxx"
whitelisted-phrases: []
punishment:
commands: []
# -- PROFANITY FILTER ---------------------------------------
profanity:
enabled: true
points: 3
# Censor mode replaces bad words instead of blocking the message.
# If a message can't be censored safely (spaced-out obfuscation, for
# example) it is blocked rather than sent through uncensored.
censor-mode: true
# Character used to replace bad words
censor-char: "*"
# Points awarded when a message is censored rather than blocked
censor-points: 1
# How many profane words are allowed before blocking the message entirely
max-per-message: 3
blocked-phrases: []
whitelisted-phrases: []
punishment:
commands: []
# Discord webhook alerts.
discord:
enabled: false
# Your Discord webhook URL (channel settings > integrations)
webhook-url: ""
# Also post manual staff actions (mutes, unmutes, warns)
staff-actions: true
# Colour for staff action embeds
action-color: 3447003
# Alerts are queued and sent one at a time so a spam wave doesn't
# trip Discord's rate limit.
send-interval-seconds: 2
# Maximum queued alerts before the oldest are dropped
queue-limit: 50
filters:
# Each filter can be individually enabled/disabled for webhook alerts.
# Color is decimal - use https://www.spycolor.com to convert from hex.
Spam:
enabled: false
color: 16776960
Advertising:
enabled: true
color: 16753920
Toxicity:
enabled: true
color: 16711680
Threats:
enabled: true
color: 9109504
Profanity:
enabled: false
color: 16755200
# Fallback color if the filter name doesn't match any above
default-color: 16711680
# URL to an image shown as the footer icon (leave empty to disable)
footer-icon: ""
# URL to an image shown as the embed thumbnail (leave empty to disable)
thumbnail: ""
# In-game staff alerts.
notifications:
# Send in-game alerts to players with chatcop.notify
staff-alerts: true
# Also announce manual staff actions (mute, unmute, warn)
staff-action-alerts: true
# Mirror alerts to the server console
log-to-console: true
# Send the alert to the offending player too (useful when testing)
notify-offender: false
# Trim the quoted message in alerts to this many characters
max-message-length: 50
alert-format: "&8[&cAlert&8] &7%player% &8| &f%filter% &8| &7\"%message%\""
action-format: "&8[&bChatCop&8] &7%actor% &f%action% &7%target%&f %detail%"
# Play a sound to staff when an alert is sent
alert-sound: true
# Either a Bukkit sound name (BLOCK_NOTE_BLOCK_PLING) or a namespaced
# key (block.note_block.pling). Both work on every supported version.
alert-sound-type: "BLOCK_NOTE_BLOCK_PLING"
# Supports & color codes and &#RRGGBB hex. Write && for a literal &.
# Both {name} and %name% placeholders work.
messages:
blocked: "&cYour message was blocked. Reason: &f{reason}"
censored: "&eYour message contained inappropriate language and was censored."
muted: "&cYou are muted. &7Time remaining: &f{time} &8| &7Reason: &f{reason}"
muted-permanent: "&cYou are permanently muted. &7Reason: &f{reason}"
unmuted: "&aYou are no longer muted."
warned: "&eWarning &7({count}): &f{reason}"
mute-success: "&aMuted &f{player} &afor &f{duration}&a: &f{reason}"
unmute-success: "&aSuccessfully unmuted &f{player}&a."
warn-success: "&aWarned &f{player} &7({count})&a: &f{reason}"
already-muted: "&c{player} is already muted."
not-muted: "&c{player} is not muted."
mute-exempt: "&c{player} cannot be muted."
slowmode: "&cSlowmode is on. &7Wait &f{time} &7before chatting again."
join-delay: "&cYou just joined. &7You can chat in &f{time}&7."
chat-locked: "&cChat is currently locked by staff."
chat-lock-on: "&cChat has been locked by staff."
chat-lock-off: "&aChat has been unlocked."
reload-success: "&aChatCop configuration reloaded."
player-not-found: "&cPlayer not found: &f{player}"
no-permission: "&cYou don't have permission to do that."
Need help, or want a feature?
Join the Discord
Bugs and suggestions are welcome on the issues page. If ChatCop keeps your chat clean, a rating helps a lot.


