What the bot does
- Reads every new and edited message in your server (it needs the Message Content intent).
- Asks layahost’s
moderationtemplate whether the message istoxicorsafe. The answer comes back with a probability in about a tenth of a second. - Above 0.9 it deletes the message and times the author out for ten minutes. Between 0.6 and 0.9 it leaves the message up and posts it to a private
#mod-logchannel for a human to decide.
Discord’s built-in AutoMod is still worth keeping for invite links, mention spam and your own word lists. The bot adds what word lists can’t do: understanding insults, harassment and threats written in ordinary words.
1. Create the bot
- In the Discord Developer Portal, create an application, open Bot, copy the token, and switch on Message Content Intent.
- Under OAuth2 → URL Generator pick the
botscope and the permissions View Channels, Send Messages, Manage Messages and Moderate Members (for timeouts). Open the generated URL and add the bot to your server. - Create a
#mod-logchannel that only moderators and the bot can see, and copy its ID (right click → Copy Channel ID, with Developer Mode on). - Create a layahost account, verify your email ($1 of free credit, no card) and create an API key.
2. The code
Node.js 18 or newer, one dependency:
npm init -y && npm pkg set type=module && npm install discord.js
import { Client, Events, GatewayIntentBits } from "discord.js";
const DELETE_AT = 0.9; // delete + timeout above this
const REVIEW_AT = 0.6; // send to #mod-log above this
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
// Ask layahost: is this message toxic? Returns the probability, or null on any error.
async function toxicity(text) {
try {
const res = await fetch("https://layahost.com/v1/decide", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LAYAHOST_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, template: "moderation" }),
signal: AbortSignal.timeout(3000),
});
if (!res.ok) return null;
const answer = await res.json(); // { decision: "toxic", probabilities: { toxic: 0.93, safe: 0.07 }, ... }
return answer.probabilities.toxic;
} catch {
return null; // fail open: never block chat because the check is down
}
}
async function moderate(message) {
if (message.author?.bot || !message.inGuild() || !message.content?.trim()) return;
const p = await toxicity(message.content);
if (p === null || p < REVIEW_AT) return;
const where = `${message.author} in ${message.channel}`;
const quiet = { allowedMentions: { parse: [] } }; // never ping anyone from a quoted message
try {
const log = await client.channels.fetch(process.env.MOD_LOG_CHANNEL_ID);
if (p >= DELETE_AT) {
await message.delete().catch(() => {});
await message.member?.timeout(10 * 60 * 1000, "Toxic message").catch(() => {});
const quoted = message.content.slice(0, 1500).split("\n").map((line) => `> ${line}`).join("\n");
await log.send({ content: `Removed (${p.toFixed(2)}) from ${where}:\n${quoted}`, ...quiet });
} else {
await log.send({ content: `Please check (${p.toFixed(2)}) ${where}: ${message.url}`, ...quiet });
}
} catch (err) {
// Wrong channel ID or missing permission: log it, keep the bot running.
console.error("Moderation action failed:", err.message);
}
}
client.on(Events.MessageCreate, moderate);
client.on(Events.MessageUpdate, (_before, after) => moderate(after)); // edits are checked too
client.once(Events.ClientReady, (c) => console.log(`Moderating as ${c.user.tag}`));
client.login(process.env.DISCORD_TOKEN);
DISCORD_TOKEN=… LAYAHOST_API_KEY=lh_… MOD_LOG_CHANNEL_ID=… node bot.js
Run it anywhere Node runs: a small VPS, a Raspberry Pi, Railway or Fly.io. The bot keeps one connection open to Discord, so serverless functions are a poor fit.
3. Tune it on your server’s messages
Every community has its own tone: a gaming server swears more than a support forum. Before you let the bot delete anything, set DELETE_AT to 1.1 (never) and let it only log for a few days. Then export a few hundred real messages, label them toxic or safe, and upload them as a test set in the layahost console. A run gives you the accuracy on your own messages and the probability threshold that separates the two best. If a hosted model such as clef-flash does better on your messages, add "model": "clef-flash" to the request; the comparison is one click in the same test set. Hosted models are billed by input tokens from prepaid credit, and their text is processed by Cloudflare rather than on our EU servers.
More than toxicity
- Scam links and spam: the
spamtemplate catches fake Nitro giveaways, crypto scams and DM-me offers. To run moderation and spam with one call, put both template questions in a decision flow; each question answered is one decision. - Your own rules: ask anything with two to a few dozen answers, for example “Is this message about selling accounts?” or “Which channel does this question belong in?”. See /v1/decide.
- Personal data: the
piitemplate finds phone numbers, emails and card numbers people paste by mistake, and returns a redacted copy. - Existing OpenAI moderation code: layahost also answers OpenAI’s
/v1/moderationsformat (beta), so a bot written for it can switch by changing the base URL.
What it costs
With the default Laya model, one message checked (or edited) is one decision: $15 per million on pay-as-you-go, less on a monthly plan. A server with 3,000 messages a day uses about 90,000 decisions a month, roughly $1.35. The $1 welcome credit covers the first 66.7K messages. See pricing.
Good to know
- Language. English works best. Other languages are scored too, less accurately; test on your own messages.
- Privacy. With the default Laya model, messages are processed in memory on servers in the EU and not stored, unless you turn on request logging. Hosted models (Clef) run on Cloudflare. Tell your members a bot checks messages.
- Images. layahost reads text only. Leave images to Discord’s explicit-media filter.
- Mistakes happen. Keep a human in the loop for the middle band, and let people appeal a timeout.
FAQ
Is this an official Discord bot?
No. It is your own bot, running on your own machine, calling the layahost API. Discord is a trademark of Discord Inc.; layahost is not affiliated with it.
Will it slow down my chat?
No. Messages are posted as usual; the bot checks them a moment later and removes the bad ones, typically within half a second.
Can I use Python instead?
Yes. The API is plain HTTPS; with discord.py, call the same endpoint from your on_message handler. Code examples show the request in Python.