Build an AI Discord Chatbot: Complete Step-by-Step Guide

Learn how to make a Discord chatbot with AI knowledge base integration. Complete tutorial with code, setup guide, and 24/7 automated support features.
Chatbots
Article Main Image

If you run a thriving Discord community, answering the same questions repeatedly can be a major time sink. Imagine if you could delegate that task to a custom-trained AI that provides instant, accurate answers 24/7.

In this comprehensive guide, I'll show you exactly how to build a powerful AI agent that connects to Discord, understands complex user questions, and provides intelligent answers by searching your own knowledge base. In this sample walkthrough, our chatbot will use a knowledge base to answer common questions users in your Discord channels might ask. But in reality, you can build many kinds of Discord AI chatbots. This is just one use case and a great starting point to allow you to build your own bot for your Discord server.

Discord AI Chat Bot Key Features

We are creating a sophisticated Discord bot that goes beyond simple commands. This bot will:

  • Understand Natural Language: Users can ask questions conversationally using a simple /ask command.
  • Use a Custom Knowledge Base: The bot's answers are sourced directly from information you provide, whether it's your website content, product documentation, or FAQs.
  • Optimize User Queries: It intelligently refines user questions into powerful search terms to find the most relevant information.
  • Handle Complex Responses: The bot can process long answers from your knowledge base and deliver them in clean, readable chunks within Discord's character limits.
  • Run 24/7: Hosted on Replit, your AI agent will always be online to assist your community members.

The Technology Stack

This powerful solution is made possible by combining three key platforms, each with a specific role:

  • Voiceflow: The AI Brain. We use Voiceflow's visual canvas to design the entire conversation flow and manage the bot's Knowledge Base.
  • Discord Developer Portal: The Bot's Body. This is where we create the bot application itself, set its permissions, and get the credentials needed to bring it online.
  • Replit: The Nervous System. Replit provides the cloud environment to run our Node.js code, which acts as the crucial bridge connecting Discord's interface with Voiceflow's intelligence.

How to Build and Integrate an AI Bot in Your Discord Server

Prerequisites & Setup

Before we begin, make sure you have accounts for the following services:

All the code, prompts, and templates for this entire tutorial are available in this post.

Want a head start? Download this Voiceflow template. This is the Discord bot we will be building in this guide.

Phase 1: Designing the AI Brain in Voiceflow

First, we will build the core logic of our bot inside Voiceflow.

Step 1: Creating the Knowledge Base (KB) The foundation of our bot is its data. In your Voiceflow project, navigate to the Knowledge Base. Here, you can add your data sources. A powerful method is to use the Sitemap option, which can ingest all the pages from your website at once. You can also upload files or add individual URLs. This data will be broken down into "chunks" that the AI will use to find answers.

Step 2: Building the Conversation Flow Our Voiceflow canvas will have a simple but powerful three-step flow:

  1. Question Optimization: User questions can be messy. Our first step is to refine them. We use a Set Block configured as an AI Prompt. This prompt takes the user's raw input ({last_utterance}) and transforms it into an optimized search query by extracting keywords and removing filler. The clean query is saved to a variable called {optimized_question}.
  2. KB Search: Next, a KB Search Block uses the {optimized_question} to search your Knowledge Base. It retrieves the most relevant informational chunks and saves them to a {chunks} variable. This block has a "Not Found" path in case the search score for the chunks is too low.
  3. AI Response: Finally, an AI Response Block receives the {chunks} and the user's original question. Its job is to formulate a direct, conversational answer using only the information provided in the chunks.

After connecting the "Not Found" path and the AI Response to an End Block, click Publish on your project. Navigate to Integrations -> Dialog API and copy your VOICEFLOW_API_KEY.

Phase 2: Creating the Discord Bot Application

Next, we'll register our bot with Discord.

  1. Go to the Discord Developer Portal and create a New Application.
  2. Get Credentials:
    • In the General Information tab, copy the APPLICATION ID. This is your APP_ID.
    • Go to the Bot tab, click Reset Token, and copy the new token. This is your DISCORD_TOKEN.
  3. Enable Intents: On the Bot tab, scroll down to Privileged Gateway Intents and enable all three: PRESENCE INTENT, SERVER MEMBERS INTENT, and MESSAGE CONTENT INTENT.
  4. Set Permissions & Invite:
    • Go to the OAuth2 -> URL Generator tab.
    • In "Scopes," select bot.
    • In "Bot Permissions," select the necessary permissions like Send Messages, Embed Links, Use Slash Commands, Read Message History, and View Channels.
    • Copy the generated URL at the bottom, paste it into your browser, and add the bot to your server.
  5. Get Server ID: In Discord (with Developer Mode enabled), right-click your server icon and Copy Server ID. This is your SERVER_ID.

