# Building AI Discord Bots with NeuroLink

> Source: <https://dev.to/neurolink/building-ai-discord-bots-with-neurolink-7i4>
> Published: 2026-07-04 04:02:14+00:00

You will build a fully-featured AI Discord bot using Discord.js and NeuroLink's generation API. By the end of this tutorial, you will have slash commands, multi-turn conversations, AI-powered moderation, and production deployment -- all using NeuroLink as the AI backend.

Tip:This tutorial builds a custom Discord bot from scratch. NeuroLink does not provide a built-in Discord integration -- you will build the bot infrastructure yourself using Discord.js, with NeuroLink handling AI generation.

{: .prompt-tip }

This tutorial requires several npm packages for building Discord bots:

```
npm install @juspay/neurolink discord.js
npm install dotenv
npm install -D typescript @types/node ts-node nodemon
```

**Required Packages:**

`@juspay/neurolink`

- NeuroLink SDK for AI generation`discord.js`

(v14.x) - Discord's official JavaScript library`dotenv`

- Environment variable management`typescript`

and `@types/node`

- TypeScript support`ts-node`

and `nodemon`

- Development toolsBefore we begin, ensure you have the following:

Important: This tutorial uses Discord.js v14.x. If you're upgrading from v13 or earlier, review the[Discord.js v14 migration guide].

First, we need to create a Discord application and bot user through the Discord Developer Portal.

Under the Bot section, configure these essential settings:

```
Privileged Gateway Intents:
- MESSAGE CONTENT INTENT: Enabled (required for reading messages)
- SERVER MEMBERS INTENT: Enabled (if tracking member events)
- PRESENCE INTENT: Optional (for user status features)
```

Navigate to OAuth2 > URL Generator and select:

`bot`

, `applications.commands`

`Send Messages`

, `Read Message History`

, `Use Slash Commands`

, `Embed Links`

, `Attach Files`

Copy the generated URL and use it to invite your bot to your test server.

Let's initialize our project and install the necessary dependencies.

```
mkdir neurolink-discord-bot
cd neurolink-discord-bot
npm init -y
```

Install the required packages:

```
npm install discord.js @juspay/neurolink dotenv
npm install -D typescript @types/node ts-node nodemon
```

Initialize TypeScript:

```
npx tsc --init
```

Update your `tsconfig.json`

:

```
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

Organize your project with a clean structure:

```
neurolink-discord-bot/
├── src/
│   ├── index.ts           # Main entry point
│   ├── config.ts          # Configuration
│   ├── commands/          # Slash commands
│   │   ├── index.ts
│   │   ├── ask.ts
│   │   ├── chat.ts
│   │   └── summarize.ts
│   ├── events/            # Discord event handlers
│   │   ├── ready.ts
│   │   └── interactionCreate.ts
│   ├── services/          # NeuroLink integration
│   │   ├── neurolink.ts
│   │   └── conversation.ts
│   └── utils/             # Helper functions
│       ├── embed.ts
│       └── logger.ts
├── .env
├── package.json
└── tsconfig.json
```

Create a `.env`

file in your project root:

```
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_application_client_id
NEUROLINK_API_KEY=your_neurolink_api_key
```

Create `src/config.ts`

:

``` python
import dotenv from 'dotenv';
dotenv.config();

export const config = {
  discord: {
    token: process.env.DISCORD_TOKEN!,
    clientId: process.env.DISCORD_CLIENT_ID!,
  },
  neurolink: {
    apiKey: process.env.NEUROLINK_API_KEY!,
  },
};

// Validate required environment variables
const requiredEnvVars = ['DISCORD_TOKEN', 'DISCORD_CLIENT_ID', 'NEUROLINK_API_KEY'];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
}
```

Create the NeuroLink service layer in `src/services/neurolink.ts`

:

``` js
import { NeuroLink } from '@juspay/neurolink';
import { config } from '../config';

const neurolink = new NeuroLink();

