> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Bridging a Hytale Forum and Discord Without Making a Mess
- URL: https://blog.sudowl.com/hytale-forum-discord-bridge/
- Published: 2026-08-27T10:00:00.000Z
- Updated: 2026-10-11T10:16:27.000Z
- Description: Bridging a XenForo forum and Discord: threads routed to channels, Discord replies posted under the user's own forum account, loop markers.
- Author: Lukas
- Tags: Hytale, Gameserver

I run a small community forum for Hytale, on XenForo, and like every game community it also needs a Discord. The problem with having both is obvious: conversations split. Someone asks a server question in Discord, gets an answer, and three days later the next person asks the same thing because chat history is where knowledge goes to die. The forum is searchable and Google finds it. Discord isn't and doesn't.

So I built a bot that bridges the two. The interesting part wasn't the code, it was deciding **what** goes where. My first design mirrored far more than it should have. The version that's live now follows a few strict rules, and those rules are what I'd copy if I were you.

**TL;DR:** Forum threads go to Discord, one channel per forum category. Threads started in Discord stay in Discord. Discord replies to bridged threads only go back to the forum if the person has linked their account with `/link`, and then they're posted **under their own forum account**, with a "via Discord" footer that doubles as a loop marker. The bot has no admin rights, the forum keys have the narrowest scopes that work, and the whole thing runs as one Docker compose service.

## The rules

**1\. Forum → Discord, routed by category.** Every new forum thread gets posted into Discord as a feed item. Not into one giant channel: announcements go to an announcements channel, patch-note threads to a patch-notes channel, the server showcase forum to its own channel, server help and modding help each to theirs, and everything else into a general forum feed. A server owner who only cares about help threads mutes the rest.

**2\. Threads started in Discord stay in Discord.** The first design was a full two-way bridge for the help channels, where a thread started in Discord would create a forum thread too. I dropped that before it mattered, because it would fill the forum with half-sentence chat threads ("anyone online?") that nobody wants to read later. Now it's a single flag in the config:

```bash
BRIDGE_DISCORD_NEW_THREADS=false
BRIDGE_LINKED_ONLY=true

```

The forum is where things get written properly. Discord is where people chat about them.

**3\. Discord replies go back only from linked users, under their own name.** If you reply in Discord to a bridged forum thread, your reply appears in the forum thread, but only if you've linked your Discord account to your forum account. And it's posted as **you**, from your forum account, not as some anonymous "Discord Bridge" user. Your post count, your avatar, your reputation.

**4\. Unlinked users get one hint per thread.** If you're not linked, the bot tells you once in that thread how to `/link`. Once. Not on every message, because that's how bots get muted.

**5\. A fallback bridge account exists, but only as a backup.** There's a dedicated "Discord Bridge" forum user from the first version. It's still there for the case where acting as the user isn't possible, but it's not the normal path.

## Linking accounts

`/link` in Discord gives you a short one-time code, like `LINK-7Q2K9F`. You paste it into a field on your forum profile, the bot reads your profile through the forum's API, finds the code, and stores the Discord-ID-to-forum-user mapping. No passwords, no OAuth app to maintain, and it proves you control both accounts.

## Posting as the user, with minimal scope

XenForo's REST API has a concept that makes rule 3 possible: a **super user key** can act on behalf of any user by sending an `XF-Api-User` header. That sounds dangerous, and it is if you give the key every scope. So the key the bridge uses for this has exactly one scope: `thread:write`. It can post replies as a user. It can't read private conversations, change users, touch permissions or delete anything.

```bash
curl -s -X POST https://forum.example.com/api/posts/ \
  -H "XF-Api-Key: $XF_POSTAS_KEY" \
  -H "XF-Api-User: 42" \
  --data-urlencode "thread_id=1234" \
  --data-urlencode "message=Have you opened UDP 5520 on the host firewall?

[I]via Discord[/I]"

```

The feed side uses a different key entirely: a guest-level key with read scopes only (nodes, threads, users). Two keys, two jobs, and the one that can write can only write replies. If the bot's environment ever leaks, the damage is "someone can post replies", not "someone owns the forum".

## The loop marker