Phase 3: Building the Bot's Code on Replit

This is where we connect everything together.

3.1: Project Setup In Replit, create a new Node.js project. Inside, you must create a specific folder and file structure. You will have an index.js file and three folders: commands, events, and utils, each containing the necessary files.

3.2: Installing Dependencies Go to the Shell tab in Replit and run this command to install the required libraries: npm install discord.js dotenv axios

3.3: Configuring Secrets Go to the Secrets tab in Replit and add the five keys you've collected:

  • VOICEFLOW_API_KEY
  • VOICEFLOW_API_URL (set this to https://general-runtime.voiceflow.com)
  • APP_ID
  • DISCORD_TOKEN
  • SERVER_ID

3.4: The Code Explained Here is the full code for each file and what it does.

index.js (The Main Engine) This file is the entry point of your application. It initializes the Discord client with the correct permissions (Intents), loads all your command and event files dynamically, and logs the bot into Discord.

index.js - Main Bot Engine

// Final index.js
require('dotenv').config();

const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits, Routes } = require('discord.js');
const { DISCORD_TOKEN, APP_ID, SERVER_ID } = process.env;

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent
    ]
});

client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
const commands = [];

for (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    const command = require(filePath);
    client.commands.set(command.data.name, command);
    commands.push(command.data.toJSON());
}

const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));

for (const file of eventFiles) {
    const filePath = path.join(eventsPath, file);
    const event = require(filePath);
    if (event.once) {
        client.once(event.name, (...args) => event.execute(...args));
    } else {
        client.on(event.name, (...args) => event.execute(...args));
    }
}

client.rest.setToken(DISCORD_TOKEN);

(async () => {
    try {
        console.log(`Started refreshing ${commands.length} application (/) commands.`);
        const data = await client.rest.put(
            Routes.applicationGuildCommands(APP_ID, SERVER_ID),
            { body: commands }
        );
        console.log(`Successfully reloaded ${data.length} application (/) commands.`);
        await client.login(DISCORD_TOKEN);
    } catch (error) {
        console.error(error);
    }
})();

commands/ask.js (The Slash Command)
This file defines the /ask command that users will see and use in Discord.

  • Key Points & Customization:
    • You can change the command's name and description by editing the .setName() and .setDescription() fields.
    • The interaction.deferReply({ flags: 64 }) line makes the initial "thinking..." response visible only to the user who ran the command (ephemeral).

ask.js - The Slash Command

// ask.js
const { SlashCommandBuilder } = require('discord.js');
const { interact } = require('../utils/dialogapi.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('ask')
        .setDescription('Ask AY bot a question.')
        .addStringOption(option =>
            option.setName('question')
                .setDescription('The question you want to ask.')
                .setRequired(true)),

    async execute(interaction) {
        // Show the "thinking..." message with proper flags
        await interaction.deferReply({ 
            flags: 64 // This is the ephemeral flag (1 << 6)
        });

        // Call the Voiceflow function
        await interact(interaction);
    },
};

utils/dialogapi.js (The Bridge to Voiceflow)

This is the most complex file. It handles the API communication with Voiceflow, processes the response, and sends messages back to Discord.

  • Key Points & Customization:
    • Launch Action: The code first sends a launch action to Voiceflow. This properly initializes a new session for each interaction, which is a robust way to prevent state issues.
    • Message Splitting: The splitMessage helper function at the bottom is crucial. It automatically breaks down long responses from Voiceflow into smaller chunks that fit within Discord's 2000-character limit, ensuring your bot never fails when delivering a long answer.
    • Error Handling: The try...catch block is very detailed. It will send specific error messages to the user if the bot service has an authentication error (401) or is temporarily down (500+).

dialogapi.js - The Bridge to Voiceflow

// dialogapi.js
const axios = require('axios');
require('dotenv').config();

