Two-way sync between a Discord bug-report forum and GitHub issues.
Note
Although you can just deploy to any VPS after filling the config, I encourage forking and making any modifications you need to tailor it your server.
| Trigger | Effect |
|---|---|
| New forum post in Discord | Opens a GitHub issue (title = post title, body includes the poster's username + content + mapped labels) and replies in-thread with the issue link |
| Comment in a synced thread | Cross-posted to the GitHub issue, attributed to the Discord author |
GitHub issue comment starting with !dc |
The text after !dc is posted back into the Discord thread |
| Forum tags changed | GitHub labels reconciled to match (mapped tags only) |
| GitHub labels changed | Discord forum tags reconciled to match (mapped tags only) |
| GitHub issue closed | Adds the Resolved tag to the thread + a notice (thread stays open) |
| GitHub issue reopened | Removes the Resolved tag |
Discord Gateway ──► discord.js client ──► Sync ──► GitHub (App auth)
(src/discord.ts) (src/sync.ts) ▲
│ │ webhook
GitHub ──────────────────────────────────┴────────────── h3 ◄─┘ (src/webhook.ts)
│
db0/SQLite map (src/store.ts, thread ↔ issue)
src/discord.ts— Gateway client; discord.js owns heartbeats, RESUME, reconnection.src/webhook.ts— h3 + listhen server;@octokit/webhooksverifies signatures and routes events.src/sync.ts— all bidirectional logic.src/tags.ts— pure tag↔label translation (unit-tested).src/store.ts—db0(better-sqlite3 connector) thread↔issue map (suppit.db).src/github.ts— Octokit App client (handles installation-token refresh).src/config.ts— c12 + defu: env >suppit.config.ts> defaults.src/logger.ts— consola.
Tooling: tsdown (rolldown) builds src/index.ts → dist/index.mjs; deps stay external.
- Create an app at https://discord.com/developers/applications, add a Bot.
- Enable the Message Content Intent (Bot → Privileged Gateway Intents).
- Invite with scope
botand permissions: View Channels, Send Messages, Send Messages in Threads, Manage Threads, Read Message History. (274877983744) - Note the bot token, guild id, and the forum channel id. Create the forum
tags you want (including a
Resolvedtag).
- Create a GitHub App (Settings → Developer settings → GitHub Apps).
- Permissions: Issues: Read & write. Subscribe to events: Issues, Issue comment.
- Webhook URL:
https://<your-host>/github/webhook, with a webhook secret. - Install it on the target repo; note the App ID and Installation ID.
- Generate a private key (
.pem). Octokit accepts the PKCS#1 key as-is.
Copy .env.example → .env and fill in every value (TAG_MAP maps Discord tag name →
GitHub label). For GITHUB_APP_PRIVATE_KEY, paste the PEM with literal \n between
lines, wrapped in quotes.
Non-secret values (IDs, tagMap, port) may instead live in a suppit.config.ts file —
copy suppit.config.example.ts. Resolution order is env > suppit.config.ts > defaults,
so env always wins. Secrets must only come from env / .env.
pnpm install
pnpm run lint
pnpm run build && pnpm run start # production -> dist/index.mjsThe GitHub webhook needs to reach this host. In dev, expose :3000 with a tunnel
(cloudflared tunnel --url http://localhost:3000) and point the App's webhook URL at it.
Any always-on Node host works (Railway, Fly.io, Render, a VPS, systemd, Dokploy +
Railpack). It needs: outbound network for the Discord Gateway, an inbound HTTPS route
to /github/webhook, and a persistent disk for the SQLite database.
Container filesystems (Railpack/Dokploy etc.) are ephemeral — anything written during
a run is lost on the next deploy or restart. Mount a persistent volume and point
DB_PATH at a file inside it, e.g. set DB_PATH=/data/suppit.db and mount a volume
at /data in Dokploy.
- Discord forum posts allow at most 5 tags; reconciliation truncates to 5.
- Reading ongoing comment text requires the Message Content privileged intent.
- Only
TAG_MAP-listed tags/labels are synced; anything else on either side is preserved.