Discord Bot Integration for Feature Requests
Last Updated: 2026-01-12
Status: 📝 Documented (Not Yet Implemented)
Overview
This guide explains how to allow users to submit feature requests directly from Discord using a bot with slash commands.
Why Discord Integration?
- Convenience: Users can submit feedback without leaving Discord
- Community Engagement: Encourages participation from the Discord community
- Accessibility: Non-technical users may prefer Discord over GitHub
- Notifications: Team already monitors Discord channels
Architecture
Discord User
↓
/feature-request command
↓
Discord Bot (Node.js)
↓
Firebase Cloud Function (createFeatureRequest)
↓
GitHub Issue + Discord NotificationImplementation Options
Option 1: Discord Modal (RECOMMENDED)
Use Discord's built-in modal forms for structured input.
Pros:
- Native Discord UI
- Input validation
- Character limits enforced
- Best UX for Discord users
Cons:
- Requires custom Discord bot
- Additional hosting for bot
- More complex setup
Option 2: Web Form Link
Bot provides a link to the web form.
Pros:
- Simplest implementation
- Reuses existing web form
- No duplicate code
Cons:
- Takes user out of Discord
- Less seamless experience
Option 1: Discord Bot with Slash Commands
Prerequisites
- Discord Application with bot token
- Node.js hosting (Cloudflare Workers, Railway, Heroku, etc.)
- Firebase Functions already deployed
1. Create Discord Application
- Go to Discord Developer Portal
- Click "New Application"
- Name it "Lantern Feedback Bot"
- Go to "Bot" tab
- Click "Add Bot"
- Copy the bot token (save it securely)
- Enable "Message Content Intent" if needed
2. Set Up Bot Permissions
In the "OAuth2" → "URL Generator":
Scopes:
botapplications.commands
Bot Permissions:
- Send Messages
- Use Slash Commands
- Embed Links
Copy the generated URL and invite the bot to your Discord server.
3. Register Slash Command
Create /feature-request command:
// register-commands.js
import { REST, Routes, SlashCommandBuilder } from 'discord.js'
const commands = [
new SlashCommandBuilder()
.setName('feature-request')
.setDescription('Submit a feature request or bug report')
.toJSON()
]
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN)
async function registerCommands() {
try {
await rest.put(
Routes.applicationGuildCommands(
process.env.DISCORD_CLIENT_ID,
process.env.DISCORD_GUILD_ID
),
{ body: commands }
)
console.log('✅ Slash command registered!')
} catch (error) {
console.error('Error registering command:', error)
}
}
registerCommands()Run once to register:
node register-commands.js4. Create Bot Server
// bot.js
import { Client, GatewayIntentBits, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } from 'discord.js'
import { initializeApp } from 'firebase-admin/app'
import { getFunctions, httpsCallable } from 'firebase-admin/functions'
// Initialize Firebase Admin
initializeApp()
const client = new Client({
intents: [GatewayIntentBits.Guilds]
})
client.on('interactionCreate', async interaction => {
if (!interaction.isChatInputCommand()) return
if (interaction.commandName === 'feature-request') {
// Show modal form
const modal = new ModalBuilder()
.setCustomId('featureRequestModal')
.setTitle('Submit Feature Request')
// Request Type (doesn't support select in modals, use description field)
const typeInput = new TextInputBuilder()
.setCustomId('type')
.setLabel('Type (feature/bug/improvement/question)')
.setStyle(TextInputStyle.Short)
.setPlaceholder('feature')
.setRequired(true)
.setMaxLength(20)
// Title
const titleInput = new TextInputBuilder()
.setCustomId('title')
.setLabel('Title')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Brief summary of your request')
.setRequired(true)
.setMaxLength(100)
// Description
const descriptionInput = new TextInputBuilder()
.setCustomId('description')
.setLabel('Description')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('Detailed explanation...')
.setRequired(true)
.setMaxLength(1000)
// Use Case
const useCaseInput = new TextInputBuilder()
.setCustomId('useCase')
.setLabel('Use Case (Optional)')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('Example: As a user, when I...')
.setRequired(false)
.setMaxLength(500)
// Priority
const priorityInput = new TextInputBuilder()
.setCustomId('priority')
.setLabel('Priority (low/medium/high/critical)')
.setStyle(TextInputStyle.Short)
.setPlaceholder('medium')
.setRequired(true)
.setMaxLength(20)
// Add inputs to action rows (max 5 per modal)
const firstRow = new ActionRowBuilder().addComponents(typeInput)
const secondRow = new ActionRowBuilder().addComponents(titleInput)
const thirdRow = new ActionRowBuilder().addComponents(descriptionInput)
const fourthRow = new ActionRowBuilder().addComponents(useCaseInput)
const fifthRow = new ActionRowBuilder().addComponents(priorityInput)
modal.addComponents(firstRow, secondRow, thirdRow, fourthRow, fifthRow)
await interaction.showModal(modal)
}
})
// Handle modal submission
client.on('interactionCreate', async interaction => {
if (!interaction.isModalSubmit()) return
if (interaction.customId === 'featureRequestModal') {
await interaction.deferReply({ ephemeral: true })
try {
// Get form values
const type = interaction.fields.getTextInputValue('type').toLowerCase()
const title = interaction.fields.getTextInputValue('title')
const description = interaction.fields.getTextInputValue('description')
const useCase = interaction.fields.getTextInputValue('useCase') || null
const priority = interaction.fields.getTextInputValue('priority').toLowerCase()
// Validate type
const validTypes = ['feature', 'bug', 'improvement', 'question']
if (!validTypes.includes(type)) {
await interaction.editReply({
content: '❌ Invalid type. Must be: feature, bug, improvement, or question'
})
return
}
// Validate priority
const validPriorities = ['low', 'medium', 'high', 'critical']
if (!validPriorities.includes(priority)) {
await interaction.editReply({
content: '❌ Invalid priority. Must be: low, medium, high, or critical'
})
return
}
// Call Firebase Function
const createFeatureRequest = httpsCallable(getFunctions(), 'createFeatureRequest')
const result = await createFeatureRequest({
type,
title,
description,
useCase,
priority,
impact: 'individual', // Default for Discord submissions
isAnonymous: false, // Discord username will be in GitHub issue
submittedBy: interaction.user.tag // Discord username
})
// Success!
await interaction.editReply({
content: `✅ **Request Submitted!**\n\nGitHub Issue: ${result.data.issueUrl}\nIssue #${result.data.issueNumber}\n\nYou can track progress using the link above.`
})
} catch (error) {
console.error('Error submitting request:', error)
await interaction.editReply({
content: '❌ Failed to submit request. Please try again or use the web form.'
})
}
}
})
client.login(process.env.DISCORD_BOT_TOKEN)5. Deploy Bot
Option A: Cloudflare Workers
# Cloudflare Workers doesn't support Discord.js well
# Use Railway or Heroku insteadOption B: Railway (RECOMMENDED)
# 1. Create account at railway.app
# 2. New Project → Deploy from GitHub
# 3. Add environment variables:
# DISCORD_BOT_TOKEN=your_bot_token
# DISCORD_CLIENT_ID=your_client_id
# DISCORD_GUILD_ID=your_server_id
# FIREBASE_PROJECT_ID=lantern-app-dev (or prod)
# 4. Deploy!Option C: Heroku
heroku create lantern-discord-bot
heroku config:set DISCORD_BOT_TOKEN=your_token
heroku config:set FIREBASE_PROJECT_ID=lantern-app-dev
git push heroku main6. Testing
In Discord:
/feature-requestModal appears → Fill out form → Submit → Bot replies with GitHub issue link
Option 2: Simple Link Bot
Much simpler - just provides a link to the web form:
// simple-bot.js
import { Client, GatewayIntentBits } from 'discord.js'
const client = new Client({
intents: [GatewayIntentBits.Guilds]
})
client.on('interactionCreate', async interaction => {
if (!interaction.isChatInputCommand()) return
if (interaction.commandName === 'feedback') {
await interaction.reply({
content: '📝 **Submit Feedback**\n\n' +
'🌐 Web Form: https://ourlantern.app/#/feedback\n' +
'📖 GitHub Issues: https://github.com/cattreedev/lantern_app/issues\n\n' +
'Please use the web form to submit feature requests, bug reports, or questions.',
ephemeral: true
})
}
})
client.login(process.env.DISCORD_BOT_TOKEN)Register command:
new SlashCommandBuilder()
.setName('feedback')
.setDescription('Get link to submit feedback')Copilot Integration for Refinement
AI-Powered Request Improvement
When a user submits via Discord, the bot can use AI to refine the request before creating the GitHub issue.
import { Configuration, OpenAIApi } from 'openai' // Or Claude, etc.
async function refineWithAI(userInput) {
const prompt = `
You are helping refine a feature request for the Lantern app.
Lantern is an anonymous, location-based social app for meeting people at real places.
User's request:
Title: ${userInput.title}
Description: ${userInput.description}
Please suggest:
1. A clearer, more specific title (max 100 chars)
2. An improved description that explains:
- What problem this solves
- How it would work
- Why it matters
3. A concrete use case or user story
Keep the user's intent but make it more actionable for developers.
`
const response = await openai.createCompletion({
model: 'gpt-4',
prompt,
max_tokens: 500
})
return response.data.choices[0].text
}
// In the modal submission handler:
const refinedRequest = await refineWithAI({ title, description })
// Show user the suggestions
await interaction.followUp({
content: `🤖 **AI Suggestions:**\n\n${refinedRequest}\n\n` +
'Would you like to:\n' +
'1️⃣ Use AI suggestions\n' +
'2️⃣ Keep original\n' +
'3️⃣ Edit manually',
ephemeral: true
})Duplicate Detection in Discord
Before submitting, check for duplicates and warn the user:
const checkDuplicates = httpsCallable(getFunctions(), 'checkDuplicates')
const duplicates = await checkDuplicates({ title, description })
if (duplicates.data.similar.length > 0) {
const similarList = duplicates.data.similar
.slice(0, 3)
.map(req => `• #${req.githubIssueNumber}: ${req.title} (${Math.round(req.score * 100)}% similar)`)
.join('\n')
await interaction.followUp({
content: `⚠️ **Similar Requests Found:**\n\n${similarList}\n\n` +
'Your request may be a duplicate. Continue anyway?',
components: [
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('submit_anyway')
.setLabel('Submit Anyway')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Secondary)
)
],
ephemeral: true
})
}Environment Configuration
The Discord bot should respect the same dev/prod split:
Dev Bot:
- Uses lantern-app-dev Firebase project
- Submits to #dev-feedback Discord channel
- Creates issues with
env-developmentlabel
Prod Bot:
- Uses lantern-app-prod Firebase project
- Submits to #user-feedback Discord channel
- Creates issues with
env-productionlabel
Or use a single bot with environment detection based on the Discord server.
Security Considerations
- Rate Limiting: Enforce same 5 submissions per 24 hours per Discord user
- Validation: Always validate input (type, priority, character limits)
- Bot Token: Never commit the bot token - use environment variables
- Permissions: Bot should have minimal Discord permissions
- Authentication: Identify users by Discord ID, not username (usernames can change)
Monitoring
Track Discord submissions:
- Count per user
- Success/failure rate
- Most common request types
- Duplicate detection effectiveness
Cost Considerations
Discord Bot Hosting:
- Railway: Free tier available, then $5/month
- Heroku: Free tier deprecated, starts at $7/month
- Self-hosted: Free if you have a server
Firebase Functions:
- Included in free tier for low volume
- ~$0.40 per million invocations after free tier
AI Refinement (Optional):
- OpenAI GPT-4: ~$0.03 per request
- Claude: ~$0.008 per request
- Can skip AI to reduce costs
Implementation Status
| Feature | Status | Priority |
|---|---|---|
| Basic slash command | 📝 Documented | P1 |
| Modal form | 📝 Documented | P1 |
| Firebase integration | ✅ Ready | P1 |
| Duplicate detection | ✅ Ready | P2 |
| AI refinement | 📝 Documented | P3 |
| Simple link bot | 📝 Documented | P0 (Quick win) |
Quick Start (Simple Link Bot)
The fastest implementation - just add a /feedback command that links to the web form:
- Create Discord bot application
- Deploy 50-line Node.js script to Railway
- Invite bot to server
- Users type
/feedbackand get web form link
Estimated setup time: 30 minutes
Next Steps
- ⏳ Deploy simple link bot first (lowest effort, immediate value)
- ⏳ Implement full modal form bot (better UX)
- ⏳ Add AI refinement (optional enhancement)
- ⏳ Monitor usage and iterate