Guide
How to Add Content Moderation to a Discord Bot
Discord servers with open communities face a constant moderation challenge. Manual moderation doesn't scale, and basic keyword filters miss context, slang, and images entirely. This guide shows how to add automated content moderation to a Discord bot using discord.js and the Vettly API.
What You'll Build
By the end of this guide, your bot will:
- Check every message in designated channels against your moderation policy
- Auto-delete messages that are blocked by the policy
- Flag borderline messages for moderator review
- Log all moderation decisions with audit trails
- Handle image moderation for attachments
Prerequisites
You'll need a Discord bot token (from the Discord Developer Portal) and a Vettly API key.
npm install discord.js @vettly/sdk
Basic Message Moderation
The core loop listens for new messages and checks them against your policy:
import { Client, GatewayIntentBits } from 'discord.js';import { Vettly } from '@vettly/sdk';const client = new Client({intents: [GatewayIntentBits.Guilds,GatewayIntentBits.GuildMessages,GatewayIntentBits.MessageContent,],});const vettly = new Vettly(process.env.VETTLY_API_KEY);client.on('messageCreate', async (message) => {// Skip bot messagesif (message.author.bot) return;const result = await vettly.check({content: message.content,policy: 'discord-community',});if (result.action === 'block') {await message.delete();await message.channel.send({content: `Message removed: violates community guidelines.`,});}if (result.action === 'flag') {// Forward to a mod-log channel for human reviewconst modChannel = message.guild?.channels.cache.find((c) => c.name === 'mod-log');if (modChannel?.isTextBased()) {await modChannel.send(`Flagged message from ${message.author.tag} in #${message.channel.name}:\n> ${message.content}\nDecision ID: ${result.decisionId}`);}}});client.login(process.env.DISCORD_TOKEN);
Image Moderation
Discord messages can include image attachments. Check them in the same message handler:
// Inside the messageCreate handler, after text checkconst imageAttachments = message.attachments.filter((a) =>a.contentType?.startsWith('image/'));for (const attachment of imageAttachments.values()) {const imgResult = await vettly.check({imageUrl: attachment.url,policy: 'discord-community',});if (imgResult.action === 'block') {await message.delete();await message.channel.send({content: 'Image removed: violates community guidelines.',});break;}}
Slash Command for Reporting
Add a /report slash command so users can flag messages to moderators:
import { SlashCommandBuilder } from 'discord.js';export const reportCommand = new SlashCommandBuilder().setName('report').setDescription('Report a message to moderators').addStringOption((opt) =>opt.setName('message_id').setDescription('Message ID to report').setRequired(true)).addStringOption((opt) =>opt.setName('reason').setDescription('Why are you reporting this?').setRequired(true));export async function handleReport(interaction) {const messageId = interaction.options.getString('message_id');const reason = interaction.options.getString('reason');await vettly.reports.create({contentId: messageId,reason,reportedBy: interaction.user.id,});await interaction.reply({content: 'Report submitted. Moderators will review it.',ephemeral: true,});}
Rate Limiting and Performance
Discord bots in large servers can process hundreds of messages per second. To avoid overwhelming your moderation API:
- Batch where possible: if a message has both text and an image, send them in one check call
- Skip trusted channels: configure channels that don't need moderation (e.g., admin-only channels)
- Cache repeat offenders: if a user is repeatedly flagged, escalate to auto-block without an API call
// Channels to skip moderationconst SKIP_CHANNELS = new Set(['mod-log','admin-chat','bot-commands',]);// In messageCreate handler:if (SKIP_CHANNELS.has(message.channel.name)) return;
Audit Logging
Every moderation decision returns a decisionId. Log these to a database or a dedicated Discord channel so you have a full audit trail for disputes and appeals:
async function logDecision(message, result) {await db.moderationLogs.create({guildId: message.guild.id,channelId: message.channel.id,userId: message.author.id,content: message.content,action: result.action,categories: result.categories,decisionId: result.decisionId,timestamp: new Date(),});}
Next Steps
- Custom policies: create different policies for different channel types (e.g., stricter for general chat, relaxed for memes)
- Appeal workflow: let users contest moderation decisions through a ticket system
- Dashboard integration: use the Vettly dashboard to monitor moderation metrics across all your servers
Moderate your Discord server with Vettly
Text, image, and video moderation in one API. Free tier includes 15,000 decisions per month — enough for most community servers.