A two-way bridge has an obvious failure mode: a Discord reply gets posted to the forum, the forum feed sees a new post, mirrors it to Discord, the bridge sees a new Discord message... you get the idea.

Every post the bridge writes into the forum ends with a footer, `[I]via Discord[/I]`. That footer is both visible to readers (so they know where the reply came from) and the marker the bot checks before mirroring anything forum → Discord. Matches the footer pattern? Already came from Discord, skip. Simple and visible beats a hidden database of "posts I made", because a human can see at a glance what happened.

## Deployment: boring on purpose

The bot is discord.js v14 in a small Node container, run with compose on the same server as the rest of my web stuff:

```yaml
services:
  bot:
    build: .
    restart: unless-stopped
    env_file: .env
    mem_limit: 256m
    volumes:
      - ./data:/app/data

```

`.env` is `chmod 600`. The `data/` folder holds the bot's state, like the account links.

**No admin rights on Discord.** During setup the bot briefly needed broad permissions to create channels and roles. Once that was done, admin came off. It also doesn't have attach-files or pin-messages; pins I do by hand. It has exactly what it needs to read the channels it bridges and post into them. If its token ever leaks, the worst case is spam in a few channels, not a deleted server.

**Tests.** The routing and bridge rules are the part I'd break without noticing, so they're covered: which forum goes to which channel, footer detection, linked vs. unlinked, the once-per-thread hint. About 60 tests, and they run before I deploy. Deploy is copy the source, `docker compose build && docker compose up -d`.

## Gotcha: new XenForo users from a script

The fallback bridge account was created by a script rather than the admin panel. My first attempt just created the user entity, set username and email, and saved. XenForo threw an exception, because a user in XF isn't one row; it comes with related records (options, profile, privacy settings) that have to exist too.

The correct way is to let the user repository set up the base user:

```php
$user = \XF::repository('XF:User')->setupBaseUser();
$user->username = 'Discord Bridge';
$user->email = 'bridge@example.com';
$user->Auth->setNoPassword();
$user->save();

```

`setupBaseUser()` creates the user entity together with those related records, with sane defaults. Anything else is fighting the framework.

## Gotcha: empty keys in a pipe chain

I moved the API keys from one server to another with a pipe: read from the source over SSH, write into `.env` on the target over SSH. One run, the source side came back empty, the pipe happily passed the empty value along, and the bot started with the bridge enabled and no key. It crashed, compose restarted it, it crashed again: a crashloop for about 45 seconds.

Every pipe chain that moves a secret now has a guard:

```bash
KEY=$(ssh old-host 'cat /root/keys/bridge.key')
[ -n "$KEY" ] || { echo "empty key, aborting" >&2; exit 1; }
printf 'XF_POSTAS_KEY=%s\n' "$KEY" | ssh new-host 'cat >> /opt/bot/.env'

```

And the bot itself should refuse to enable the bridge without a key and log why, instead of dying. Fail closed, loudly.

## Gotcha: the API only lists recent threads by default

When I seeded the Discord feed with the existing forum threads, the API returned far fewer than the forum had. XenForo's thread list endpoint only returns recently active threads unless you tell it otherwise. Passing a large `last_days` value got me the full list.

## Would I do it again?

Yes, with these rules from day one. The bridge's job isn't to make Discord and the forum the same place. It's to make sure good answers end up somewhere searchable, credited to the person who wrote them, while chat stays chat.

If you're building the server side of a Hytale community: my [Hytale server Docker panel setup](/hytale-server-docker-panel/), [how much RAM a Hytale server needs](/hytale-server-ram-how-much/), and for bot-style alerting in Discord without a bot framework, [Discord alerts from server logs with systemd](/discord-alerts-server-logs-systemd/).

---

### Related posts

- [Hytale Server Files Explained: Where Your World Actually Lives](https://blog.diengdoh.com/hytale-server-files-explained/?ref=blog.sudowl.com)
- [How Much RAM Does a Hytale Server Need? The Formula I Actually Use](https://blog.diengdoh.com/hytale-server-ram-how-much/?ref=blog.sudowl.com)
- [How to Install Mods on a Hytale Server (Full Guide)](https://blog.diengdoh.com/install-hytale-mods-plugins-guide/?ref=blog.sudowl.com)