export interface ChatMessage {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

export interface GenerateOptions {
  systemPrompt?: string;
  maxTokens?: number;
  temperature?: number;
}

export async function generateResponse(
  prompt: string,
  options: GenerateOptions = {}
): Promise<string> {
  const {
    systemPrompt = 'You are a helpful Discord bot assistant. Be concise, friendly, and informative.',
    maxTokens = 1000,
    temperature = 0.7,
  } = options;

  try {
    const result = await neurolink.generate({
      input: { text: prompt },
      systemPrompt,
      provider: 'openai',
      model: 'gpt-4o-mini',
      maxTokens,
      temperature,
    });

    return result?.content ?? 'I could not generate a response.';
  } catch (error) {
    console.error('NeuroLink API error:', error);
    throw new Error('Failed to generate AI response');
  }
}

export async function generateChatResponse(
  messages: ChatMessage[],
  systemPrompt?: string
): Promise<string> {
  // Build conversation text from message history
  const conversationText = messages
    .map((msg) => `${msg.role}: ${msg.content}`)
    .join('\n');

  try {
    const result = await neurolink.generate({
      input: { text: conversationText },
      systemPrompt: systemPrompt || 'You are a helpful assistant.',
      provider: 'openai',
      model: 'gpt-4o-mini',
      maxTokens: 1000,
      temperature: 0.7,
    });

    return result?.content ?? 'I could not generate a response.';
  } catch (error) {
    console.error('NeuroLink API error:', error);
    throw new Error('Failed to generate AI response');
  }
}

export async function summarizeText(text: string): Promise<string> {
  return generateResponse(text, {
    systemPrompt: 'Summarize the following text concisely while preserving key information.',
    maxTokens: 500,
    temperature: 0.3,
  });
}
```

For multi-turn conversations, we need to track message history. Create `src/services/conversation.ts`

:

``` js
import { ChatMessage } from './neurolink';

interface Conversation {
  messages: ChatMessage[];
  lastActivity: number;
  channelId: string;
  userId: string;
}

class ConversationManager {
  private conversations: Map<string, Conversation> = new Map();
  private readonly maxMessages = 20;
  private readonly timeoutMs = 30 * 60 * 1000; // 30 minutes

  private getKey(userId: string, channelId: string): string {
    return `${userId}-${channelId}`;
  }

  getConversation(userId: string, channelId: string): ChatMessage[] {
    const key = this.getKey(userId, channelId);
    const conversation = this.conversations.get(key);

    if (!conversation) {
      return [];
    }

    // Check if conversation has expired
    if (Date.now() - conversation.lastActivity > this.timeoutMs) {
      this.conversations.delete(key);
      return [];
    }

    return conversation.messages;
  }

  addMessage(userId: string, channelId: string, message: ChatMessage): void {
    const key = this.getKey(userId, channelId);
    let conversation = this.conversations.get(key);

    if (!conversation) {
      conversation = {
        messages: [],
        lastActivity: Date.now(),
        channelId,
        userId,
      };
      this.conversations.set(key, conversation);
    }

    conversation.messages.push(message);
    conversation.lastActivity = Date.now();

    // Trim old messages if exceeding limit
    if (conversation.messages.length > this.maxMessages) {
      conversation.messages = conversation.messages.slice(-this.maxMessages);
    }
  }

  clearConversation(userId: string, channelId: string): void {
    const key = this.getKey(userId, channelId);
    this.conversations.delete(key);
  }