module.exports = {
    interact: async (interaction) => {
        const question = interaction.options.getString('question');
        const user = interaction.user.id;
        const username = interaction.user.username;

        console.log(`User ${username} (${user}) asked: "${question}"`);

        try {
            // Always start with a launch action to properly initialize the session
            const launchResponse = await axios.post(
                `${process.env.VOICEFLOW_API_URL}/state/user/${user}/interact`,
                {
                    action: { type: 'launch' },
                    config: {
                        tts: false,
                        stripSSML: true,
                        stopAll: true,
                        excludeTypes: ['path', 'debug', 'flow', 'block']
                    },
                },
                {
                    headers: {
                        Authorization: process.env.VOICEFLOW_API_KEY,
                        'Content-Type': 'application/json',
                    },
                }
            );

            console.log('Launch response traces:', launchResponse.data.map(trace => ({ type: trace.type })));

            // Then send the actual question
            const response = await axios.post(
                `${process.env.VOICEFLOW_API_URL}/state/user/${user}/interact`,
                {
                    action: { 
                        type: 'text', 
                        payload: question 
                    },
                    config: {
                        tts: false,
                        stripSSML: true,
                        stopAll: true,
                        excludeTypes: ['path', 'debug', 'flow', 'block']
                    },
                },
                {
                    headers: {
                        Authorization: process.env.VOICEFLOW_API_KEY,
                        'Content-Type': 'application/json',
                    },
                }
            );

            console.log('Question response traces:', response.data.map(trace => ({ 
                type: trace.type, 
                hasMessage: !!trace.payload?.message 
            })));

            if (response.data.length === 0) {
                console.log('Empty response from Voiceflow after launch');
                await interaction.followUp({ 
                    content: "I'm having trouble processing your request right now. Please try again.", 
                    flags: 64
                });
                return;
            }

            // Process each trace from Voiceflow
            let hasReplied = false;

            for (const trace of response.data) {
                if (trace.type === 'text' && trace.payload?.message) {
                    const message = trace.payload.message;
                    console.log('Processing text response, length:', message.length);

                    // Split long messages into chunks of 2000 characters or less
                    const chunks = splitMessage(message, 2000);

                    for (let i = 0; i < chunks.length; i++) {
                        console.log(`Sending chunk ${i + 1}/${chunks.length}:`, chunks[i].substring(0, 100) + '...');

                        await interaction.followUp({
                            content: chunks[i],
                            flags: 64
                        });
                        hasReplied = true;

                        // Add small delay between chunks
                        if (i < chunks.length - 1) {
                            await new Promise(resolve => setTimeout(resolve, 500));
                        }
                    }
                }
                else if (trace.type === 'speak' && trace.payload?.message) {
                    const message = trace.payload.message;
                    console.log('Processing speak response, length:', message.length);

                    const chunks = splitMessage(message, 2000);

                    for (let i = 0; i < chunks.length; i++) {
                        await interaction.followUp({
                            content: chunks[i],
                            flags: 64
                        });
                        hasReplied = true;

                        if (i < chunks.length - 1) {
                            await new Promise(resolve => setTimeout(resolve, 500));
                        }
                    }
                }
            }

            // Only send fallback if no valid responses were found
            if (!hasReplied) {
                console.log('No valid responses found in traces, sending fallback');
                await interaction.followUp({ 
                    content: "I received your message but don't have a specific response for that right now.", 
                    flags: 64
                });
            }

            // Check if conversation ended
            const isEnding = response.data.some(trace => trace.type === 'end');
            if (isEnding) {
                console.log('Conversation ended, session will be cleaned by Voiceflow');
            }

        } catch (error) {
            console.error('Error interacting with Voiceflow:', error.response?.data || error.message);

            if (error.response?.status === 401) {
                await interaction.followUp({ 
                    content: 'Authentication error with the bot service. Please contact support.', 
                    flags: 64
                });
            } else if (error.response?.status >= 500) {
                await interaction.followUp({ 
                    content: 'The bot service is temporarily unavailable. Please try again later.', 
                    flags: 64
                });
            } else {
                await interaction.followUp({ 
                    content: 'Sorry, I had trouble processing your request. Please try again.', 
                    flags: 64
                });
            }
        }
    },
};

// Helper function to split long messages into Discord-compatible chunks
function splitMessage(text, maxLength = 2000) {
    if (text.length <= maxLength) {
        return [text];
    }

    const chunks = [];
    let currentChunk = '';

    // Split by paragraphs first (double line breaks)
    const paragraphs = text.split('\n\n');

    for (const paragraph of paragraphs) {
        // If adding this paragraph would exceed the limit
        if (currentChunk.length + paragraph.length + 2 > maxLength) {
            if (currentChunk.length > 0) {
                chunks.push(currentChunk.trim());
                currentChunk = '';
            }

            // If a single paragraph is too long, split by sentences
            if (paragraph.length > maxLength) {
                const sentences = paragraph.split('. ');

                for (const sentence of sentences) {
                    const sentenceWithPeriod = sentence.endsWith('.') ? sentence : sentence + '.';

                    if (currentChunk.length + sentenceWithPeriod.length + 1 > maxLength) {
                        if (currentChunk.length > 0) {
                            chunks.push(currentChunk.trim());
                            currentChunk = '';
                        }

                        // If a single sentence is still too long, force split by words
                        if (sentenceWithPeriod.length > maxLength) {
                            const words = sentenceWithPeriod.split(' ');
                            let wordChunk = '';

                            for (const word of words) {
                                if (wordChunk.length + word.length + 1 > maxLength) {
                                    if (wordChunk.length > 0) {
                                        chunks.push(wordChunk.trim());
                                        wordChunk = '';
                                    }
                                }
                                wordChunk += (wordChunk.length > 0 ? ' ' : '') + word;
                            }

                            if (wordChunk.length > 0) {
                                currentChunk = wordChunk;
                            }
                        } else {
                            currentChunk = sentenceWithPeriod;
                        }
                    } else {
                        currentChunk += (currentChunk.length > 0 ? ' ' : '') + sentenceWithPeriod;
                    }
                }
            } else {
                currentChunk = paragraph;
            }
        } else {
            currentChunk += (currentChunk.length > 0 ? '\n\n' : '') + paragraph;
        }
    }

    if (currentChunk.length > 0) {
        chunks.push(currentChunk.trim());
    }

    return chunks;
}

