
Security News
Scaling Socket from Zero to 10,000+ Organizations
Socket CEO Feross Aboukhadijeh shares lessons from scaling a developer security startup to 10,000+ organizations in this founder interview.
discord-conversation-wizard
Advanced tools
A powerful, library-agnostic wizard for creating multi-step conversations and forms in Discord bots. Supports discord.js and Eris with advanced validation, middleware, and state management.
A powerful, library-agnostic wizard for creating multi-step conversations and forms in Discord bots.
npm install discord-conversation-wizard
For Discord.js:
npm install discord.js@^14.0.0
For Eris:
npm install eris@^0.17.0
import { Client, GatewayIntentBits } from 'discord.js';
import { Wizard, DiscordJSAdapter, StepType } from 'discord-conversation-wizard';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
client.on('messageCreate', async (message) => {
if (message.content === '!register') {
const adapter = new DiscordJSAdapter(client);
const wizard = new Wizard(adapter, {
steps: [
{
id: 'name',
type: StepType.TEXT,
prompt: '๐ What is your name?',
validate: (response) => {
if (response.length < 2) return 'Name must be at least 2 characters';
if (response.length > 32) return 'Name must be less than 32 characters';
return true;
},
},
{
id: 'age',
type: StepType.NUMBER,
prompt: '๐ How old are you?',
minValue: 13,
maxValue: 120,
},
{
id: 'role',
type: StepType.SELECT_MENU,
prompt: '๐ญ Select your preferred role:',
options: [
{ label: 'Developer', value: 'dev', emoji: '๐ป' },
{ label: 'Designer', value: 'design', emoji: '๐จ' },
{ label: 'Manager', value: 'manager', emoji: '๐' },
],
},
],
allowBack: true,
allowCancel: true,
});
wizard.on('complete', (responses) => {
message.channel.send(
`โ
Registration complete!\n` +
`**Name:** ${responses.name}\n` +
`**Age:** ${responses.age}\n` +
`**Role:** ${responses.role}`
);
});
wizard.on('cancel', () => {
message.channel.send('โ Registration cancelled.');
});
await wizard.start({
userId: message.author.id,
channelId: message.channel.id,
guildId: message.guild?.id,
});
}
});
client.login('YOUR_BOT_TOKEN');
import Eris from 'eris';
import { Wizard, ErisAdapter, StepType } from 'discord-conversation-wizard';
const bot = new Eris('YOUR_BOT_TOKEN');
bot.on('messageCreate', async (message) => {
if (message.content === '!survey') {
const adapter = new ErisAdapter(bot);
const wizard = new Wizard(adapter, {
steps: [
{
id: 'feedback',
type: StepType.TEXT,
prompt: '๐ฌ What do you think about our service?',
maxLength: 500,
},
{
id: 'rating',
type: StepType.SELECT_MENU,
prompt: 'โญ Rate your experience:',
options: [
{ label: 'โญโญโญโญโญ Excellent', value: '5' },
{ label: 'โญโญโญโญ Good', value: '4' },
{ label: 'โญโญโญ Average', value: '3' },
{ label: 'โญโญ Poor', value: '2' },
{ label: 'โญ Terrible', value: '1' },
],
},
],
});
wizard.on('complete', (responses) => {
bot.createMessage(message.channel.id, `Thank you for your feedback! Rating: ${responses.rating}/5`);
});
await wizard.start({
userId: message.author.id,
channelId: message.channel.id,
});
}
});
bot.connect();
Show or hide steps based on previous responses:
const wizard = new Wizard(adapter, {
steps: [
{
id: 'hasExperience',
type: StepType.CONFIRMATION,
prompt: 'Do you have previous experience?',
},
{
id: 'yearsOfExperience',
type: StepType.NUMBER,
prompt: 'How many years of experience do you have?',
minValue: 0,
maxValue: 50,
condition: (responses) => responses.hasExperience === 'wizard_confirm_hasExperience',
},
],
});
Add custom logic at different stages:
const wizard = new Wizard(adapter, {
steps: [...],
middleware: {
beforeStep: async (step, context) => {
console.log(`Starting step: ${step.id}`);
},
afterStep: async (step, response, context) => {
console.log(`Completed step ${step.id} with response:`, response);
// Save to database, log analytics, etc.
},
onError: async (error, step, context) => {
console.error(`Error in step ${step.id}:`, error);
// Send to error tracking service
},
onComplete: async (responses, context) => {
console.log('Wizard completed!', responses);
// Save final data to database
},
onCancel: async (context) => {
console.log('Wizard cancelled at step:', context.stepId);
},
},
});
Transform user input before validation:
{
id: 'email',
type: StepType.TEXT,
prompt: '๐ง Enter your email address:',
transform: (response) => response.toLowerCase().trim(),
validate: (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email) || 'Please enter a valid email address';
},
}
Allow users to select multiple options:
{
id: 'interests',
type: StepType.SELECT_MENU,
prompt: '๐ฏ Select your interests (you can choose multiple):',
allowMultiple: true,
options: [
{ label: 'Gaming', value: 'gaming', emoji: '๐ฎ' },
{ label: 'Music', value: 'music', emoji: '๐ต' },
{ label: 'Sports', value: 'sports', emoji: 'โฝ' },
{ label: 'Art', value: 'art', emoji: '๐จ' },
{ label: 'Technology', value: 'tech', emoji: '๐ป' },
],
}
Save wizard state and resume later:
const wizard = new Wizard(adapter, {
steps: [...],
persistSession: true,
sessionId: 'user_' + userId, // Optional custom session ID
});
wizard.on('sessionSaved', (session) => {
console.log('Session saved:', session.sessionId);
});
// Resume a previously started wizard
await wizard.resume('session_id_here');
Enable powerful navigation controls:
const wizard = new Wizard(adapter, {
steps: [...],
allowBack: true, // User can type "back" to go to previous step
allowSkip: true, // User can type "skip" to skip optional steps
allowCancel: true, // User can type "cancel" to abort the wizard
});
// Programmatic navigation
await wizard.goBack();
await wizard.skip();
await wizard.jumpToStep('stepId');
wizard.cancel();
Request and validate file uploads:
{
id: 'avatar',
type: StepType.ATTACHMENT,
prompt: '๐ Upload your profile picture:',
validate: (attachment) => {
const validTypes = ['image/png', 'image/jpeg', 'image/gif'];
if (!validTypes.includes(attachment.contentType)) {
return 'Please upload a PNG, JPEG, or GIF image';
}
if (attachment.size > 5 * 1024 * 1024) {
return 'Image must be smaller than 5MB';
}
return true;
},
}
Configure maximum retry attempts for validation failures:
{
id: 'code',
type: StepType.TEXT,
prompt: 'Enter the verification code:',
retry: 3, // Maximum 3 attempts
validate: (code) => {
return code === 'SECRET123' || 'Invalid verification code';
},
}
Use pre-built validators for common validation patterns:
import { validators } from 'discord-conversation-wizard';
const wizard = new Wizard(adapter, {
steps: [
{
id: 'email',
type: StepType.TEXT,
prompt: '๐ง Enter your email:',
validate: validators.email(),
transform: (value) => value.toLowerCase().trim(),
},
{
id: 'website',
type: StepType.TEXT,
prompt: '๐ Enter your website:',
validate: validators.url({ requireProtocol: false }),
},
{
id: 'phone',
type: StepType.TEXT,
prompt: '๐ฑ Enter your phone number:',
validate: validators.phone(),
},
{
id: 'username',
type: StepType.TEXT,
prompt: '๐ค Enter your username:',
validate: validators.regex(/^[a-zA-Z0-9_]{3,16}$/, {
message: 'Username must be 3-16 characters (letters, numbers, underscores only)'
}),
},
{
id: 'bio',
type: StepType.TEXT,
prompt: '๐ Write your bio:',
validate: validators.length({ min: 10, max: 500 }),
},
{
id: 'age',
type: StepType.NUMBER,
prompt: '๐ Enter your age:',
validate: validators.range({ min: 13, max: 120 }),
},
{
id: 'password',
type: StepType.TEXT,
prompt: '๐ Create a password:',
validate: validators.combine([
validators.length({ min: 8, max: 128 }),
validators.regex(/[A-Z]/, { message: 'Must contain uppercase' }),
validators.regex(/[0-9]/, { message: 'Must contain number' }),
]),
},
],
});
Available Validators:
validators.email() - Valid email addressvalidators.url() - Valid URL with optional protocol requirementsvalidators.phone() - International phone number formatvalidators.regex(pattern) - Custom regex pattern matchingvalidators.length({ min, max }) - String length validationvalidators.range({ min, max }) - Numeric range validationvalidators.combine([...]) - Combine multiple validators (AND logic)validators.oneOf([...]) - Value must be in allowed listShow visual progress throughout the wizard:
const wizard = new Wizard(adapter, {
steps: [...],
showProgress: true,
progressFormat: '๐ Step {current}/{total}', // or '{percent}%'
});
Format Placeholders:
{current} - Current step number (1-indexed){total} - Total number of steps{percent} - Progress percentage (0-100)Default format: ๐ Step {current}/{total}
Send warnings before response timeout expires:
const wizard = new Wizard(adapter, {
steps: [...],
timeout: 60, // seconds
timeoutWarning: true, // warns 15 seconds before by default
// OR
timeoutWarning: 20, // warns 20 seconds before timeout
timeoutWarningMessage: 'โฐ Hurry! Time is running out!',
});
Options:
timeoutWarning: true - Warns 15 seconds before timeout (default)timeoutWarning: <number> - Warns N seconds before timeouttimeoutWarningMessage - Custom warning messageWizardMain wizard class for managing conversation flow.
new Wizard(adapter: AdapterInterface, options: WizardOptions)
start(context: WizardContext): Promise<void> - Start the wizardresume(sessionId: string): Promise<boolean> - Resume a saved sessioncancel(): void - Cancel the wizardgoBack(): Promise<boolean> - Navigate to previous stepskip(): Promise<boolean> - Skip current stepjumpToStep(stepId: string): Promise<boolean> - Jump to a specific stepgetResponses(): Record<string, any> - Get all collected responsesgetCurrentStepIndex(): number - Get current step indexisActive(): boolean - Check if wizard is runninggetSessionId(): string - Get session IDstart - Wizard startedstep - New step startedskip - Step was skippedcomplete - Wizard completed successfullycancel - Wizard was cancellederror - Error occurredsessionSaved - Session was savedresume - Session was resumedmaxRetriesReached - Maximum retry attempts reachedWizardOptionsConfiguration options for the wizard.
interface WizardOptions {
steps: WizardStep[];
title?: string;
timeout?: number;
middleware?: WizardMiddleware;
allowBack?: boolean;
allowSkip?: boolean;
allowCancel?: boolean;
sessionId?: string;
persistSession?: boolean;
}
WizardStepConfiguration for a single step.
interface WizardStep {
id: string;
prompt: string;
type: StepType;
options?: StepOption[];
validate?: (response: any, context: WizardStepContext) => boolean | string | Promise<boolean | string>;
transform?: (response: any, context: WizardStepContext) => any | Promise<any>;
timeout?: number;
minLength?: number;
maxLength?: number;
minValue?: number;
maxValue?: number;
required?: boolean;
allowMultiple?: boolean;
condition?: (responses: Record<string, any>, context: WizardContext) => boolean | Promise<boolean>;
onSkip?: (context: WizardStepContext) => void | Promise<void>;
retry?: number;
}
StepTypeAvailable step types:
StepType.TEXT - Text inputStepType.NUMBER - Numeric input with validationStepType.SELECT_MENU - Discord select menu (dropdown)StepType.BUTTON - Button interactionsStepType.CONFIRMATION - Yes/No confirmation buttonsStepType.ATTACHMENT - File uploadContributions are welcome! Please check out the Contributing Guide for more information.
This project is licensed under the MIT License - see the LICENSE file for details.
Created with โค๏ธ by Jersuxs
โญ Star us on GitHub if you find this helpful!
FAQs
A powerful, library-agnostic wizard for creating multi-step conversations and forms in Discord bots. Supports discord.js and Eris with advanced validation, middleware, and state management.
We found that discord-conversation-wizard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh shares lessons from scaling a developer security startup to 10,000+ organizations in this founder interview.

Research
Socket Threat Research maps a rare inside look at OtterCookieโs npm-Vercel-GitHub chain, adding 197 malicious packages and evidence of North Korean operators.

Research
Socket researchers identified a malicious Chrome extension that manipulates Raydium swaps to inject an undisclosed SOL transfer, quietly routing fees to an attacker wallet.