How to add license keys to a discord.js bot
Short answer: npm install sunlicense, read the buyer's key from .env, check it before client.login(), and exit when SunLicense says no. A network error is not a no: start anyway and check again later.
import { SunLicense } from 'sunlicense';
import { Client, GatewayIntentBits } from 'discord.js';
const key = process.env.LICENSE_KEY;
if (!key) { console.error('Put your license key in .env (LICENSE_KEY).'); process.exit(1); }
const sunlicense = new SunLicense({ baseUrl: 'https://api.sunlicense.com', token: API_KEY }); // API key from your build
try {
const r = await sunlicense.validate(key, { host: machineId() });
if (r.status === 400) { // EXPIRED, REVOKED, TOO_MANY_MACHINES…
console.error(`License rejected: ${r.data?.code}`);
process.exit(1);
}
} catch (e) {
console.warn('SunLicense unreachable, starting anyway:', e.message);
}
const bot = new Client({ intents: [GatewayIntentBits.Guilds] });
await bot.login(process.env.DISCORD_TOKEN);validate resolves to { valid, status, data }; it throws only when SunLicense can't be reached.
Start from the template instead: example-discord-bot-js (Node.js 22, discord.js 14) has this plus a stable machine id, masked keys, re-checks every 6 hours, a signed offline license fallback, a sealed src/core.js, a /license command, tests and CI.
Or let your AI agent do it: paste the setup prompt into Claude Code, Cursor or Codex in the bot's folder.
Free plan: 3 products, 100 licenses, no credit card. Create a free account.
Step by step
In the dashboard: a product for the bot, a policy (1 machine means one running copy per license) and an API key, which can only validate licenses.
Two secrets, two places. The buyer's
DISCORD_TOKENandLICENSE_KEYgo in their.env. Your API key belongs to the build you hand out, not to their.env: the template'snpm run buildwrites it into a generated settings file fromSUNLICENSE_API_KEY. Never commit either.Machine id: hash something stable about the host and send it as
host, so restarts count as the same machine:jsimport { createHash } from 'node:crypto'; import os from 'node:os'; const machineId = () => createHash('sha256').update(`${os.platform()}|${os.arch()}|${os.hostname()}`).digest('hex');In Docker the host name changes with the container; set
hostname:in the buyer's compose file, or store a random id in a mounted volume. Choosing a machine id.Results: 200 logs in, 400 exits with the reason (mask the key in logs:
AX8P-XXXX-XXXX-XXXX), anything else starts and retries in about 10 minutes, or uses an offline license.Re-check while it runs (
setIntervalevery 6 hours,.unref()it) and callbot.destroy()and exit on a 400, so revoking a leaked key reaches bots that are already running.
Selling it
Bots sold to server owners usually go through Tebex, Gumroad, Lemon Squeezy, Stripe or BuiltByBit: SunLicense issues a key per purchase and revokes it on a refund. The SunLicense Discord bot can hand buyers their key and a customer role in your own support server.
Going further
- Protection: the template seals
src/core.jsso a copy with the check deleted has nothing to run. - Obfuscation for the JavaScript you ship.
- JDA and discord.py versions of this guide. More on licensing Discord bots.