  // Cleanup expired conversations periodically
  cleanup(): void {
    const now = Date.now();
    for (const [key, conversation] of this.conversations.entries()) {
      if (now - conversation.lastActivity > this.timeoutMs) {
        this.conversations.delete(key);
      }
    }
  }
}

export const conversationManager = new ConversationManager();

// Run cleanup every 10 minutes
setInterval(() => conversationManager.cleanup(), 10 * 60 * 1000);
```

Create `src/commands/index.ts`

to handle command registration:

``` js
import { REST, Routes, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';

export const commands = [
  new SlashCommandBuilder()
    .setName('ask')
    .setDescription('Ask the AI a question')
    .addStringOption((option) =>
      option
        .setName('question')
        .setDescription('Your question for the AI')
        .setRequired(true)
    ),

  new SlashCommandBuilder()
    .setName('chat')
    .setDescription('Have a conversation with the AI')
    .addStringOption((option) =>
      option
        .setName('message')
        .setDescription('Your message to the AI')
        .setRequired(true)
    ),

  new SlashCommandBuilder()
    .setName('summarize')
    .setDescription('Summarize recent channel messages')
    .addIntegerOption((option) =>
      option
        .setName('count')
        .setDescription('Number of messages to summarize (default: 20)')
        .setMinValue(5)
        .setMaxValue(100)
    ),

  new SlashCommandBuilder()
    .setName('clear')
    .setDescription('Clear your conversation history with the AI'),

  new SlashCommandBuilder()
    .setName('help')
    .setDescription('Show available commands and how to use them'),
];

export async function registerCommands(): Promise<void> {
  const rest = new REST().setToken(config.discord.token);

  try {
    console.log('Registering slash commands...');

    await rest.put(Routes.applicationCommands(config.discord.clientId), {
      body: commands.map((cmd) => cmd.toJSON()),
    });

    console.log('Slash commands registered successfully!');
  } catch (error) {
    console.error('Error registering commands:', error);
    throw error;
  }
}
```

Create `src/commands/ask.ts`

:

``` js
import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
import { generateResponse } from '../services/neurolink';

export async function handleAskCommand(
  interaction: ChatInputCommandInteraction
): Promise<void> {
  const question = interaction.options.getString('question', true);

  await interaction.deferReply();

  try {
    const response = await generateResponse(question);

    const embed = new EmbedBuilder()
      .setColor(0x5865f2)
      .setTitle('AI Response')
      .setDescription(response)
      .setFooter({
        text: `Asked by ${interaction.user.username}`,
        iconURL: interaction.user.displayAvatarURL(),
      })
      .setTimestamp();

    await interaction.editReply({ embeds: [embed] });
  } catch (error) {
    console.error('Error in ask command:', error);
    await interaction.editReply({
      content: 'Sorry, I encountered an error while processing your question. Please try again.',
    });
  }
}
```

Create `src/commands/chat.ts`

:

``` js
import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
import { generateChatResponse, ChatMessage } from '../services/neurolink';
import { conversationManager } from '../services/conversation';

const CHAT_SYSTEM_PROMPT = `You are a friendly and helpful Discord bot assistant named NeuroBot.
You can engage in natural conversations, answer questions, help with coding, and provide information.
Keep responses concise but informative. Use markdown formatting when appropriate.
Remember context from previous messages in the conversation.`;

export async function handleChatCommand(
  interaction: ChatInputCommandInteraction
): Promise<void> {
  const message = interaction.options.getString('message', true);
  const userId = interaction.user.id;
  const channelId = interaction.channelId;

  await interaction.deferReply();

  try {
    // Get existing conversation history
    const history = conversationManager.getConversation(userId, channelId);

    // Add user's new message
    const userMessage: ChatMessage = { role: 'user', content: message };
    conversationManager.addMessage(userId, channelId, userMessage);

    // Generate response with full history
    const response = await generateChatResponse(
      [...history, userMessage],
      CHAT_SYSTEM_PROMPT
    );

    // Store assistant's response in history
    conversationManager.addMessage(userId, channelId, {
      role: 'assistant',
      content: response,
    });

    const embed = new EmbedBuilder()
      .setColor(0x57f287)
      .setDescription(response)
      .setFooter({
        text: `Conversation with ${interaction.user.username} | Use /clear to reset`,
        iconURL: interaction.user.displayAvatarURL(),
      });

    await interaction.editReply({ embeds: [embed] });
  } catch (error) {
    console.error('Error in chat command:', error);
    await interaction.editReply({
      content: 'Sorry, I encountered an error. Please try again.',
    });
  }
}
```

Create `src/commands/summarize.ts`

:

``` js
import {
  ChatInputCommandInteraction,
  EmbedBuilder,
  TextChannel,
  ChannelType,
} from 'discord.js';
import { summarizeText } from '../services/neurolink';

export async function handleSummarizeCommand(
  interaction: ChatInputCommandInteraction
): Promise<void> {
  const count = interaction.options.getInteger('count') || 20;

  if (interaction.channel?.type !== ChannelType.GuildText) {
    await interaction.reply({
      content: 'This command can only be used in text channels.',
      ephemeral: true,
    });
    return;
  }

  await interaction.deferReply();

  try {
    const channel = interaction.channel as TextChannel;
    const messages = await channel.messages.fetch({ limit: count });

    // Filter out bot messages and format for summarization
    const humanMessages = messages
      .filter((msg) => !msg.author.bot && msg.content.length > 0)
      .reverse()
      .map((msg) => `${msg.author.username}: ${msg.content}`)
      .join('\n');

    if (!humanMessages) {
      await interaction.editReply({
        content: 'No messages found to summarize.',
      });
      return;
    }

    const summary = await summarizeText(humanMessages);

    const embed = new EmbedBuilder()
      .setColor(0xfee75c)
      .setTitle(`Summary of Last ${count} Messages`)
      .setDescription(summary)
      .setFooter({
        text: `Requested by ${interaction.user.username}`,
        iconURL: interaction.user.displayAvatarURL(),
      })
      .setTimestamp();

    await interaction.editReply({ embeds: [embed] });
  } catch (error) {
    console.error('Error in summarize command:', error);
    await interaction.editReply({
      content: 'Sorry, I could not summarize the messages. Please try again.',
    });
  }
}
```

Create `src/events/ready.ts`

:

``` js
import { Client, ActivityType } from 'discord.js';

export function handleReady(client: Client<true>): void {
  console.log(`Bot is online as ${client.user.tag}`);
  console.log(`Serving ${client.guilds.cache.size} server(s)`);

  // Set bot status
  client.user.setPresence({
    activities: [
      {
        name: '/help for commands',
        type: ActivityType.Listening,
      },
    ],
    status: 'online',
  });
}
```

Create `src/events/interactionCreate.ts`

:

``` js
import { Interaction, EmbedBuilder } from 'discord.js';
import { handleAskCommand } from '../commands/ask';
import { handleChatCommand } from '../commands/chat';
import { handleSummarizeCommand } from '../commands/summarize';
import { conversationManager } from '../services/conversation';

export async function handleInteraction(interaction: Interaction): Promise<void> {
  if (!interaction.isChatInputCommand()) return;

  const { commandName } = interaction;

  try {
    switch (commandName) {
      case 'ask':
        await handleAskCommand(interaction);
        break;

      case 'chat':
        await handleChatCommand(interaction);
        break;

      case 'summarize':
        await handleSummarizeCommand(interaction);
        break;

      case 'clear':
        conversationManager.clearConversation(
          interaction.user.id,
          interaction.channelId
        );
        await interaction.reply({
          content: 'Your conversation history has been cleared!',
          ephemeral: true,
        });
        break;

      case 'help':
        await handleHelpCommand(interaction);
        break;

      default:
        await interaction.reply({
          content: 'Unknown command.',
          ephemeral: true,
        });
    }
  } catch (error) {
    console.error(`Error handling command ${commandName}:`, error);

    const errorMessage = 'An error occurred while processing your command.';
    if (interaction.deferred || interaction.replied) {
      await interaction.editReply({ content: errorMessage });
    } else {
      await interaction.reply({ content: errorMessage, ephemeral: true });
    }
  }
}

async function handleHelpCommand(interaction: Interaction): Promise<void> {
  if (!interaction.isChatInputCommand()) return;

  const embed = new EmbedBuilder()
    .setColor(0x5865f2)
    .setTitle('NeuroLink Bot Commands')
    .setDescription('Here are all available commands:')
    .addFields(
      {
        name: '/ask <question>',
        value: 'Ask a one-off question to the AI',
      },
      {
        name: '/chat <message>',
        value: 'Have a conversation with the AI (remembers context)',
      },
      {
        name: '/summarize [count]',
        value: 'Summarize recent messages in the channel',
      },
      {
        name: '/clear',
        value: 'Clear your conversation history',
      },
      {
        name: '/help',
        value: 'Show this help message',
      }
    )
    .setFooter({ text: 'Powered by NeuroLink AI' });

  await interaction.reply({ embeds: [embed], ephemeral: true });
}
```

Create `src/index.ts`

:

``` js
import { Client, GatewayIntentBits, Partials } from 'discord.js';
import { config } from './config';
import { registerCommands } from './commands';
import { handleReady } from './events/ready';
import { handleInteraction } from './events/interactionCreate';

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

// Event handlers
client.once('ready', (c) => handleReady(c));
client.on('interactionCreate', handleInteraction);

// Error handling
client.on('error', (error) => {
  console.error('Discord client error:', error);
});

process.on('unhandledRejection', (error) => {
  console.error('Unhandled promise rejection:', error);
});

// Start the bot
async function main(): Promise<void> {
  try {
    await registerCommands();
    await client.login(config.discord.token);
  } catch (error) {
    console.error('Failed to start bot:', error);
    process.exit(1);
  }
}

main();
```

Protect your API usage with rate limiting. Create `src/utils/rateLimiter.ts`

:

```
interface RateLimitEntry {
  count: number;
  resetTime: number;
}

class RateLimiter {
  private limits: Map<string, RateLimitEntry> = new Map();
  private readonly maxRequests: number;
  private readonly windowMs: number;

  constructor(maxRequests = 10, windowMs = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
  }

  isRateLimited(userId: string): boolean {
    const now = Date.now();
    const entry = this.limits.get(userId);

    if (!entry || now > entry.resetTime) {
      this.limits.set(userId, {
        count: 1,
        resetTime: now + this.windowMs,
      });
      return false;
    }

    if (entry.count >= this.maxRequests) {
      return true;
    }

    entry.count++;
    return false;
  }

  getRemainingTime(userId: string): number {
    const entry = this.limits.get(userId);
    if (!entry) return 0;
    return Math.max(0, entry.resetTime - Date.now());
  }
}

export const rateLimiter = new RateLimiter(10, 60000); // 10 requests per minute
```

For long responses, implement streaming updates:

``` js
import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
import { NeuroLink } from '@juspay/neurolink';

export async function handleStreamingResponse(
  interaction: ChatInputCommandInteraction,
  prompt: string
): Promise<void> {
  await interaction.deferReply();

  const neurolink = new NeuroLink();

  let fullResponse = '';
  let lastUpdate = Date.now();
  const updateInterval = 1500; // Update every 1.5 seconds

  const result = await neurolink.stream({
    input: { text: prompt },
    systemPrompt: 'You are a helpful Discord bot assistant.',
    provider: 'openai',
    model: 'gpt-4o',
  });

  for await (const chunk of result.stream) {
    if ('content' in chunk) {
      fullResponse += chunk.content;
    }

    // Update message periodically to avoid rate limits
    if (Date.now() - lastUpdate > updateInterval) {
      const embed = new EmbedBuilder()
        .setDescription(fullResponse + ' ...')
        .setColor(0x5865f2);

      await interaction.editReply({ embeds: [embed] });
      lastUpdate = Date.now();
    }
  }

  // Final update
  const finalEmbed = new EmbedBuilder()
    .setDescription(fullResponse)
    .setColor(0x57f287)
    .setFooter({ text: 'Response complete' });

  await interaction.editReply({ embeds: [finalEmbed] });
}
```

Add automatic responses to @mentions:

``` js
// Add to src/index.ts
client.on('messageCreate', async (message) => {
  // Ignore bots and messages without mentions
  if (message.author.bot) return;
  if (!message.mentions.has(client.user!)) return;

  // Remove the mention from the message
  const content = message.content
    .replace(/<@!?\d+>/g, '')
    .trim();

  if (!content) {
    await message.reply('How can I help you? Ask me anything!');
    return;
  }

  try {
    await message.channel.sendTyping();

    const response = await generateResponse(content, {
      systemPrompt: 'You are a helpful Discord bot. Keep responses brief and friendly.',
      maxTokens: 500,
    });

    await message.reply(response);
  } catch (error) {
    console.error('Error responding to mention:', error);
    await message.reply('Sorry, I encountered an error. Please try again.');
  }
});
```

Update `package.json`

:

```
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "nodemon --exec ts-node src/index.ts",
    "register": "ts-node src/commands/index.ts"
  }
}
```

Create a `Dockerfile`

:

```
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY dist/ ./dist/

