Skip to content

Wizards

Wizards guide users through multi-step conversations (forms, registration flows, onboarding). Each step is a function that runs sequentially.

Quick Start

typescript
import { Bot, session, Wizard } from 'vibegram';

const bot = new Bot('YOUR_BOT_TOKEN');
bot.use(session());

const registrationWizard = new Wizard('register', [
    async (ctx) => {
        await ctx.reply('Step 1: What is your name?');
        ctx.wizard?.next();
    },
    async (ctx) => {
        ctx.wizard!.state.name = ctx.message?.text;
        await ctx.reply(`Step 2: Hello ${ctx.wizard!.state.name}! What is your email?`);
        ctx.wizard?.next();
    },
    async (ctx) => {
        ctx.wizard!.state.email = ctx.message?.text;
        await ctx.reply(`Done!\nName: ${ctx.wizard!.state.name}\nEmail: ${ctx.wizard!.state.email}`);
        ctx.wizard?.leave();
    }
]);

bot.use(registrationWizard.middleware());

bot.command('register', ctx => registrationWizard.enter(ctx));

Wizard API

MethodDescription
wizard.enter(ctx)Start the wizard from step 0
ctx.wizard?.next()Advance to the next step
ctx.wizard?.back()Go back to the previous step
ctx.wizard?.leave()Exit the wizard
ctx.wizard?.stateShared state object across all steps
ctx.wizard?.cursorCurrent step index (0-based)

Wizard State

The ctx.wizard.state object persists across all steps within a single wizard session:

typescript
const orderWizard = new Wizard('order', [
    async (ctx) => {
        await ctx.reply('What product would you like to order?');
        ctx.wizard?.next();
    },
    async (ctx) => {
        ctx.wizard!.state.product = ctx.message?.text;
        await ctx.reply('How many units?');
        ctx.wizard?.next();
    },
    async (ctx) => {
        ctx.wizard!.state.quantity = parseInt(ctx.message?.text || '1');
        await ctx.reply('What is your delivery address?');
        ctx.wizard?.next();
    },
    async (ctx) => {
        const { product, quantity } = ctx.wizard!.state;
        const address = ctx.message?.text;
        await ctx.reply(`Order confirmed!\nProduct: ${product}\nQuantity: ${quantity}\nAddress: ${address}`);
        ctx.wizard?.leave();
    }
]);

Input Validation

typescript
const validated = new Wizard('validated', [
    async (ctx) => {
        await ctx.reply('Enter your age (number only):');
        ctx.wizard?.next();
    },
    async (ctx) => {
        const age = parseInt(ctx.message?.text || '');
        if (isNaN(age) || age < 1 || age > 150) {
            return ctx.reply('Invalid age. Please enter a valid number:');
            // Don't call next() — stay on same step
        }
        ctx.wizard!.state.age = age;
        await ctx.reply(`Age ${age} recorded. Done!`);
        ctx.wizard?.leave();
    }
]);

TIP

If you don't call ctx.wizard?.next(), the user stays on the current step — perfect for input validation loops.

Released under the ISC License.