Multi-server#
Several Minecraft servers can share one Discord channel. Players on all of them see each other's chat, joins, leaves, deaths, and advancements, and a message typed in Discord reaches every server. Each server stays a separate Viscord install with its own token and its own config file.
The hard part is not making messages arrive. It is stopping them from arriving twice, or forever.
The shape#
Each server needs:
- A unique
server.prefix. This is the identity that everything else keys on. - The same
discord.channel_id, pointing at the shared channel. - Either its own webhook or a shared one. Distinct webhooks are easier to reason about and are what most deployments use.
- Every other server's bot and webhook IDs in
filters.trusted_bot_ids.
┌──────────────────────────┐
│ #minecraft (one channel)│
└────┬──────────────┬──────┘
│ │
[Survival] webhook [Creative] webhook
│ │
Server A Server B
prefix=[Survival] prefix=[Creative]
Example: two servers#
Server A:
[general]
enabled = true
platform = "discord"
[discord]
bot_token = "YOUR_SERVER_A_BOT_TOKEN"
channel_id = "123456789012345678"
webhook_url = "https://discord.com/api/webhooks/111111111111111111/YOUR_SERVER_A_WEBHOOK_TOKEN"
[server]
prefix = "[Survival]"
name = "Survival"
[filters]
trusted_bot_ids = "222222222222222222,333333333333333333"
Server B:
[general]
enabled = true
platform = "discord"
[discord]
bot_token = "YOUR_SERVER_B_BOT_TOKEN"
channel_id = "123456789012345678"
webhook_url = "https://discord.com/api/webhooks/222222222222222222/YOUR_SERVER_B_WEBHOOK_TOKEN"
[server]
prefix = "[Creative]"
name = "Creative"
[filters]
trusted_bot_ids = "111111111111111111,333333333333333333"
111111111111111111 and 222222222222222222 are the webhook IDs of A and B
respectively; 333333333333333333 stands for the bot user ID of the other
server's application. Both kinds go in the same comma-separated list, because
the check is a plain string comparison against the author ID regardless of
whether that author is a bot or a webhook.
All values above are placeholders.
Loop prevention in three layers#
Every incoming Discord message is tested against three independent checks before
it is allowed to reach Minecraft. All three live in isSelfOriginated and all
three return early, dropping the message.
Layer 1 — webhook ID. If the author is a webhook and its ID matches the
numeric segment of this server's discord.webhook_url, drop it. This is the
server's own outbound chat coming back through the gateway.
Layer 2 — bot ID. If the author is a bot and its user ID matches the ID of
this server's own bot, drop it. This catches anything the bot posted directly,
including event embeds and /list responses.
Layer 3 — prefix. For both bot and webhook authors, if the display name
starts with this server's server.prefix, drop it. This is the belt to the ID
checks' braces: it still works if a webhook was recreated and now has a different
ID. When messages.webhook_username contains {prefix}, Viscord also builds the
expected name by splitting the format at the placeholder and prefixing the text
before it, so a format like MC {prefix} {username} is checked correctly.
These checks apply only to bots and webhooks
An ordinary Discord user's message is never considered self-originated and is
never prefix-filtered. A human typing [Survival] hello in the channel has
their message bridged normally. Only automated authors are subject to the
prefix rule. This distinction is what keeps a player from being silenced by
starting a message with a bracketed word.
Why trusted_bot_ids exists#
By default filters.ignore_bots and filters.ignore_webhooks are both true, and
the shared channel is full of automated authors: every other server's webhook
chat and bot event embeds. Filtering all of them out would leave a server seeing
only human messages and none of its neighbours' traffic.
The trusted list is the exception mechanism. An author ID in
filters.trusted_bot_ids bypasses both ignore filters:
boolean trusted = isTrustedAuthor(message.getAuthor());
if (!trusted && Filters.IGNORE_BOTS.get() && author.isBotUser()) return;
if (!trusted && Filters.IGNORE_WEBHOOKS.get() && author.isWebhook()) return;
Three properties matter:
- It is an identity allowlist, not a content filter. A trusted bot's messages pass the author check and are then parsed normally. Anything it posts is eligible to appear in game.
- It does not bypass the self-origin checks. Those run earlier and unconditionally, so trusting your own bot ID changes nothing.
- Trusting a bot is a trust decision about that bot's operator. If someone else's bot can post in the channel and you have trusted it, they can put text of their choosing into your Minecraft chat. Treat the list as a list of peers, not as a spam filter configuration.
filters.show_other_server_events is a separate switch for the event channel
specifically. With it false, anything arriving on discord.events.channel_id is
dropped before the author checks — which means a server can share a chat channel
but keep its neighbours' join/leave noise out of game.
What other servers look like in game#
A cross-server webhook message reaches Minecraft in a different shape from a
local one. Viscord reads the leading [...] tag off the author name:
- With a tag and no trailing name (
[Survival]) or a name containing "server", the line renders as the tag followed by the content. - With a tag and a trailing name (
[Survival] Steve), it renders astag name: content. - With no recognisable tag, it falls back to
[Cross-Server] author: content.
That last fallback is what you see when a trusted bot posts under a name that does not carry a bracket prefix.
Per-player control#
Each player can turn down the noise without asking an operator. All three
toggles are per-UUID and persist in config/viscord/viscord-preferences.json.
| Command | Suppresses |
|---|---|
/discord messages |
Cross-server chat relayed from Discord into game |
/discord events |
Cross-server event messages (joins, leaves, deaths, advancements) |
/discord servermessages disable |
Server system messages such as the player-list embed |
Two caveats. First, these are client-side visibility toggles: the messages are still broadcast and filtered per recipient, so they do not reduce network traffic meaningfully. Second, they are toggles, not switches — running one twice returns the player to the previous state.
Multi-server plus tridirectional#
Combining a shared Discord channel, Fluxer, and tridirectional relay is supported but multiplies the paths a message can take. The rules stack:
- The tridirectional bridge suppresses Discord → Fluxer → Discord and
Fluxer → Discord → Fluxer echoes using a bounded 512-entry
fingerprint cache with a five-second window, plus a literal
[Discord]check on the inbound Fluxer path. - The Discord-side self-origin checks suppress this server's own webhook and bot output before it can be processed as an incoming message.
- The prefix check keeps servers from echoing each other's bridged chat back into game.
Two practical requirements follow:
- Give every server a distinct prefix and never reuse one across platforms. The prefix is compared case-insensitively against whatever the other server sent, and a collision means each server drops the other's legitimate traffic as if it were its own.
- Decide which platform is authoritative for cross-server chat. In
bothmode, Viscord reads cross-server messages from Discord only and skips Fluxer messages whose author name looks like another server's prefix, specifically so one relayed line does not appear twice. With tridirectional relay on, that de-duplication is partly replaced by the bridge cache. If you see doubled cross-server lines in a combined setup, that is the interaction to look at first.
A combined deployment also raises the cost of a mistyped prefix: a server whose
server.prefix matches another's will silently drop that server's chat while its
own keeps working, which looks like a partial outage rather than a config error.
Checklist#
Before declaring a multi-server setup healthy on every node:
- [ ]
server.prefixis unique on this node and on every peer. - [ ]
discord.channel_idis identical everywhere. - [ ] This node's own webhook ID and bot ID are not in its own trusted list.
- [ ] Every peer's webhook ID and bot ID are in this node's trusted list.
- [ ]
filters.ignore_botsandfilters.ignore_webhooksare left attrue. - [ ] Joined a server, left it, and died once, and confirmed each event appeared exactly once on every peer.
- [ ] Typed one message in each server and confirmed it appears once, under the right prefix, on every other server.