Creating a Telegram Bot with Laravel 12
In this detailed guide, we will dive into the process of building our own Telegram bot using the power of PHP and the elegance of the Laravel framework. Thanks to its well-thought-out architecture, expressive syntax, and extensive ecosystem, Laravel is an ideal platform for building and later managing complex bots. We will go through the entire path together: from the very first step, registering a bot in Telegram, to deploying and writing its logic on a server running Ubuntu 24.04.
Why Laravel? Copy link
It is possible to write complex logic in “pure” PHP, but this is slow and not always efficient. This is where Laravel comes in: a PHP framework that provides a ready-made structure and a huge number of convenient development tools.
Laravel organizes your PHP code into an elegant and scalable system. It takes care of all the routine work:
- Routing: easily directs an incoming request from Telegram to the correct part of the code (controller).
- Working with the database: allows you to effortlessly save information about users, their messages, or settings.
- Processing HTTP requests: provides convenient tools for sending response messages back to the Telegram API.
- Queues and scheduled tasks: makes it possible to perform long operations (for example, mailings) in the background without slowing down bot responses.
Registering the Bot and Obtaining a Token Copy link
Before writing even a single line of code, we must perform a preparatory but very important action: register our future digital assistant in the Telegram ecosystem and obtain a unique access key, the API token.
-
Launch your Telegram client. In the search bar, enter the username
@BotFatherand select the official management bot marked with the blue verification checkmark. -
Initiate a dialogue: start communicating with it by sending the command
/newbot. This tells it that you intend to create a new bot. -
Choose a name and a unique username. BotFather will ask you to provide two names sequentially. The first is the public name that users will see (it can be anything). The second is the technical username, which must be absolutely unique within all of Telegram and must end with the suffix bot (for example,
MySuperLaravelBotorTestProject_bot). -
Save the access token. As soon as you pick a unique username, BotFather will congratulate you on the successful creation of your bot and will send you a message containing the token. This token is a long random sequence of letters and numbers. It is your secret key for interacting with the Telegram Bot API. Copy it and save it in a secure, inaccessible place. Treat it like the password to a very important account.
Congratulations, you now have a bot in Telegram!
Installing the Required Software Copy link
Update system packages:
sudo apt update && sudo apt upgrade -yTo get access to the latest PHP packages, we need to add the repository maintained by Ondrej Sury. Use this command to add the repository:
sudo add-apt-repository ppa:ondrej/php
sudo apt updateAdd the PPA for modern PHP versions, then install PHP 8.2 and the required extensions:
sudo apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-xml php8.2-sqlite3 php8.2-curl php8.2-zip php8.2-intl php8.2-bcmathInstall Nginx and Composer:
sudo apt install -y nginx composerCheck the version with:
php -vThe output should look something like “PHP 8.2.x”.
Setting Up Laravel and the Telegraph SDK Copy link
Now that you have the token, you can proceed to set up the Laravel project. We will use the package Telegraph: defstudio/telegraph. It provides convenient artisan commands for registering bots and webhooks.
-
Navigate to the websites folder:
cd /var/www-
Create a new Laravel project:
composer create-project laravel/laravel telegram-bot-
Go to the project folder:
cd telegram-bot-
Install the SDK via Composer:
composer require defstudio/telegraph:1.57.1-
For the package to work, Telegraph requires running its migrations to create the
telegraph_botsandtelegraph_chatstables, which will store bot data and associated chats. Run the migrations:
php artisan vendor:publish --tag="telegraph-migrations"
php artisan migrate-
Publish the configuration file. This command will create the file
config/telegraph.phpwhere all your bot’s settings will be stored:
php artisan vendor:publish --tag="telegraph-config"-
Open the
.envfile in the root of your project:
nano .env-
Add the token you received from BotFather:
TELEGRAPH_BOT_TOKEN=your_tokenChanging Bot Permissions and Privacy Copy link
Before our bot can send and, more importantly, receive messages in groups, we need to change some of its settings through @BotFather.
-
Give the bot permission to join Telegram groups. Send the command
/setjoingroupsin the chat with@BotFather. It will ask you which bot you want to change the setting for. Select your bot from the list and click “Enable”. -
Disable privacy mode. This is a key step. By default, a bot added to a group can only see messages that start with the
/symbol or are direct replies to its messages. To allow our bot to read all messages in the chat and respond to them, privacy mode must be disabled. Send the command/setprivacyto the same@BotFather, select the bot, and change its status to “Disabled”.
Registering the Bot in Telegraph Copy link
Now we need to “introduce” the Telegraph package to our bot and the chat where we will be sending messages.
To register a new bot in the application, Telegraph provides a convenient interactive wizard. Launch it:
php artisan telegraph:new-botThis command starts a wizard that will guide you through the bot creation process. During the setup, you will have the option to:
- create a new bot,
- add the bot token,
- enter the bot’s name (optional),
- add a new chat with its ID,
- configure a webhook for the bot.
This method allows you to quickly register a bot directly from the command line.
Registering a Chat in Telegraph Copy link
To send messages to a specific chat, Telegraph needs to know its unique identifier (chat_id).
- First, send any message to your bot.
- Then open the following link in your browser, substituting your token:
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesYou will see a JSON response, where you can easily find the chat object, and inside it, the id field. Copy this numeric value.
To make the chat known to Telegraph, it must be registered:
php artisan telegraph:new-chatEnter the Telegram chat ID. Optionally, you can specify a name for the chat.
The chat is successfully added to the database.
Testing Message Sending Copy link
Once your bot and at least one chat are set up, you can send messages using the Telegraph package.
Sending a Simple Message Copy link
Open the file routes/web.php:
nano routes/web.phpAdd the following code:
use DefStudio\Telegraph\Models\TelegraphChat;
Route::get('/send-telegram', function () {
$chat = TelegraphChat::where('chat_id', 'your_chat_id')->first();
$chat->message('Hello!')->send();
return response()->json(['Message sent successfully'], 200);
});
Replace your_chat_id with your actual chat ID.
Run the server for testing:
php artisan serveNow, when you go to http://127.0.0.1:8000/send-telegram, the bot should send a message to your Telegram chat.
You can test it with:
curl -I http://127.0.0.1:8000/send-telegramFormatting Message Text Copy link
In Telegraph, you can send messages in Markdown and HTML formats:
$chat->markdown("*Hello! This message is in...*\n\n_Markdown!_")->send();
$chat->html("<strong>And this message</strong>\n\nHTML!")->send();It is especially useful for high-load bots to send messages via Laravel’s queue system so as not to delay the execution of the main code. To organize interaction with Telegraph through the queue system, you can use the dispatch() method:
Telegraph::message('Hello')->dispatch('your_queue_name');Sending a message without sound:
$chat->message("shhh 🤫, a silent message")->silent()->send();The bot successfully sent messages.
For more detailed information about all Telegraph features, you can always check the documentation.
Setting Up an Nginx Web Server Copy link
The built-in server launched with php artisan serve works great for development, but for real-world use you need a proper web server.
On Ubuntu, Nginx traditionally uses /etc/nginx/sites-available and /etc/nginx/sites-enabled. This is where we’ll store the site configuration and manage enabling or disabling virtual hosts.
Open the Nginx configuration file for the project:
nano /etc/nginx/sites-available/telegram-botInsert the following:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/telegram-bot/public; # <-- if the project is elsewhere, replace the path
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
access_log off;
expires max;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# on Ubuntu the socket is usually in /run/php/
fastcgi_pass unix:/run/php/php8.2-fpm.sock; # <-- check your PHP version
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
access_log /var/log/nginx/telegram-bot.access.log;
error_log /var/log/nginx/telegram-bot.error.log;
}
Carefully check that the paths in root and fastcgi_pass are correct. The root argument must point to the public folder of the project, and fastcgi_pass must point to the PHP socket.
Check that the socket exists:
ls -l /run/php/You should see the file php8.2-fpm.sock.
Create a symlink:
sudo ln -s /etc/nginx/sites-available/telegram-bot /etc/nginx/sites-enabled/telegram-botBy default, /etc/nginx/sites-enabled contains default, which also specifies default_server. Remove its symlink to avoid duplicates:
sudo rm /etc/nginx/sites-enabled/defaultTest the configuration:
sudo nginx -tIf you see syntax is ok and test is successful, reload Nginx:
sudo systemctl reload nginxChange the owner and group for the following directories so that Nginx can handle them properly:
chown -R www-data:www-data /var/www/telegram-bot/storage/framework/views/
chown -R www-data:www-data /var/www/telegram-bot/storage/logs/
chown -R www-data:www-data /var/www/telegram-bot/public/
chown -R www-data:www-data /var/www/telegram-bot/database/Receiving Updates from Telegram in Laravel Copy link
You need to define how Telegram will deliver updates to your bot.
In production, webhooks are recommended. They let you receive updates (messages, commands, etc.) in real time through your app’s URL, without constantly polling the API. Telegram will send HTTP requests to the webhook you provide every time an event occurs with your bot, for example when a user sends a message.
Creating a Webhook Handler Copy link
Create a folder for handlers:
mkdir app/HandlersCreate a file CustomWebhookHandler.php inside Handlers:
nano app/Handlers/CustomWebhookHandler.phpInsert:
<?php
namespace App\Handlers;
use Illuminate\Support\Stringable;
use DefStudio\Telegraph\Handlers\WebhookHandler;
class CustomWebhookHandler extends WebhookHandler
{
protected function handleChatMessage(Stringable $text): void
{
// the received message is sent back to the chat
$this->chat->html("You wrote: $text")->send();
}
}
Next, we need to tell Telegraph to use this handler for incoming requests.
Open the config:
nano config/telegraph.phpFind the webhook parameter and change it to:
'webhook' => [
'handler' => App\Handlers\CustomWebhookHandler::class,
],Now, every plain message sent to the bot in Telegram will be processed by the handleChatMessage method of CustomWebhookHandler. The bot will reply with the same message in chat.
By default, webhooks can only process requests from known chats, i.e., those stored in the database as TelegraphChat models. All others are rejected.
To allow processing messages from unknown chats, update config/telegraph.php like this:
'security' => [
/*
* if enabled, allows callback queries from unregistered chats
*/
'allow_callback_queries_from_unknown_chats' => true,
/*
* if enabled, allows messages and commands from unregistered chats
*/
'allow_messages_from_unknown_chats' => true,
/*
* if enabled, store unknown chats as new TelegraphChat models
*/
'store_unknown_chats_in_db' => true,
],
Setting the Webhook Copy link
Because we’re working locally, we need to expose our Laravel server to the internet over HTTPS. Telegram does not support plain HTTP.
During development, you can use services like ngrok to create a temporary domain for your local server. By default, Laravel runs on port 8000. This is the simplest way to give Telegram a public HTTPS URL to your local Nginx.
In production, you should use a reliable VPS and your own domain.
Method 1: Using Your Own Domain (Recommended) Copy link
Switching to your own domain is an important step in running your bot in production. A domain gives stability and user trust. Unlike temporary tunnels, a domain doesn’t change as long as it is valid.
Buy a domain and create an A record pointing to your server’s IP.
Install a free SSL certificate with Certbot, which automatically updates the Nginx configuration:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com
sudo systemctl reload nginxUpdate .env with the new link:
nano .envChange:
APP_URL=https://yourdomain.comSet the webhook:
php artisan telegraph:set-webhookNow the bot is ready for production.
Method 2: Using Ngrok Copy link
Register an ngrok account and install ngrok on Linux. Run:
ngrok http 80The terminal will display a public HTTPS URL. Copy the tunnel link.
Update .env with this link:
nano .envChange:
APP_URL=https://your-tunnel-linkSet the webhook:
php artisan telegraph:set-webhookNow the bot is ready to receive updates through ngrok.
Testing the Bot Copy link
Send any message to your bot in Telegram. If everything is set up correctly, it should reply immediately.
If you encounter errors, note the step where they occur. Check the logs in the terminal running the tunnel and local server. Laravel errors can be found in storage/logs/laravel.log.
Supporting Commands Copy link
Let’s teach the bot to react to specific commands such as /start, and also show interactive buttons.
To handle commands, define their logic inside CustomWebhookHandler.
Open:
nano app/Handlers/CustomWebhookHandler.phpUpdate it as follows:
<?php
namespace App\Handlers;
use DefStudio\Telegraph\Keyboard\Button;
use DefStudio\Telegraph\Keyboard\Keyboard;
use Illuminate\Support\Stringable;
use DefStudio\Telegraph\Handlers\WebhookHandler;
class CustomWebhookHandler extends WebhookHandler
{
protected function handleChatMessage(Stringable $text): void
{
$this->chat->markdown("You wrote: $text")->send();
}
/**
* @var string[] List of allowed commands
*/
protected array $allowedCommands = [
'start',
'buttons',
'chatid', // command from the base class
];
/**
* Parent method for filtering commands
*/
protected function handleCommand(Stringable $text): void
{
[$command, $parameter] = $this->parseCommand($text);
// if the command is not in our whitelist,
// handle it as an unknown command
if (!in_array($command, $this->allowedCommands)) {
$this->handleUnknownCommand($text);
return;
}
// otherwise call the parent command handler
parent::handleCommand($text);
}
public function start(): void
{
$this->chat->markdown("*Hello!* I’m a bot running on Laravel!")->send();
}
public function buttons(): void
{
$this->chat->message('This is a message with buttons. Choose an action:')
->keyboard(Keyboard::make()->buttons([
Button::make("🗑️ Delete message")->action("delete"),
Button::make("📖 Share wisdom")->action("read"),
Button::make("👀 Open link")->url('https://hostman.com/'),
])->chunk(2))->send();
}
/**
* Handles pressing the button with action("delete")
*/
public function delete(): void
{
$this->reply('Message deleted');
$this->chat->deleteMessage($this->messageId)->send();
}
/**
* Handles pressing the button with action("read")
*/
public function read(): void
{
$wisdomText = "40% of performance problems are solved by code optimization. The remaining 60% are solved by moving to *Hostman*. Be wiser—start with the 60%.";
$this->reply('Wisdom revealed!', true);
$this->chat->edit($this->messageId)->markdown($wisdomText)->send();
}
}
We added two commands:
/startdisplays a greeting message./buttonsshows an interface with buttons that trigger different behaviors.
Pressing the buttons will trigger the corresponding methods (delete, read), while the button with a URL will simply open the link in the browser.
For example, testing the “Share wisdom” button should return the custom wisdom message.
Conclusion Copy link
In this guide, we demonstrated the full cycle of building a Telegram bot with the Laravel framework and the Telegraph package. You now have a working foundation that can:
- Send messages,
- Handle commands,
- Display interactive keyboards.
These are the building blocks for creating functional bots capable of solving real-world tasks, from automating routine operations to establishing new communication channels with users.