Filters
plugins/AstralChat/filters.yml defines a registry of ChatFilter instances. Filters run only on channels where filtered: true, and they are skipped entirely for players holding astralchat.filters.bypass.
Top-level structure
Field | Type | Description |
|---|---|---|
|
| Words that are always allowed; they are stripped before each filter runs. |
|
| Filter registry. The map key is the filter id, used by |
Common fields
Every filter shares these fields:
Field | Type | Description |
|---|---|---|
|
| Selects the filter implementation. |
| boolean | When |
| Object | What to do when the filter matches — see below. |
actions
Field | Type | Description |
|---|---|---|
| boolean | Log the violation to the server console. |
| boolean | Drop the message entirely. The sender still sees the |
| String | Replace matched substrings with this text (e.g. |
| MiniMessage | Sent to the violating player. |
| String | Channel name to broadcast the violation to (typically |
|
| A list of AstralCore actions to execute as the violating player (e.g. mute, warn). |
The actions list is a full PaperActionList, so you can chain [console], [message], [sound], and any custom action your network has registered.
Filter types
regex
Matches the message against a list of patterns.
Field | Type | Description |
|---|---|---|
|
| Patterns to match. Use |
|
| Substrings that exempt a message from this filter. |
Best practice: design patterns with character-evasion in mind (b[\\s._-]*a[\\s._-]*d) and use the whitelist to avoid false positives on legitimate words.
caps
Counts uppercase characters and triggers when the total exceeds the threshold.
Field | Type | Description |
|---|---|---|
| int | Threshold — the number of uppercase characters that triggers the filter. |
| boolean | When |
similarity
Anti-spam by string similarity. Recent messages are kept in a per-player cache and compared to new ones.
Field | Type | Description |
|---|---|---|
| Duration | How long a message stays in the comparison cache. |
| Duration | Minimum delay between two messages from the same player; faster sends always trigger. |
| int (0–100) | Similarity ratio above which the new message is considered spam. |
Pipeline
The filter result drives both whether the chat event is cancelled and whether the broadcast component is rewritten — multiple filters can fire on the same message in sequence. A cancel: true filter returns immediately, so the filters after it never run; the others accumulate, and their names are joined into the single %reason% the staff notification carries.
The component filters see is the unrendered message body — the player's own words, without the channel format, prefix or display name — so a filter can never censor a rank prefix by accident.
Moderation dataset
Every filter hit is also handed to auto moderation through AutoModerationService.recordFilterHit, with source = FILTER and action = BLOCKED or FLAGGED. These are cheap, high-precision labels covering exactly what a classifier tends to miss (links, spam, obfuscated slurs), and for a blocked message they are the only trace left — a blocked message never reaches chat_messages.
Recording is skipped entirely when dataset.enabled or dataset.store-filter-hits is off in moderation.yml, and is subject to the same per-player hourly row quota as everything else.
custom-message and the fallback
A filter with cancel: true and no custom-message falls back to the message-doesnt-respect-rules message. When custom-message is set, it is sent instead — never both.