ENV NODE_ENV=production

CMD ["node", "dist/index.js"]
```

Create `docker-compose.yml`

:

```
version: '3.8'
services:
  bot:
    build: .
    restart: unless-stopped
    env_file:
      - .env
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
```

**Railway:**

```
railway login
railway init
railway up
```

**Fly.io:**

```
fly launch
fly secrets set DISCORD_TOKEN=xxx NEUROLINK_API_KEY=xxx
fly deploy
```

**DigitalOcean App Platform:**

Always wrap API calls in try-catch blocks and provide user-friendly error messages:

``` js
try {
  const response = await generateResponse(prompt);
  await interaction.editReply({ content: response });
} catch (error) {
  if (error.code === 'RATE_LIMITED') {
    await interaction.editReply({
      content: 'I\'m receiving too many requests. Please wait a moment.',
    });
  } else {
    await interaction.editReply({
      content: 'Something went wrong. Please try again later.',
    });
  }
  console.error('API Error:', error);
}
```

Note:Timeout-based cancellation is available via the`timeout`

parameter in NeuroLink.

Create a simple test script:

``` js
// src/test.ts
import { generateResponse } from './services/neurolink';

async function test() {
  console.log('Testing NeuroLink integration...');

  const response = await generateResponse('What is Discord?');
  console.log('Response:', response);

  console.log('Test completed successfully!');
}

test().catch(console.error);
```

Run with: `npx ts-node src/test.ts`

You built an AI Discord bot with slash commands, multi-turn conversations, thread summarization, and mention responses using Discord.js and NeuroLink's generation API. The bot handles natural language queries, maintains conversation context per thread, and provides AI-powered moderation and analysis.

Continue with these related tutorials:

*Have questions about building Discord bots with NeuroLink? Join our Discord community or reach out on Twitter.*

**Related posts:**
