A template for building Discord User App Widgets to link users external accounts to Discord and updating their profile cards via Discord's REST API.
Discord.js bot + Elysia.js OAuth2 server + MongoDB for credential storage.
├── src/
│ ├── api/
│ │ ├── routes/
│ │ │ └── oauth.ts # Elysia handler for /oauth/discord/callback
│ │ └── server.ts # Elysia HTTP server entry point
│ ├── bot/
│ │ ├── commands/
│ │ │ ├── link.ts # /link command — generates Discord OAuth URL
│ │ │ └── refresh.ts # /refresh command — fetches stats and patches profile
│ │ └── index.ts # Bot runner & slash command registration
│ ├── database/
│ │ └── mongo.ts # MongoDB connection & model helpers
│ ├── services/
│ │ └── discord.service.ts # Discord REST client (token exchange & PATCH profile)
│ └── types/
│ └── widget.types.ts # TypeScript definitions for the Widget spec
├── .env.example
├── tsconfig.json
└── package.json
- Go to the Discord Developer Portal and create a new application.
- Copy your Application ID (Client ID) and Public Key.
- Under the Bot tab, click Add Bot and save the Bot Token.
- Go to the OAuth2 tab.
- Under Redirects, add:
http://localhost:3000/oauth/discord/callback - Save.
cp .env.example .envFill in .env:
| Variable | Description |
|---|---|
DISCORD_CLIENT_ID |
Your application's Client ID |
DISCORD_CLIENT_SECRET |
Your application's Client Secret |
DISCORD_TOKEN |
Your bot token |
MONGO_URI |
MongoDB connection string (default: mongodb://localhost:27017) |
npm install
npm run dev:server
npm run dev:botEdit src/bot/commands/refresh.ts and find the comment:
DEVELOPER: FETCH PROFILE DATA FROM YOUR EXTERNAL SERVICE API
Use account.externalAccountId to fetch the user's stats from your service. The bot forwards the result directly to Discord's profile PATCH endpoint.
interface ApplicationProfilePayload {
username?: string; // External display name
metadata?: Record<string, string>; // Custom key-values (max 25)
data?: {
primary?: {
season?: string; // Heading or category (e.g. "Recently Scrobbled")
rank_name?: string; // Primary badge label (e.g. "Top Track: Karma Police")
highest_rank?: string; // Sub-badge label (e.g. "Top Artist: Radiohead")
playtime_hours?: number; // Scrobble hours, playtime, etc.
total_wins?: number; // Wins, scrobble count, commit count, etc.
total_games?: number; // Total tracks, matches, etc.
server_name?: string; // Text indicator (e.g. "Last.fm", server name)
user_id?: string; // External account ID or username
};
dynamic?: Array<{
type: 1 | 2 | 3; // 1 = TEXT, 2 = NUMBER, 3 = IMAGE
name: string; // Field key
value: string | number | { url: string }; // Field value
}>;
};
}