events/ready.js (The Online Signal)

This is a simple but important event handler. Its sole purpose is to run once when your bot successfully logs in and connects to Discord's servers. It prints a confirmation message to your Replit console so you know the bot is online and ready to receive commands.

ready.js - The Online Signal

// ready.js
const { Events } = require('discord.js');

module.exports = {
    name: Events.ClientReady,
    once: true,
    execute(client) {
        console.log(`Ready! Logged in as ${client.user.tag}`);
    },
};

events/interactionCreate.js (The Command Router)

This file is the bot's central traffic controller. It listens for any interaction happening in your server. When a user runs a slash command, this code checks the command's name (e.g., "ask") and finds the corresponding file in your commands folder. It then executes the code within that file. Without this event handler, the bot would be online but would not respond to any commands.

interactionCreate.js - The Command Router

// interactionCreate.js
const { Events } = require('discord.js');

module.exports = {
    name: Events.InteractionCreate,
    async execute(interaction) {
        if (!interaction.isChatInputCommand()) return;

        const command = interaction.client.commands.get(interaction.commandName);

        if (!command) {
            console.error(`No command matching ${interaction.commandName} was found.`);
            return;
        }

        try {
            await command.execute(interaction);
        } catch (error) {
            console.error(`Error executing ${interaction.commandName}`);
            console.error(error);
        }
    },
};

Phase 4: Running Your Bot & Final Test

With all the files and secrets in place, click the "Run" button in Replit. You should see the following in your console: Started refreshing 1 application (/) commands. Successfully reloaded 1 application (/) commands. Ready! Logged in as [Your Bot Name]

Now, go to your Discord server and test it with the /ask command. The bot will spring to life, providing intelligent answers from your knowledge base.

Conclusion: Your 24/7 AI Community Assistant

You have now built a robust, intelligent AI agent that can serve as a valuable assistant for your Discord community. By combining the powerful conversation design of Voiceflow with the ubiquitous reach of Discord, you've created a scalable solution to automate support and provide instant value to your users.

Your AI-Powered Business Assistant Building a Discord AI bot integrated with Voiceflow isn't just about automation—it's about creating intelligent community experiences that scale your operations. You've just built a system that combines natural conversation with sophisticated knowledge base processing.

The combination of Voiceflow's conversation design, Discord's community platform, and your custom knowledge base creates something greater than the sum of its parts—a truly intelligent community assistant that works around the clock, answers complex questions instantly, and provides consistent value to your members.

Ready to implement this for your business? Start with the code templates above, or reach out to me (Abdullah) if you need help customizing this system for your specific use case. As an AI automation specialist, I help businesses implement these exact workflows with proper customization and optimization tailored to their unique community needs and knowledge requirements.

Contributor
Verify logo
Content reviewed by Voiceflow
AI Automation Specialist
I’m Abdullah Yahya, a hands-on AI agent builder from the Netherlands. I specialize in building chatbots and automated workflows using Voiceflow and make.com—cutting support costs, speeding up sales, and making businesses more efficient. I don’t waste time on theory or hype. Every solution I deliver is designed to solve a real business problem and show results. If you want a partner who actually builds, tests, and improves real automations—let’s work.
Create a Custom Chatbot Now
Get started, it’s free
Create a Custom Chatbot Now
Get started, it’s free
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
Abdullah Yahya
AI Automation Specialist
I’m Abdullah Yahya, a hands-on AI agent builder from the Netherlands. I specialize in building chatbots and automated workflows using Voiceflow and make.com—cutting support costs, speeding up sales, and making businesses more efficient. I don’t waste time on theory or hype. Every solution I deliver is designed to solve a real business problem and show results. If you want a partner who actually builds, tests, and improves real automations—let’s work.
Published: 
June 24, 2025
June 20, 2025
Read Time: 
5
 mins

Start building
AI agents,

it’s free.

Keep Reading

See all
No items found.

Start building AI Agents

Want to explore how Voiceflow can be a valuable resource for you? Let's talk.

ghraphic