On this page
Configuration
Manuka Moderation is configured in plugins/ManukaModeration/config.yml, which the first start creates with comments on every key. Edit it and run /mm reload; no restart is needed except where noted. On Pro you can also change every setting from the panel's Settings page. On Free the panel can change the language, the theme and the accent colour, and everything else stays editable in the file.
A bad value never stops the plugin: the console names the key, what was expected and the default used instead. If the file is not valid YAML at all, the previous settings stay in use.
Keys marked Pro only take effect with a Pro license. On Free they are kept, never deleted.
General #
| Key | Default | What it does |
|---|---|---|
language | en | Language of console, in-game and default panel text: en, es or es_AR. Each player can choose their own with /mm lang. |
server-name | "My Server" | Shown in the panel, e-mails and the AI prompt, and part of the license instance label. |
timezone | system | system or a time zone such as America/Argentina/Buenos_Aires. Decides day boundaries, digest times, the daily purge and chat log days. |
Console #
| Key | Default | What it does |
|---|---|---|
console.banner | always | The large banner on start: always, first-run or never. The one-line summary (version, platform, panel address, language, tier) is always printed. |
console.ascii-only | false | true forces the plain # banner, for consoles that garble the block letters. |
Capture #
What the plugin records. Login, register and password commands are never captured, whatever you set here.
| Key | Default | What it does |
|---|---|---|
capture.capture-cancelled | false | Also store and analyse messages that another chat filter cancelled, so evasion attempts are still seen. |
capture.private-commands | [msg, tell, t, w, whisper, m, pm, r, reply, emsg, etell, ewhisper, er, ereply] | Commands captured as private messages. |
capture.staff-commands | [sc, staffchat, ac, modchat, helperchat] | Commands captured as staff chat. Staff chat never produces advertising detections. |
capture.text-commands | [me, mail, helpop, report, broadcast, bc, say, nick, teammsg] | Other commands whose text is captured. |
capture.denied-commands | [] | Commands never captured, on top of the built-in login, register and password list. Add your login plugin's aliases. |
capture.mask-after-keywords | [password, contraseña, contrasena, clave, senha] | Everything after one of these words is stored as [masked]. |
capture.context-messages | 10 | Lines kept before and after a flagged message as the case's context. |
Storage #
| Key | Default | What it does |
|---|---|---|
storage.retention-days | 30 | Days of chat log kept. Free keeps at most 30. Pro accepts any value, and 0 keeps it forever. Cases, events and punishments are kept regardless. |
storage.purge-at | "04:30" | Local time of the daily purge. |
Everything is stored in one SQLite file, plugins/ManukaModeration/data.db. Back it up with the server stopped, or copy data.db, data.db-wal and data.db-shm together.
Groups and exemptions #
| Key | Default | What it does |
|---|---|---|
groups.provider | auto | Where player groups come from: auto (LuckPerms, else Vault, else permissions only), luckperms, vault or permissions. |
groups.staff-groups | [admin, mod, staff] | Groups whose members count as staff for the rank-abuse detector. |
exempt.players | [] | Names or UUIDs that never produce cases. /mm exempt add and /mm exempt remove edit this list. |
exempt.groups | [] | Groups whose members never produce cases. |
Exempt players' lines are still stored and shown as context in other players' cases. The permission manukamod.exempt exempts a player too.
Advertising #
| Key | Default | What it does |
|---|---|---|
advertising.allowed | [] | Your community's official links: your website, your Discord invite and any other address, IP or name of your own. |
advertising:
allowed: [myserver.net, discord.gg/abc123, "play.myserver.net"]
A message that shares one of these links is never flagged because of it, however players write it: with or without https:// and www., on a subdomain, in capitals, inside brackets or behind a colour code, or spelled out ("myserver punto net"). Everything else still is: another Discord or website, a look-alike name, a longer address (myserver.net.ru) or the same code on another invite service (dsc.gg, discord.me).
- Write a host (
myserver.net) or an invite (discord.gg/abc123). A port or a query in an entry is ignored. - An entry with a path (your page on a server list, such as
minecraft-mp.com/server/123456) allows that page and the pages under it, not the rest of the site. - An invite code with capital letters only matches when typed exactly like that. Write a vanity invite in lower case.
- A bare name (
myserver) allows that name under every domain, so list it only if nobody else runs a server with that name.
Detection #
| Key | Default | What it does |
|---|---|---|
detection.packs | [en, es] | Rule packs loaded from rules/<pack>.yml, in order. rules/custom.yml is always loaded last. |
detection.case-window-minutes | 30 | Detections of the same player and type within this window join one case. |
detection.max-events-per-case | 200 | After this many events a case stops storing new ones (it still counts them). |
detection.cooldown-seconds | 30 | A repeat hit inside the cooldown is still recorded, but alerts nobody unless it is more severe. |
detection.reassembly | { messages: 5, seconds: 20 } | Split-message detection: a player's last 5 messages sent within 20 seconds are joined and checked for advertising and personal data. |
detection.harassment | { repeats: 3, window-minutes: 10, distress-window-seconds: 60 } | Harassment after 3 insults to the same target within 10 minutes, or a victim's distress within 60 seconds of an insult. |
detection.spam | { similarity: 0.6, window-seconds: 30, repeats: 3 } | Spam after 3 similar messages within 30 seconds. |
detection.caps | { min-letters: 12, ratio: 0.7 } | Caps when a line has at least 12 letters and 70% of them are capitals. |
detection.actions | see below | What happens for each violation type and severity. |
detection.commands | [] | Pro. Console commands run when a case reaches a threshold. |
Actions #
Each type and severity gets a list of actions: log (store only; always implied), notify (in-game alert, real-time e-mails and a panel notification) or command (Pro, runs the matching detection.commands entries). Types that are not listed use default. As shipped:
detection:
actions:
default: { low: [log], medium: [notify], high: [notify] }
advertising: { low: [notify], medium: [notify], high: [notify] }
personal_data: { low: [notify], medium: [notify], high: [notify] }
spam: { low: [log], medium: [log], high: [notify] }
caps: { low: [log], medium: [log], high: [log] }
Automatic commands (Pro) #
Nothing is punished automatically by default. An entry of detection.commands runs once per case, as the console, when the case reaches threshold events at min-severity or above, and only while the detection.actions row of that type and severity includes command. Placeholders: {player}, {uuid}, {type}, {severity}, {case} and {server}. Use your punishment plugin's commands, and test them by hand first.
detection:
actions:
advertising: { low: [notify], medium: [notify], high: [notify, command] }
commands:
- { type: advertising, min-severity: high, threshold: 3, run: ["warn {player} Advertising (case {case})"] }
Rules files #
The rules live in plugins/ManukaModeration/rules/:
en.ymlandes.ymlare the bundled language packs, copied on the first start.custom.ymlholds your own rules and is always loaded last. Its lists add to the packs unless you setmerge: replaceat the top.
# rules/custom.yml
version: 1
merge: append
detectors:
advertising:
allowed: [] # e.g. [myserver.net, discord.gg/abc123]
hosts: [] # extra competitor hosts
insult:
words: [] # extra word stems
safe-words: [] # words that must never flag
custom:
toxic-links: # shows as "custom:toxic-links" in the panel
enabled: false
severity: medium
regex: ["\\bbit\\.ly/\\w+\\b"]
- Words are stems:
idiotalso matchesidiotsandidiotic. End an entry with!to match the whole word only. - Regular expressions use Java syntax and are matched, by default, on the normalized text: lower case, accents removed and leetspeak undone. Start a pattern with
raw:to match the original text instead. - Patterns are checked when the file loads. A pattern that could hang (such as
(a+)+) is rejected, and one that takes more than 50 ms on hostile input is disabled and named in the console. - A rules file with an error is reported, and the previous rules stay active until it is fixed.
Alerts #
| Key | Default | What it does |
|---|---|---|
alerts.enabled | true | In-game alerts for players with manukamod.alerts, who can turn them off for themselves with /mm alerts off. Only detections whose action includes notify alert anyone. |
alerts.min-severity | medium | Lowest severity that produces an alert. |
alerts.types | [all] | Violation types that produce an alert. |
Panel #
| Key | Default | What it does |
|---|---|---|
panel.enabled | true | Start the web panel. With false, /mm password still works, so accounts are ready when you turn it on. |
panel.bind | 127.0.0.1 | Address the panel listens on. 127.0.0.1 means this machine only. Use 0.0.0.0 only behind HTTPS or a reverse proxy. |
panel.port | 8765 | Port of the panel. |
panel.base-path | "" | Serve the panel under a prefix such as /mm, for a reverse proxy that maps https://example.com/mm/ to it. |
panel.public-url | "" | The address used in links (console, alerts, e-mails, login links). Set it when the panel is reached through a proxy or a domain. |
panel.https.enabled | false | Built-in HTTPS with the keystore in panel.https.keystore (keystore.p12 by default; PKCS12 or JKS). |
panel.behind-proxy | { enabled: false, trusted-proxies: ["127.0.0.1"] } | Trust the forwarded client address and protocol from these proxies only. |
panel.allowed-origins | [] | Pro. Websites allowed to use the panel's API (external website mode). |
panel.session-hours | 72 | How long a login lasts. |
panel.max-login-attempts | 5 | Failed logins from one address for one account before they are locked out for panel.lockout-minutes (15). |
panel.game-login.enabled | false | Let staff sign in with the password of the server's login plugin (LoginSecurity or AuthMe). |
panel.skin-downloads | true | The player page shows each player's skin; the server downloads the images from Mojang's skin server (textures.minecraft.net). false downloads nothing and shows a silhouette. |
panel.accent | "#2EC27E" | Accent colour of the panel and e-mails. |
panel.default-theme | auto | auto (follow the device), light or dark. |
Changes to panel.bind, panel.port, panel.base-path and panel.https.* are applied by a restart or by /mm reload from the console or the game, not by a save in the panel. Setup recipes are in the panel guide.
Access #
Which groups may log in to the panel and what each one sees.
access:
permissions-override: true
groups:
owner: { violations: [all], punishments: true, logs: true, analytics: true, settings: true, users: true, ai: true }
admin: { violations: [all], punishments: true, logs: true, analytics: true, settings: false, users: false, ai: true }
mod: { violations: [advertising, insult, harassment, discrimination, sexual, threat], punishments: true, logs: false, analytics: true, settings: false, users: false, ai: false }
- Each entry is a group name as in LuckPerms or your Vault permission plugin.
violationsis[all]or a list of violation types; the other keys aretrueorfalse(missing ones count asfalse). - A player's access is the sum of all their groups, primary and inherited. A player with no matching group and no panel permission cannot log in.
- Free uses the first two groups only (as shipped,
modis the third). The console says so at start. manukamod.admin(operators by default) always sees everything.access.permissions-override(Pro with LuckPerms or Vault): themanukamod.panel.*permissions add access on top of the groups. Without LuckPerms or Vault, those permissions are the only way in, on Free and Pro.- The
settingsscope can change the automatic commands, which run as the console. Give it to owners only.
E-mail #
| Key | Default | What it does |
|---|---|---|
email.enabled | false | Send e-mail notifications. |
email.smtp.host | "" | SMTP server. |
email.smtp.port | 587 | 587 for STARTTLS, 465 for SSL, 25 only for a local relay. |
email.smtp.security | starttls | starttls, ssl or none. |
email.smtp.username, email.smtp.password | "" | SMTP login. The password can come from the MM_SMTP_PASSWORD environment variable instead. |
email.smtp.from, email.smtp.from-name | "", "Manuka Moderation" | Sender address and name. |
email.realtime-per | case | Real-time e-mails per case (one when it opens and one per severity escalation) or per event. |
email.realtime-max-per-hour | 20 | Above this, a recipient gets one summary at the end of the hour instead. |
email.recipients | [] | Who receives what (below). |
email:
recipients:
- { address: "[email protected]", language: en, categories: [all, digest], min-severity: medium, mode: digest, every: "daily@08:00" }
Each recipient has a language (en, es, es_AR), categories (violation types, punishments, digest, or all), a minimum severity and a mode: realtime, digest or both. Digest schedules look like 30m, 6h, daily@08:00, daily@08:00,20:00 or weekly@mon-08:00. Free uses the first recipient only, with real-time e-mails or one daily digest; Pro allows any number of recipients, any schedule and both modes.
Any SMTP service works: a Gmail or Google Workspace app password, Outlook or Microsoft 365, SendGrid, Mailgun, Brevo or your own server. Test with /mm email test <address>.
AI #
Off by default. Nothing in the plugin depends on it.
| Key | Default | What it does |
|---|---|---|
ai.enabled | false | Turn on the AI features. |
ai.provider | anthropic | anthropic, or openai-compatible for OpenAI, OpenRouter, Groq, Ollama or LM Studio. |
ai.api-key | "" | Your own provider key (you pay the provider's usage). The MM_AI_KEY environment variable can provide it instead. |
ai.model | claude-opus-5-5 | Model name sent to the provider. |
ai.base-url | https://api.openai.com/v1 | openai-compatible only: the API address, for example a local Ollama. |
ai.max-tokens | 4096 | Output budget. Keep it at 4096 or more for Claude models. |
ai.second-opinion | false | Pro. Automatic false-positive check of new medium and high cases. |
ai.digest-summary | false | Pro. An AI-written paragraph in digest e-mails. |
Free allows 10 explanations a day; Pro has no limit. An explanation or a second opinion sends the flagged case and the lines around it, with e-mail addresses and phone numbers replaced by [personal data]. The digest paragraph (Pro) sends the digest of the period instead: its totals, each new case's player, group, type, severity and first evidence line, and the punishments; that text is not masked. Answers are shown as text and never executed. Test with /mm ai test.
License #
| Key | Default | What it does |
|---|---|---|
license.key | "" | Your Pro key. Usually set with mm license activate <key> or from the panel; the MM_LICENSE_KEY environment variable can provide it instead. |
license.provider | lemonsqueezy | Leave it as shipped: the official release already knows which store its keys come from. none keeps everything Free. |
Updates #
| Key | Default | What it does |
|---|---|---|
updates.check | true | Look for new releases about a minute after start and then every 6 hours. |
updates.source | modrinth | modrinth or github; the other is the fallback. |
updates.auto-download | false | Pro. Download new releases automatically; they are applied on the next restart, only if their signature checks out. |
updates.notify | [console, panel, staff] | Where to announce a new version. staff means players with manukamod.update.notify, when they join. |
Punishments #
LiteBans, AdvancedBan and EssentialsX mutes are read directly, and the vanilla ban list, which Essentials bans also use, is checked for changes. For other punishment plugins, the commands below are recorded when staff type them. The same punishment seen twice within 5 seconds is recorded once.
| Key | Default | What it does |
|---|---|---|
punishments.banlist-poll-seconds | 60 | How often the ban lists are checked for changes. |
punishments.commands | see below | Commands recorded as punishments, by kind. Add your plugin's command names to the matching list. |
punishments:
commands:
ban: [ban, eban]
tempban: [tempban, etempban]
ipban: [ipban, banip, tempipban, tempbanip]
unban: [unban, pardon, unbanip, pardon-ip]
mute: [mute, tempmute, ipmute, emute]
unmute: [unmute, eunmute]
kick: [kick, ekick]
warn: [warn, ewarn]
Advanced #
| Key | Default | What it does |
|---|---|---|
advanced.intake-queue | 10000 | Chat lines waiting for analysis; above this, lines are stored without analysis. Needs a full restart. |
advanced.db-queue | 20000 | Database write queue. Needs a full restart. |
advanced.http-threads | 8 | Threads serving the panel (at least 4). |
advanced.debug | false | Verbose logging; /mm debug on and /mm debug off toggle it until the next reload. |
Secrets in environment variables #
Four keys are secrets: they are never logged, the panel only shows them masked, and they can come from environment variables instead of the file. A variable always wins over config.yml and is never written back to it.
| Environment variable | Replaces |
|---|---|
MM_SMTP_PASSWORD | email.smtp.password |
MM_AI_KEY | ai.api-key |
MM_LICENSE_KEY | license.key |
MM_PANEL_KEYSTORE_PASSWORD | panel.https.password |
Reloading #
/mm reload re-reads config.yml, the rules files, the language files, the AI prompts and the e-mail templates, and applies them without a restart. If the new config.yml cannot be read, nothing changes and the errors are listed.
- The panel's address keys (
panel.bind,panel.port,panel.base-path,panel.https.*) andadvanced.http-threadsare applied by a restart or by/mm reloadfrom the console or the game. advanced.intake-queueandadvanced.db-queueneed a full restart.- Never use the server's own
/reload. Restart the server to update the plugin.