Voiceflow named a 2026 Best Software Award winner by G2
Read now
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.
We are creating a sophisticated Discord bot that goes beyond simple commands. This bot will:
This powerful solution is made possible by combining three key platforms, each with a specific role:
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.
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:
{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}.{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.{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.
Next, we'll register our bot with Discord.
APPLICATION ID. This is your APP_ID.DISCORD_TOKEN.SERVER_ID.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_KEYVOICEFLOW_API_URL (set this to https://general-runtime.voiceflow.com)APP_IDDISCORD_TOKENSERVER_ID3.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.
// 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.
// 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.
// 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 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 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); } }, };
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.
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.

