Skip to main content

Quick Start Guide

Get a working Telegram bot up and running in minutes. This guide will walk you through creating a simple bot that responds to messages.

Prerequisites

Before starting, ensure you have:
  • PHP 8.2+ (64-bit) installed
  • Basic knowledge of PHP
  • A Telegram account or bot token
New to MadelineProto? Check the Requirements page first to ensure your system is ready.

Your First Bot

Let’s create a simple bot that responds to all incoming messages.
1

Create bot.php

Create a new file called bot.php:
<?php declare(strict_types=1);

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\Plugin\RestartPlugin;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\SimpleEventHandler;

// Load MadelineProto
if (file_exists('vendor/autoload.php')) {
    require_once 'vendor/autoload.php';
} else {
    if (!file_exists('madeline.php')) {
        copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
    }
    require_once 'madeline.php';
}

class MyBot extends SimpleEventHandler
{
    public const ADMIN = "@me"; // Change this to your username!
    
    public function getReportPeers()
    {
        return [self::ADMIN];
    }
    
    public static function getPlugins(): array
    {
        return [
            RestartPlugin::class, // Enables /restart command
        ];
    }
    
    #[Handler]
    public function handleMessage(Incoming&Message $message): void
    {
        $message->reply("Hello! You said: " . $message->message);
    }
}

MyBot::startAndLoop('bot.madeline');
2

Run the bot

Execute the script:
php bot.php
On first run, you’ll be prompted to log in.
3

Login

Choose your login method:
Enter your bot token from @BotFather:
Enter your bot token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
A session file (bot.madeline) will be created. Next time you run the bot, it will use this session.
4

Test your bot

Send a message to your bot on Telegram. It should echo your message back!

Adding Commands

Let’s enhance the bot with command handling using filters:
<?php declare(strict_types=1);

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\SimpleEventHandler;
use danog\MadelineProto\ParseMode;

if (file_exists('vendor/autoload.php')) {
    require_once 'vendor/autoload.php';
} else {
    if (!file_exists('madeline.php')) {
        copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
    }
    require_once 'madeline.php';
}

class CommandBot extends SimpleEventHandler
{
    public const ADMIN = "@me";
    
    public function getReportPeers()
    {
        return [self::ADMIN];
    }
    
    // Handle /start command
    #[FilterCommand('start')]
    public function startCommand(Message $message): void
    {
        $message->reply(
            message: "**Welcome to MadelineProto!**\n\nAvailable commands:\n/start - Show this message\n/ping - Check if bot is alive\n/echo <text> - Echo your message",
            parseMode: ParseMode::MARKDOWN
        );
    }
    
    // Handle /ping command
    #[FilterCommand('ping')]
    public function pingCommand(Message $message): void
    {
        $message->reply("Pong! 🏓");
    }
    
    // Handle /echo command with arguments
    #[FilterCommand('echo')]
    public function echoCommand(Message $message): void
    {
        $args = $message->commandArgs;
        $message->reply($args[0] ?? 'You did not provide any text!');
    }
    
    // Handle all other incoming messages
    #[Handler]
    public function handleMessage(Incoming&Message $message): void
    {
        // Only respond to non-command messages
        if (!str_starts_with($message->message, '/')) {
            $message->reply("I received your message: " . $message->message);
        }
    }
}

CommandBot::startAndLoop('bot.madeline');

Working with Media

Here’s how to handle photos and files:
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\SimpleFilter\HasMedia;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;

// Send a photo
#[FilterCommand('photo')]
public function sendPhoto(Message $message): void
{
    $message->reply(
        message: "Here's a photo!",
        media: 'https://example.com/photo.jpg'
    );
}

// Handle incoming photos
#[Handler]
public function handlePhoto(Incoming&Message&HasMedia $message): void
{
    if ($message->media->photo ?? false) {
        $message->reply("Nice photo! I received it.");
        
        // Download the photo
        $message->media->downloadToDir('/path/to/save/');
    }
}

// Get download link for any media
#[FilterCommand('dl')]
public function downloadLink(Message $message): void
{
    $reply = $message->getReply(Message::class);
    if (!$reply?->media) {
        $message->reply("Reply to a media message to get download link!");
        return;
    }
    
    $reply->reply("Download: " . $reply->media->getDownloadLink());
}

Using Filters

MadelineProto offers powerful filtering with PHP attributes and intersection types:
use danog\MadelineProto\EventHandler\Filter\FilterRegex;
use danog\MadelineProto\EventHandler\Filter\FilterText;
use danog\MadelineProto\EventHandler\Message\PrivateMessage;
use danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin;
use danog\MadelineProto\EventHandler\SimpleFilter\IsReply;

