Skip to main content
This example demonstrates how to create a VK bot with interactive keyboards using the Keyboard builder. You’ll learn how to create buttons with payloads and handle button clicks.

What You’ll Learn

  • Building keyboards with the Keyboard builder
  • Creating text buttons with payloads
  • Organizing buttons into rows
  • Styling buttons with colors
  • Handling button clicks via payloads
  • Creating custom command wrappers

Prerequisites

1

Install dependencies

npm install vk-io @vk-io/hear
2

Set up your access token

export TOKEN=your_vk_access_token

Complete Example

const { VK, Keyboard } = require('vk-io');
const { HearManager } = require('@vk-io/hear');

const vk = new VK({
    token: process.env.TOKEN,
});

const hearManager = new HearManager();

// Extract command from button payload
vk.updates.on('message_new', (context, next) => {
    const { messagePayload } = context;

    context.state.command = messagePayload?.command || null;

    return next();
});

vk.updates.on('message_new', hearManager.middleware);

// Simple wrapper for commands
const hearCommand = (name, conditions, handle) => {
    if (typeof handle !== 'function') {
        handle = conditions;
        conditions = [`/${name}`];
    }

    if (!Array.isArray(conditions)) {
        conditions = [conditions];
    }

    hearManager.hear([(_text, { state }) => state.command === name, ...conditions], handle);
};

// Handle start button
hearCommand('start', (context, next) => {
    context.state.command = 'help';

    return Promise.all([
        context.send('Hello!'),

        next(),
    ]);
});

hearCommand('help', async context => {
    await context.send({
        message: `
            My commands list

            /help - The help
            /time - The current date
            /cat - Cat photo
            /purr - Cat purring
        `,
        keyboard: Keyboard.builder()
            .textButton({
                label: 'The help',
                payload: {
                    command: 'help',
                },
            })
            .row()
            .textButton({
                label: 'The current date',
                payload: {
                    command: 'time',
                },
            })
            .row()
            .textButton({
                label: 'Cat photo',
                payload: {
                    command: 'cat',
                },
                color: Keyboard.PRIMARY_COLOR,
            })
            .textButton({
                label: 'Cat purring',
                payload: {
                    command: 'purr',
                },
                color: Keyboard.PRIMARY_COLOR,
            }),
    });
});

hearCommand('cat', async context => {
    await Promise.all([
        context.send('Wait for the uploads awesome 😻'),

        context.sendPhotos({
            value: 'https://loremflickr.com/400/300/',
        }),
    ]);
});

hearCommand('time', ['/time', '/date'], async context => {
    await context.send(String(new Date()));
});

const catsPurring = [
    'http://ronsen.org/purrfectsounds/purrs/trip.mp3',
    'http://ronsen.org/purrfectsounds/purrs/maja.mp3',
    'http://ronsen.org/purrfectsounds/purrs/chicken.mp3',
];

hearCommand('purr', async context => {
    const link = catsPurring[Math.floor(Math.random() * catsPurring.length)];

    await Promise.all([
        context.send('Wait for the uploads purring 😻'),

        context.sendAudioMessage({
            value: link,
        }),
    ]);
});

vk.updates.start().catch(console.error);

Code Breakdown

Extracting Payload Commands

vk.updates.on('message_new', (context, next) => {
    const { messagePayload } = context;

    context.state.command = messagePayload?.command || null;

    return next();
});
This middleware extracts the command from button payloads and stores it in context.state. This allows you to handle both text commands (like /help) and button clicks with the same handler.
Button payloads are automatically parsed as JSON objects. You can access them via context.messagePayload.

Custom Command Wrapper

const hearCommand = (name, conditions, handle) => {
    if (typeof handle !== 'function') {
        handle = conditions;
        conditions = [`/${name}`];
    }

    if (!Array.isArray(conditions)) {
        conditions = [conditions];
    }

    hearManager.hear([(_text, { state }) => state.command === name, ...conditions], handle);
};
This helper function simplifies command handling by:
  1. Checking if the payload command matches
  2. Checking if the text command matches
  3. Supporting multiple command aliases

Building Keyboards

Keyboard.builder()
    .textButton({
        label: 'The help',
        payload: {
            command: 'help',
        },
    })
    .row()
    .textButton({
        label: 'The current date',
        payload: {
            command: 'time',
        },
    })
    .row()
    .textButton({
        label: 'Cat photo',
        payload: {
            command: 'cat',
        },
        color: Keyboard.PRIMARY_COLOR,
    })
    .textButton({
        label: 'Cat purring',
        payload: {
            command: 'purr',
        },
        color: Keyboard.PRIMARY_COLOR,
    })
The Keyboard.builder() provides a fluent API for creating keyboards:
  • .textButton() - Adds a text button
  • .row() - Starts a new row of buttons
  • payload - Data sent when button is clicked
  • color - Button color (PRIMARY_COLOR, SECONDARY_COLOR, NEGATIVE_COLOR, POSITIVE_COLOR)
Buttons in the same row (before calling .row()) will be displayed horizontally. Each row can contain up to 5 buttons.

Button Colors

VK-IO provides predefined color constants:
Keyboard.PRIMARY_COLOR

Advanced: Keyboard Builder Cloning

You can clone keyboards to create variations:
const { Keyboard } = require('vk-io');

const baseBuilder = Keyboard.builder();

const userIsNotRegistered = true;

if (userIsNotRegistered) {
    baseBuilder.textButton({
        label: 'Sign Up',
        payload: {
            command: 'sign_up',
        },
    });
}

const shopBuilder = baseBuilder.clone();

shopBuilder
    .textButton({
        label: 'Buy a coffee',
        payload: {
            command: 'buy',
            item: 'coffee',
        },
    })
    .textButton({
        label: 'Buy a tea',
        payload: {
            command: 'buy',
            item: 'tea',
        },
    })
    .row()
    .textButton({
        label: 'Go back',
        payload: {
            command: 'go_back',
        },
    });
Use .clone() to create keyboard variations based on user state. This is useful for conditional buttons like login/logout or different menu options.

Keyboard Types

VK-IO supports different keyboard types:

Text Buttons

.textButton({
    label: 'Click me',
    payload: { action: 'click' },
    color: Keyboard.PRIMARY_COLOR,
})

URL Buttons

.urlButton({
    label: 'Open website',
    url: 'https://example.com',
})

Location Button

.locationRequestButton({
    payload: { request: 'location' },
})

VK Pay Button

.payButton({
    hash: 'payment_hash',
})

VK Apps Button

.applicationButton({
    label: 'Open app',
    appId: 123456,
    ownerId: -123456,
    hash: 'app_hash',
})

Running the Bot

node keyboard-bot.js
Once running, send /help or /start to your VK community to see the interactive keyboard. Click the buttons to interact with your bot!

Next Steps

Build docs developers (and LLMs) love