- TypeScript 96%
- Dockerfile 4%
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
dokcord 🐾
A Discord widgets v2 profile widget + bot that shows your live Dokploy stats — projects, apps, databases, running/stopped counts, recent deployments and server metrics — right on your Discord profile.
It's a user-installable bot: anyone can install it, run /widget setup with their own Dokploy URL + API key, authorize the widget, and their profile widget fills with their infra stats. A background scheduler keeps every linked widget refreshed.
panel.example.com — All systems go
📦 4 projects · 🚀 11 apps · 🗄️ 3 dbs
🟢 12 running · 🔴 0 down · 🖥️ 2 servers
✅ last deploy: my-api · 5 today
📊 CPU 23% · RAM 52% · Disk 61%
Heads up: Discord "widgets v2" is an experimental feature gated behind the Social SDK / a Developer Portal experiment. The portal side is fiddly and manual — this repo gives you the whole code side (Dokploy client, bot, refresher), but you still have to design the widget and flip the experiment yourself. The flow below is based on Chloe Cinders' guide (thank you!).
How it fits together
Dokploy API ──(x-api-key)──▶ DokployClient ──▶ stats ──▶ buildIdentityProfile
│
Discord user ──/widget setup──▶ bot (discord.js) ▼
PATCH /applications/{app}/users/{user}/identities/0/profile
│
scheduler (every N s) ───────────────────────────────────────────┘ ──▶ widget renders on profile
src/dokploy.ts— readsproject.all,deployment.allCentralized,server.all, metrics.src/widget.ts— folds stats into the Discord widgetdynamicfields.src/discordIdentity.ts— the PATCH that actually fills a user's widget.src/commands.ts/src/bot.ts—/widget setup|refresh|status|unlink.src/scheduler.ts— periodic refresh of every linked user.src/db.ts— SQLite (node:sqlite, no native build) storing links; API keys encrypted at rest.
Prerequisites
- Node.js 20+ (uses the built-in
node:sqlite; tested on Node 26). - A Dokploy instance and an API key (
Settings → Profile → API/CLI). - A Discord application with Social SDK access.
Part 1 — Discord Developer Portal (manual, one-time)
This is the part no script can do for you. Follow Chloe's guide closely; the short version:
-
Create an application at the Developer Portal.
-
Get Social SDK access: sidebar → Games → Social SDK, fill the form, submit (instant).
-
Unlock the widget editor (experiment override). Open DevTools console on the Developer Portal and run the snippet from the guide that calls
createOverride("2026-03-widget-config-editor", 1). Click the back arrow, reopen your app — a Widget entry appears under Games. (Don't refresh, it clears the override.) -
Create your widget in the editor. Add a Widget Top, Widget Bottom, and Add-Widget Preview. For every stat, add a User Data field whose Data Field name matches exactly one of dokcord's field names below (and matching value type). Give each a fallback. Style it, add Sample Data, Save + Publish.
Field name (Data Field) Type Meaning instanceText Dokploy host, e.g. panel.example.comstatusText Headline, e.g. All systems go/2 erroredprojectsNumber Project count appsNumber Applications + compose stacks databasesNumber DB count (pg/mysql/mariadb/mongo/redis/libsql) runningNumber Services running stoppedNumber Services stopped or errored serversNumber Connected servers deploys_todayNumber Deployments since local midnight last_deployText e.g. ✅ my-apimetricsText e.g. CPU 23% · RAM 52% · Disk 61%You don't have to use all of them — only the fields you put in the widget get rendered. The names live in
WIDGET_FIELDSinsrc/widget.ts; keep the two in sync if you rename anything. -
OAuth2: sidebar → OAuth2. Add a redirect URI (e.g.
https://discord.com). In the URL Generator pick scopesopenid+sdk.social_layer, select your redirect, copy the URL, and changeresponse_type=code→response_type=token. This is yourDISCORD_AUTHORIZE_URL. -
Bot token: sidebar → Bot → Reset Token. That's
DISCORD_BOT_TOKEN. Your Application ID (General Information) isDISCORD_APP_ID. -
Add the widget to a profile via the snippets in the Discord Previews thread (and set the
2026-03-application-widget-v2-rendererexperiment to Variant 1).
Part 2 — Run dokcord
cp .env.example .env
# fill in DISCORD_APP_ID, DISCORD_BOT_TOKEN, DISCORD_AUTHORIZE_URL
openssl rand -hex 32 # paste into ENCRYPTION_KEY
npm install
npm run register # publish the /widget slash command (once, and after edits)
npm run dev # or: npm start
.env keys are documented in .env.example. Notable ones:
ENCRYPTION_KEY— 32-byte hex; encrypts stored Dokploy keys (AES-256-GCM).REFRESH_INTERVAL_SECONDS— auto-refresh cadence (default 300;0disables).REFRESH_STAGGER_MS— delay between users in a sweep so we don't hammer APIs.REGISTER_ON_START—trueto publish/widgeton boot (skipsnpm run register).
Running with Docker
The image is a multi-stage build that compiles to dist/ and runs on plain Node (no tsx at runtime), as a non-root user, with the SQLite DB persisted on a named volume.
cp .env.example .env # fill it in; set REGISTER_ON_START=true for first boot
docker compose up -d --build # build + run
docker compose logs -f # watch it come up
Or with plain Docker:
docker build -t dokcord .
docker run -d --name dokcord --restart unless-stopped \
--env-file .env \
-v dokcord-data:/app/data \
dokcord
Notes:
- Needs Node 24+ for the built-in
node:sqlite; the image defaults tonode:24-slim(override with theNODE_VERSIONbuild arg). - The DB lives at
/app/data/dokcord.sqliteinside the container — thedokcord-datavolume keeps your links across restarts/rebuilds. Back up that volume (it holds your encrypted links), and keepENCRYPTION_KEYstable or you won't be able to decrypt stored keys. - After the first successful boot you can set
REGISTER_ON_START=falseagain to avoid re-publishing the command every restart.
Part 3 — Using it
- Install the app on your account (user-install) from its install link.
- Run
/widget setup url:https://panel.example.com key:<dokploy-api-key>. dokcord validates the key, stores it (encrypted), and tries a first push. - Click Authorize widget in the reply and approve the scopes. (It asks for a lot — that's the
sdk.social_layerscope; dokcord never acts on those, it only PATCHes your widget identity.) - Run
/widget refresh. Your profile widget should now show live stats. The scheduler keeps it updated from here on.
Other commands: /widget status (last refresh + any error), /widget unlink (delete your key, stop refreshing).
Notes & limitations
- Server metrics require Dokploy's monitoring container; dokcord reads it best-effort via the first server that exposes a
metricsConfig. No monitoring set up → themetricsfield is simply omitted. - The Dokploy JSON shape varies across versions, so the client is defensive (it walks both the newer
environments[]nesting and the older flat layout). If a section can't be read it's skipped rather than failing the whole refresh. - Security: stored API keys are encrypted at rest, only ever used server-side to read stats, and deletable with
/widget unlink. Still — a Dokploy API key grants full control of someone's infra. If you run this publicly, make that trade-off clear to your users and protect the host +.env/database accordingly. - Discord's widgets v2 is experimental and undocumented; Discord can change or pull it at any time. Don't abuse it.
Scripts
| Command | What it does |
|---|---|
npm run dev |
Run with file-watch (tsx). |
npm start |
Run once (tsx). |
npm run register |
Publish/refresh the /widget command globally. |
npm run typecheck |
tsc --noEmit. |
npm run build |
Emit JS to dist/. |
Credits: widget technique from Chloe Cinders · xivwidget.