// Match exact text
#[FilterText('hello')]
public function greet(Message $message): void
{
    $message->reply("Hello there!");
}

// Match with regex
#[FilterRegex('/.*(mt?proto)[^.]?.*/i')]
public function mentionMTProto(Message $message): void
{
    $message->reply("Did you mean MadelineProto instead of " . $message->matches[1] . "?");
}

// Only from admins, in private chats, that are replies
#[Handler]
public function adminReply(PrivateMessage&FromAdmin&IsReply $message): void
{
    $original = $message->getReply(Message::class);
    $message->reply("Admin replied to: " . $original->message);
}

Background Execution

For production, run your bot in the background:
screen -dmS telegram-bot php bot.php

# View logs
screen -r telegram-bot

# Detach: Press Ctrl+A, then D

Complete Example

Here’s a fully-featured bot with multiple handlers:
<?php declare(strict_types=1);

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\Plugin\RestartPlugin;
use danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\SimpleEventHandler;
use danog\MadelineProto\Settings;
use danog\MadelineProto\Logger;

if (file_exists('vendor/autoload.php')) {
    require_once 'vendor/autoload.php';
} else {
    if (!file_exists('madeline.php')) {
        copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
    }
    require_once 'madeline.php';
}

class MyBot extends SimpleEventHandler
{
    public const ADMIN = "@me";
    private array $stats = [];
    
    public function __sleep(): array
    {
        return ['stats'];
    }
    
    public function getReportPeers()
    {
        return [self::ADMIN];
    }
    
    public static function getPlugins(): array
    {
        return [RestartPlugin::class];
    }
    
    public function onStart(): void
    {
        $this->logger("Bot started!");
        $this->sendMessageToAdmins("Bot is now online!");
    }
    
    #[FilterCommand('start')]
    public function start(Message $message): void
    {
        $message->reply(
            message: "**Welcome to MadelineProto Bot!**\n\nCommands:\n/start - Start\n/ping - Test\n/stats - Statistics",
            parseMode: ParseMode::MARKDOWN
        );
    }
    
    #[FilterCommand('ping')]
    public function ping(Message $message): void
    {
        $message->reply("🏓 Pong!");
    }
    
    #[FilterCommand('stats')]
    public function stats(Message&FromAdmin $message): void
    {
        $total = array_sum($this->stats);
        $message->reply("Total messages handled: " . $total);
    }
    
    #[Handler]
    public function handleMessage(Incoming&Message $message): void
    {
        $chatId = $message->chatId;
        $this->stats[$chatId] = ($this->stats[$chatId] ?? 0) + 1;
        
        if (!str_starts_with($message->message, '/')) {
            $message->reply("Echo: " . $message->message);
        }
    }
}

$settings = new Settings;
$settings->getLogger()->setLevel(Logger::LEVEL_VERBOSE);

MyBot::startAndLoop('bot.madeline', $settings);

Next Steps

Handling Updates

Learn about event handlers, filters, and update processing

File Operations

Upload and download files up to 4GB

Database Setup

Configure MySQL, PostgreSQL, or Redis for reduced RAM usage

API Reference

Explore the full MTProto API documentation

Common Patterns

use danog\MadelineProto\ParseMode;

$message->reply(
    message: "**Bold** _italic_ `code`\n[Link](https://example.com)",
    parseMode: ParseMode::MARKDOWN
);

// Or HTML
$message->reply(
    message: "<b>Bold</b> <i>italic</i> <code>code</code>",
    parseMode: ParseMode::HTML
);
$message->reply(
    message: "Choose an option:",
    replyMarkup: [
        'inline_keyboard' => [
            [
                ['text' => 'Button 1', 'callback_data' => 'btn1'],
                ['text' => 'Button 2', 'callback_data' => 'btn2'],
            ],
            [
                ['text' => 'Visit Website', 'url' => 'https://docs.madelineproto.xyz'],
            ]
        ]
    ]
);
#[Handler]
public function handleMessage(Message $message): void
{
    $userId = $message->senderId;
    $chatId = $message->chatId;
    
    // Get full user info
    $userInfo = $this->getInfo($userId);
    
    // Get current bot info
    $me = $this->getSelf();
    
    $this->logger("User: " . $userInfo['User']['first_name']);
}
Need help? Join the MadelineProto support group or check out more examples in the GitHub repository.

Build docs developers (and LLMs) love