<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Telegram Bot Engineering Note</title>
    <description>The latest articles on DEV Community by Telegram Bot Engineering Note (@telegram_bot_notes).</description>
    <link>https://dev.to/telegram_bot_notes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082007%2Fcc7cd958-86a7-45ab-9ada-ef6b7b9c9925.png</url>
      <title>DEV Community: Telegram Bot Engineering Note</title>
      <link>https://dev.to/telegram_bot_notes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/telegram_bot_notes"/>
    <language>en</language>
    <item>
      <title>How to Receive and Download Photos and Files with a Telegram Bot in Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:12:55 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-receive-and-download-photos-and-files-with-a-telegram-bot-in-python-1k53</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-receive-and-download-photos-and-files-with-a-telegram-bot-in-python-1k53</guid>
      <description>&lt;p&gt;Telegram bots can do more than reply to text messages. They can receive photos, PDF files, spreadsheets, ZIP archives, and other documents sent by users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0ltl07fsmyy2dts02l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0ltl07fsmyy2dts02l.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." alt="Uploading image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a Python bot that:&lt;/p&gt;

&lt;p&gt;Detects photos and documents&lt;br&gt;
Reads their metadata&lt;br&gt;
Downloads them to a local folder&lt;br&gt;
Creates safe, unique filenames&lt;br&gt;
Rejects unsupported or oversized uploads&lt;br&gt;
Explains the difference between file_id and file_unique_id&lt;/p&gt;

&lt;p&gt;The example uses the asynchronous python-telegram-bot library.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;p&gt;Python 3.10 or newer&lt;br&gt;
A Telegram bot created through BotFather&lt;br&gt;
Your bot token&lt;br&gt;
The python-telegram-bot package&lt;/p&gt;

&lt;p&gt;If Telegram is not yet installed on your test device, use this &lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram download guide&lt;/a&gt; to choose the appropriate mobile or desktop version.&lt;/p&gt;

&lt;p&gt;Install the Python package:&lt;/p&gt;

&lt;p&gt;pip install python-telegram-bot&lt;/p&gt;

&lt;p&gt;Keep your bot token in an environment variable instead of placing it directly inside the source code.&lt;/p&gt;

&lt;p&gt;Linux or macOS:&lt;/p&gt;

&lt;p&gt;export TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN"&lt;/p&gt;

&lt;p&gt;Windows PowerShell:&lt;/p&gt;

&lt;p&gt;$env:TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN"&lt;br&gt;
How Telegram Represents Uploaded Files&lt;/p&gt;

&lt;p&gt;When a user sends media, Telegram does not immediately place the actual file contents inside the update.&lt;/p&gt;

&lt;p&gt;Instead, the update contains information such as:&lt;/p&gt;

&lt;p&gt;file_id: Used by your bot to retrieve or resend the file&lt;br&gt;
file_unique_id: A stable identifier for recognizing the same file&lt;br&gt;
file_size: Approximate file size in bytes&lt;br&gt;
file_name: Original name of a document, when available&lt;br&gt;
mime_type: Reported media type&lt;br&gt;
Photo dimensions and available sizes&lt;/p&gt;

&lt;p&gt;Your bot passes the file_id to Telegram’s getFile method. The returned File object can then be downloaded.&lt;/p&gt;

&lt;p&gt;A photo message normally contains several PhotoSize objects. Telegram generates these versions at different resolutions. The last item is generally the largest available version:&lt;/p&gt;

&lt;p&gt;photo = update.message.photo[-1]&lt;/p&gt;

&lt;p&gt;Documents are available through:&lt;/p&gt;

&lt;p&gt;document = update.message.document&lt;br&gt;
Receiving and Downloading a Photo&lt;/p&gt;

&lt;p&gt;Create a file named bot.py and start with the following handler:&lt;/p&gt;

&lt;p&gt;from pathlib import Path&lt;br&gt;
from uuid import uuid4&lt;/p&gt;

&lt;p&gt;from telegram import Update&lt;br&gt;
from telegram.ext import ContextTypes&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR = Path("downloads").resolve()&lt;br&gt;
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)&lt;/p&gt;

&lt;p&gt;async def receive_photo(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.photo:
    return

photo = message.photo[-1]

telegram_file = await context.bot.get_file(photo.file_id)

filename = f"{uuid4().hex}.jpg"
destination = DOWNLOAD_DIR / filename

await telegram_file.download_to_drive(
    custom_path=destination
)

await message.reply_text(
    f"Photo received successfully.\nSaved as: {filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The handler performs four operations:&lt;/p&gt;

&lt;p&gt;Selects the largest photo version&lt;br&gt;
Retrieves its File object&lt;br&gt;
Generates a unique local filename&lt;br&gt;
Downloads the photo into the downloads directory&lt;/p&gt;

&lt;p&gt;The download_to_drive() documentation describes the available download behavior in python-telegram-bot.&lt;/p&gt;

&lt;p&gt;Receiving Documents Safely&lt;/p&gt;

&lt;p&gt;Documents require more validation because users can upload many different file formats.&lt;/p&gt;

&lt;p&gt;For this example, we will allow:&lt;/p&gt;

&lt;p&gt;PDF&lt;br&gt;
TXT&lt;br&gt;
CSV&lt;br&gt;
JSON&lt;br&gt;
ZIP&lt;/p&gt;

&lt;p&gt;We will also apply an application-level limit of 10 MB.&lt;/p&gt;

&lt;p&gt;MAX_FILE_SIZE = 10 * 1024 * 1024&lt;/p&gt;

&lt;p&gt;ALLOWED_EXTENSIONS = {&lt;br&gt;
    ".pdf",&lt;br&gt;
    ".txt",&lt;br&gt;
    ".csv",&lt;br&gt;
    ".json",&lt;br&gt;
    ".zip",&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now create the document handler:&lt;/p&gt;

&lt;p&gt;async def receive_document(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.document:
    return

document = message.document

if document.file_size and document.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This file is too large. The current limit is 10 MB."
    )
    return

original_name = Path(
    document.file_name or "upload.bin"
).name

extension = Path(original_name).suffix.lower()

if extension not in ALLOWED_EXTENSIONS:
    await message.reply_text(
        "Unsupported file type. Please send a PDF, TXT, CSV, JSON, or ZIP file."
    )
    return

safe_filename = f"{uuid4().hex}{extension}"
destination = DOWNLOAD_DIR / safe_filename

telegram_file = await context.bot.get_file(
    document.file_id
)

await telegram_file.download_to_drive(
    custom_path=destination
)

await message.reply_text(
    "Document received successfully.\n"
    f"Original name: {original_name}\n"
    f"Stored as: {safe_filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Why Generate a New Filename?&lt;/p&gt;

&lt;p&gt;Saving an uploaded document using its original name can create several problems.&lt;/p&gt;

&lt;p&gt;Two users might upload files with the same name:&lt;/p&gt;

&lt;p&gt;report.pdf&lt;/p&gt;

&lt;p&gt;A malicious filename might also contain path components intended to escape the download directory.&lt;/p&gt;

&lt;p&gt;Using this line removes path information:&lt;/p&gt;

&lt;p&gt;original_name = Path(document.file_name).name&lt;/p&gt;

&lt;p&gt;Generating a UUID-based filename also prevents accidental overwrites:&lt;/p&gt;

&lt;p&gt;safe_filename = f"{uuid4().hex}{extension}"&lt;/p&gt;

&lt;p&gt;Do not assume that a filename extension or MIME type proves that a file is safe. Both values can be misleading. A production application should inspect file contents and scan untrusted uploads before further processing.&lt;/p&gt;

&lt;p&gt;Complete Working Bot&lt;/p&gt;

&lt;p&gt;Here is the complete example:&lt;/p&gt;

&lt;p&gt;import logging&lt;br&gt;
import os&lt;br&gt;
from pathlib import Path&lt;br&gt;
from uuid import uuid4&lt;/p&gt;

&lt;p&gt;from telegram import Update&lt;br&gt;
from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
    MessageHandler,&lt;br&gt;
    filters,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logging.basicConfig(&lt;br&gt;
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",&lt;br&gt;
    level=logging.INFO,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR = Path("downloads").resolve()&lt;br&gt;
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)&lt;/p&gt;

&lt;p&gt;MAX_FILE_SIZE = 10 * 1024 * 1024&lt;/p&gt;

&lt;p&gt;ALLOWED_EXTENSIONS = {&lt;br&gt;
    ".pdf",&lt;br&gt;
    ".txt",&lt;br&gt;
    ".csv",&lt;br&gt;
    ".json",&lt;br&gt;
    ".zip",&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    await update.effective_message.reply_text(&lt;br&gt;
        "Send me a photo or an allowed document.\n"&lt;br&gt;
        "Supported documents: PDF, TXT, CSV, JSON, and ZIP."&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;async def receive_photo(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.photo:
    return

photo = message.photo[-1]

if photo.file_size and photo.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This photo is too large. The current limit is 10 MB."
    )
    return

telegram_file = await context.bot.get_file(
    photo.file_id
)

filename = f"{uuid4().hex}.jpg"
destination = DOWNLOAD_DIR / filename

await telegram_file.download_to_drive(
    custom_path=destination
)

logger.info(
    "Saved photo %s as %s",
    photo.file_unique_id,
    filename,
)

await message.reply_text(
    f"Photo downloaded successfully.\nFile ID: {filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def receive_document(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.document:
    return

document = message.document

if document.file_size and document.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This document is too large. The current limit is 10 MB."
    )
    return

original_name = Path(
    document.file_name or "upload.bin"
).name

extension = Path(original_name).suffix.lower()

if extension not in ALLOWED_EXTENSIONS:
    await message.reply_text(
        "Unsupported file type.\n"
        "Allowed formats: PDF, TXT, CSV, JSON, and ZIP."
    )
    return

safe_filename = f"{uuid4().hex}{extension}"
destination = DOWNLOAD_DIR / safe_filename

telegram_file = await context.bot.get_file(
    document.file_id
)

await telegram_file.download_to_drive(
    custom_path=destination
)

logger.info(
    "Saved document %s as %s",
    document.file_unique_id,
    safe_filename,
)

await message.reply_text(
    "Document downloaded successfully.\n"
    f"Original name: {original_name}\n"
    f"File ID: {safe_filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def unsupported_file(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    await update.effective_message.reply_text(&lt;br&gt;
        "I cannot process this message type yet. "&lt;br&gt;
        "Please send a photo or a supported document."&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;async def error_handler(&lt;br&gt;
    update: object,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    logger.exception(&lt;br&gt;
        "An error occurred while processing an update",&lt;br&gt;
        exc_info=context.error,&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.getenv("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()

application.add_handler(
    CommandHandler("start", start)
)

application.add_handler(
    MessageHandler(filters.PHOTO, receive_photo)
)

application.add_handler(
    MessageHandler(
        filters.Document.ALL,
        receive_document,
    )
)

application.add_handler(
    MessageHandler(
        ~filters.COMMAND,
        unsupported_file,
    )
)

application.add_error_handler(error_handler)

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Run the bot:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Open your bot in Telegram, send /start, and then upload a photo or supported document.&lt;/p&gt;

&lt;p&gt;The downloaded files will appear in:&lt;/p&gt;

&lt;p&gt;downloads/&lt;br&gt;
file_id vs. file_unique_id&lt;/p&gt;

&lt;p&gt;These two values serve different purposes.&lt;/p&gt;

&lt;p&gt;file_id&lt;/p&gt;

&lt;p&gt;A file_id can be passed to Telegram API methods. Your bot can use it to retrieve or resend a previously uploaded file without uploading that file again.&lt;/p&gt;

&lt;p&gt;However, a file_id is associated with the bot that received it. It should not be treated as a universal public identifier.&lt;/p&gt;

&lt;p&gt;file_unique_id&lt;/p&gt;

&lt;p&gt;A file_unique_id helps identify the same file over time and across bots.&lt;/p&gt;

&lt;p&gt;It is useful for:&lt;/p&gt;

&lt;p&gt;Detecting duplicate uploads&lt;br&gt;
Creating database references&lt;br&gt;
Comparing previously received media&lt;/p&gt;

&lt;p&gt;It cannot be used directly to download or resend a file.&lt;/p&gt;

&lt;p&gt;A practical database record could contain:&lt;/p&gt;

&lt;p&gt;file_id&lt;br&gt;
file_unique_id&lt;br&gt;
original_name&lt;br&gt;
stored_name&lt;br&gt;
file_size&lt;br&gt;
uploaded_by&lt;br&gt;
uploaded_at&lt;br&gt;
Important Security Rules&lt;/p&gt;

&lt;p&gt;File-upload bots should be treated like public upload forms. Never trust uploaded content automatically.&lt;/p&gt;

&lt;p&gt;Apply these precautions:&lt;/p&gt;

&lt;p&gt;Set your own size limit. Reject files before downloading whenever file_size is available.&lt;br&gt;
Generate server-side filenames. Do not use raw user filenames as storage paths.&lt;br&gt;
Use an extension allowlist. Reject formats your application does not need.&lt;br&gt;
Inspect actual contents. File extensions and MIME types can be forged.&lt;br&gt;
Store uploads outside the public web root.&lt;br&gt;
Never execute uploaded files.&lt;br&gt;
Scan untrusted files before opening or processing them.&lt;br&gt;
Restrict access by user ID if the bot is intended for a private team.&lt;br&gt;
Avoid exposing local server paths in bot replies.&lt;br&gt;
Review Telegram’s current file limits before designing large-file workflows.&lt;/p&gt;

&lt;p&gt;The official MessageFilter documentation lists filters for photos, documents, MIME types, extensions, videos, audio, voice messages, and other Telegram content.&lt;/p&gt;

&lt;p&gt;Common Problems&lt;br&gt;
The Photo Handler Does Not Run&lt;/p&gt;

&lt;p&gt;Confirm that the photo handler is registered before a broad fallback handler:&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    MessageHandler(filters.PHOTO, receive_photo)&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Handler order matters because an earlier matching handler in the same group may process the update first.&lt;/p&gt;

&lt;p&gt;The Download Directory Is Missing&lt;/p&gt;

&lt;p&gt;Create it before starting the bot:&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR.mkdir(&lt;br&gt;
    parents=True,&lt;br&gt;
    exist_ok=True&lt;br&gt;
)&lt;br&gt;
The Bot Rejects a Valid File&lt;/p&gt;

&lt;p&gt;Print or log the received filename and MIME type:&lt;/p&gt;

&lt;p&gt;logger.info(&lt;br&gt;
    "Received %s with MIME type %s",&lt;br&gt;
    document.file_name,&lt;br&gt;
    document.mime_type,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Then check whether its extension is included in ALLOWED_EXTENSIONS.&lt;/p&gt;

&lt;p&gt;Downloading Large Files Fails&lt;/p&gt;

&lt;p&gt;Telegram’s Bot API, the selected library, your network, and your hosting environment may impose different constraints. Check the current Bot API documentation and configure an application-level limit suitable for your server.&lt;/p&gt;

&lt;p&gt;Where to Go Next&lt;/p&gt;

&lt;p&gt;After receiving files successfully, you can extend the bot to:&lt;/p&gt;

&lt;p&gt;Upload files to cloud object storage&lt;br&gt;
Save metadata in PostgreSQL or SQLite&lt;br&gt;
Generate image thumbnails&lt;br&gt;
Extract text from PDF documents&lt;br&gt;
Scan uploads for malware&lt;br&gt;
Detect duplicate files with file_unique_id&lt;br&gt;
Restrict uploads to approved Telegram users&lt;br&gt;
Send processing results back to the user&lt;/p&gt;

&lt;p&gt;The core workflow remains the same: validate the incoming message, obtain its file_id, request the File object, and download it to a controlled destination.&lt;/p&gt;

&lt;p&gt;繁體中文摘要&lt;/p&gt;

&lt;p&gt;這篇教學示範如何使用 Python 與 python-telegram-bot 接收 Telegram 使用者傳送的照片及文件。程式會透過 file_id 取得檔案、限制檔案大小、檢查副檔名，並以 UUID 產生不重複的安全檔名。&lt;/p&gt;

&lt;p&gt;正式部署時，不應直接信任使用者提供的檔名、MIME 類型或副檔名。建議將上傳內容存放在網站公開目錄以外，加入檔案內容檢查、惡意程式掃描及使用者權限限制，避免上傳功能成為安全漏洞。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Add a Command Menu to a Telegram Bot with Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:07:55 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-add-a-command-menu-to-a-telegram-bot-with-python-990</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-add-a-command-menu-to-a-telegram-bot-with-python-990</guid>
      <description>&lt;p&gt;A Telegram bot can recognize commands without displaying them in the client. However, users should not have to memorize every available command. Registering a command menu lets Telegram show a list when someone taps the menu button or types &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will create &lt;code&gt;/start&lt;/code&gt;, &lt;code&gt;/help&lt;/code&gt;, and &lt;code&gt;/status&lt;/code&gt; handlers, register them through the Bot API, and display different command menus in private chats and groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Telegram Bot Command Menu?
&lt;/h2&gt;

&lt;p&gt;A bot command begins with a slash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A registered command has two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt;: the value users send, without the leading slash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: a short explanation shown in the Telegram interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start - Start the bot
help - Show available commands
status - Check the service status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering this list improves discoverability, but it does not create the command logic. Your Python application must still contain a handler for every command.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;/status&lt;/code&gt; appears in the menu but no &lt;code&gt;CommandHandler&lt;/code&gt; exists for it, Telegram will send the command to the bot, but the program will not know how to respond.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10 or newer&lt;/li&gt;
&lt;li&gt;A Telegram bot created through BotFather&lt;/li&gt;
&lt;li&gt;A valid bot token&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;python-telegram-bot&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;Basic knowledge of Python functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the Telegram client is not installed on your test device, follow this &lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram download guide&lt;/a&gt; before starting.&lt;/p&gt;

&lt;p&gt;Create a project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;telegram-command-menu
&lt;span class="nb"&gt;cd &lt;/span&gt;telegram-command-menu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it on macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it in Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="n"&gt;\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; python-telegram-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the bot token in an environment variable.&lt;/p&gt;

&lt;p&gt;macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PASTE_YOUR_TOKEN_HERE'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PASTE_YOUR_TOKEN_HERE"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not place a real token directly in a public code sample or repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Command Handlers
&lt;/h2&gt;

&lt;p&gt;Create a file named &lt;code&gt;bot.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Use /help to view available commands.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Available commands:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start - Start the bot&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/help - Show this help message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status - Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢 The service is operating normally.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the bot and test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handlers work, but the Telegram command menu may still be empty. The next step is to register the commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Register Commands with BotFather
&lt;/h2&gt;

&lt;p&gt;For a small bot with a fixed command list, BotFather provides a simple setup method.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;@BotFather&lt;/code&gt; and send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mybots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Bot
→ Edit Bot
→ Edit Commands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Submit one command per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start - Start the bot
help - Show available commands
status - Check the service status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving, reopen the bot or type &lt;code&gt;/&lt;/code&gt; in the message field. Telegram should display the menu.&lt;/p&gt;

&lt;p&gt;This approach is convenient when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The command list rarely changes.&lt;/li&gt;
&lt;li&gt;The bot has only one language.&lt;/li&gt;
&lt;li&gt;Every chat uses the same commands.&lt;/li&gt;
&lt;li&gt;Command registration does not need to be part of deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a larger project, registering commands from Python keeps the visible menu synchronized with the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Register Commands from Python
&lt;/h2&gt;

&lt;p&gt;Import &lt;code&gt;BotCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a startup function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the application builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;post_init&lt;/code&gt; callback runs after the application has been initialized and can access &lt;code&gt;application.bot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The relevant section now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command menu is now registered automatically whenever the application starts.&lt;/p&gt;

&lt;p&gt;Telegram documents this operation in the official &lt;a href="https://core.telegram.org/bots/api#setmycommands" rel="noopener noreferrer"&gt;&lt;code&gt;setMyCommands&lt;/code&gt; reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Naming Rules
&lt;/h2&gt;

&lt;p&gt;A command name should be simple and predictable.&lt;/p&gt;

&lt;p&gt;Good examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start
help
status
account
language
server_1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start
show-status
my command
/settings!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bot commands use lowercase English letters, digits, and underscores. Do not include the leading slash when creating a &lt;code&gt;BotCommand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incorrect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Descriptions should explain the result of using the command. Avoid vague descriptions such as “Click here” or “Do something.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Different Menus for Private Chats and Groups
&lt;/h2&gt;

&lt;p&gt;A bot may need one menu in private chats and another in groups.&lt;/p&gt;

&lt;p&gt;Private commands might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/settings
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Group commands might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/help
/rules
/report
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the required scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;set_commands()&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;private_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open personal settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;group_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show group commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Display the group rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Report a message to moderators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check bot status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;private_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;group_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visible menu now depends on the chat type.&lt;/p&gt;

&lt;p&gt;However, the menu and the handlers remain separate. If &lt;code&gt;/rules&lt;/code&gt; is displayed for groups, a handler must also be registered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Telegram supports several command scopes, including default commands, all private chats, all group chats, administrators, specific chats, and specific chat members. The selection rules are documented in the official &lt;a href="https://core.telegram.org/bots/api#botcommandscope" rel="noopener noreferrer"&gt;BotCommandScope reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Group-Only Command Handlers
&lt;/h2&gt;

&lt;p&gt;A command visible in a group menu should normally reject private-chat usage.&lt;/p&gt;

&lt;p&gt;Import &lt;code&gt;ChatType&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;/rules&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The /rules command is available in groups only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Group rules:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1. Be respectful.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2. Stay on topic.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3. Do not post sensitive information.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hiding a command from the private-chat menu improves the interface, but it does not enforce authorization. Users may still type a command manually, so the handler must validate the chat type and permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Administrator-Only Commands
&lt;/h2&gt;

&lt;p&gt;Some commands should only be visible to group administrators.&lt;/p&gt;

&lt;p&gt;Import the administrator scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BotCommandScopeAllChatAdministrators&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register administrator commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;admin_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review reported messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cleanup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Remove recent unwanted messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;admin_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllChatAdministrators&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The menu scope only controls visibility. It does not prove that the caller is authorized.&lt;/p&gt;

&lt;p&gt;A sensitive handler must still check the member status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_group_admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_chat_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;administrator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the check inside an administrator command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;is_group_admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You do not have permission to use this command.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cleanup request accepted.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never rely on command visibility as an access-control mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Language-Specific Commands
&lt;/h2&gt;

&lt;p&gt;Telegram can register different descriptions for different language codes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;english_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;traditional_chinese_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;開始使用機器人&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;查看可用指令&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the default menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;english_commands&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register Traditional Chinese descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;traditional_chinese_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zh-hant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A default command list should still exist for users whose language does not match a localized menu.&lt;/p&gt;

&lt;p&gt;The language code affects menu descriptions, not the command handler itself. &lt;code&gt;/help&lt;/code&gt; can inspect &lt;code&gt;update.effective_user.language_code&lt;/code&gt; if the bot needs to respond in a different language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Working Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s - %(name)s - &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(levelname)s - %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Use /help to view available commands.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Available commands:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start - Start the bot&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/help - Show this message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status - Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢 The service is operating normally.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The /rules command is available in groups only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Group rules:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1. Be respectful.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2. Stay on topic.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3. Do not share sensitive information.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;private_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;group_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show group commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Display the group rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check bot status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;private_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;group_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;error_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An exception occurred while processing an update.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_error_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the bot in both a private chat and a group. The displayed commands should differ according to the selected scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Commands Work but the Menu Is Empty
&lt;/h3&gt;

&lt;p&gt;The handlers exist, but the command list has not been registered. Use BotFather or &lt;code&gt;set_my_commands()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After registering commands, close and reopen the chat if the client does not refresh immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Command Appears but Does Nothing
&lt;/h3&gt;

&lt;p&gt;The command was registered in the menu, but no matching handler exists.&lt;/p&gt;

&lt;p&gt;For every visible command, confirm that the application contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler_function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Old Commands Still Appear
&lt;/h3&gt;

&lt;p&gt;The application may be registering commands under a different scope. It is also possible that commands were previously set through BotFather or another deployment.&lt;/p&gt;

&lt;p&gt;Inspect which scopes are being used, update them consistently, and restart the client if necessary.&lt;/p&gt;

&lt;p&gt;Telegram also provides &lt;code&gt;deleteMyCommands&lt;/code&gt; for removing a command list from a selected scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Group Commands Appear in Private Chats
&lt;/h3&gt;

&lt;p&gt;A default command list may be overriding the intended design, or the private scope was not configured.&lt;/p&gt;

&lt;p&gt;Register separate &lt;code&gt;BotCommandScopeAllPrivateChats&lt;/code&gt; and &lt;code&gt;BotCommandScopeAllGroupChats&lt;/code&gt; lists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Administrator Commands Are Visible to the Wrong Users
&lt;/h3&gt;

&lt;p&gt;Review the command scope, but also remember that visibility is not authorization. Every sensitive handler must verify the caller's current member status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands Stop Updating After Code Changes
&lt;/h3&gt;

&lt;p&gt;The startup callback may not be running, or the application may be exiting before &lt;code&gt;set_my_commands()&lt;/code&gt; succeeds.&lt;/p&gt;

&lt;p&gt;Add logging inside the startup function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Registering bot commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Register commands here.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not log the bot token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Design Guidelines
&lt;/h2&gt;

&lt;p&gt;A useful command menu should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short enough to scan quickly&lt;/li&gt;
&lt;li&gt;Consistent with implemented handlers&lt;/li&gt;
&lt;li&gt;Different where private and group workflows differ&lt;/li&gt;
&lt;li&gt;Localized when the bot serves multiple languages&lt;/li&gt;
&lt;li&gt;Protected by server-side permission checks&lt;/li&gt;
&lt;li&gt;Updated as part of deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid registering every internal or debugging command. Public menus should contain only actions that ordinary users need.&lt;/p&gt;

&lt;p&gt;The command menu improves discoverability, while handlers provide behavior and authorization. Both parts must be maintained together.&lt;/p&gt;

&lt;h2&gt;
  
  
  繁體中文摘要
&lt;/h2&gt;

&lt;p&gt;Telegram Bot 的指令選單可以讓使用者在輸入 &lt;code&gt;/&lt;/code&gt; 時看到 &lt;code&gt;/start&lt;/code&gt;、&lt;code&gt;/help&lt;/code&gt; 與 &lt;code&gt;/status&lt;/code&gt; 等功能。指令可以透過 BotFather 手動設定，也可以使用 &lt;code&gt;set_my_commands()&lt;/code&gt; 在 Python 程式啟動時自動註冊。&lt;/p&gt;

&lt;p&gt;私人聊天、群組及管理員可以使用不同的 BotCommandScope。不過，選單只控制顯示內容，不代表使用者具有操作權限。管理功能仍需在處理函式中驗證聊天類型與管理員身分。&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This tutorial was drafted with AI assistance and reviewed against the Telegram Bot API and python-telegram-bot documentation. Test all permission-sensitive commands with a development bot before production use.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build a Telegram Bot Inline Keyboard with Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:01:40 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-build-a-telegram-bot-inline-keyboard-with-python-463o</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-build-a-telegram-bot-inline-keyboard-with-python-463o</guid>
      <description>&lt;p&gt;Text commands are enough for a basic Telegram bot, but they become inconvenient as the number of features grows. An inline keyboard gives users a clearer interface by placing buttons directly below a message.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a small menu using Python and python-telegram-bot. The bot will display buttons, process callback queries, update the original message, and safely handle unknown actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1uis3o0ceywmdlcp6qt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1uis3o0ceywmdlcp6qt.jpg" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What Is an Inline Keyboard?&lt;/p&gt;

&lt;p&gt;An inline keyboard is a collection of buttons attached to a Telegram message. Unlike a reply keyboard, it does not replace the user's typing area.&lt;/p&gt;

&lt;p&gt;Inline keyboards are useful for:&lt;/p&gt;

&lt;p&gt;Navigation menus&lt;br&gt;
Confirmation dialogs&lt;br&gt;
Pagination&lt;br&gt;
Language selection&lt;br&gt;
Account settings&lt;br&gt;
Order and service status checks&lt;br&gt;
Links to websites or documentation&lt;br&gt;
Group and channel administration&lt;/p&gt;

&lt;p&gt;Telegram represents an inline keyboard with two main objects:&lt;/p&gt;

&lt;p&gt;InlineKeyboardMarkup contains the complete button layout.&lt;br&gt;
InlineKeyboardButton represents an individual button.&lt;/p&gt;

&lt;p&gt;A keyboard is arranged as a list of rows. Each row contains one or more buttons.&lt;/p&gt;

&lt;p&gt;[ Documentation ] [ Service Status ]&lt;br&gt;
[        Open the Bot API Docs        ]&lt;/p&gt;

&lt;p&gt;A simplified API representation looks like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "inline_keyboard": [&lt;br&gt;
    [&lt;br&gt;
      {&lt;br&gt;
        "text": "Documentation",&lt;br&gt;
        "callback_data": "menu:docs"&lt;br&gt;
      },&lt;br&gt;
      {&lt;br&gt;
        "text": "Service Status",&lt;br&gt;
        "callback_data": "menu:status"&lt;br&gt;
      }&lt;br&gt;
    ],&lt;br&gt;
    [&lt;br&gt;
      {&lt;br&gt;
        "text": "Open the Bot API Docs",&lt;br&gt;
        "url": "&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;"&lt;br&gt;
      }&lt;br&gt;
    ]&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A button with callback_data sends a callback query to the bot. A button with a url opens the destination directly and does not trigger the same callback flow.&lt;/p&gt;

&lt;p&gt;See Telegram's official InlineKeyboardMarkup documentation for the complete object definition.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;p&gt;Python 3.10 or newer&lt;br&gt;
A Telegram account&lt;br&gt;
A bot created through &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;&lt;br&gt;
The bot token stored securely&lt;br&gt;
Basic familiarity with running Python from a terminal&lt;/p&gt;

&lt;p&gt;If you still need the client, use this Telegram download guide to install it on desktop or mobile.&lt;/p&gt;

&lt;p&gt;This tutorial uses the asynchronous python-telegram-bot interface.&lt;/p&gt;

&lt;p&gt;Create the Project&lt;/p&gt;

&lt;p&gt;Create a new directory:&lt;/p&gt;

&lt;p&gt;mkdir telegram-inline-keyboard&lt;br&gt;
cd telegram-inline-keyboard&lt;/p&gt;

&lt;p&gt;Create a virtual environment:&lt;/p&gt;

&lt;p&gt;python -m venv .venv&lt;/p&gt;

&lt;p&gt;Activate it on macOS or Linux:&lt;/p&gt;

&lt;p&gt;source .venv/bin/activate&lt;/p&gt;

&lt;p&gt;Activate it in Windows PowerShell:&lt;/p&gt;

&lt;p&gt;.venv\Scripts\Activate.ps1&lt;/p&gt;

&lt;p&gt;Install or upgrade the library:&lt;/p&gt;

&lt;p&gt;python -m pip install --upgrade python-telegram-bot&lt;/p&gt;

&lt;p&gt;The current stable python-telegram-bot library uses an asynchronous API. Handler functions therefore use async def, and Telegram operations must be awaited.&lt;/p&gt;

&lt;p&gt;Store the Bot Token Safely&lt;/p&gt;

&lt;p&gt;Do not place a real token directly in source code.&lt;/p&gt;

&lt;p&gt;On macOS or Linux:&lt;/p&gt;

&lt;p&gt;export TELEGRAM_BOT_TOKEN='PASTE_YOUR_TOKEN_HERE'&lt;/p&gt;

&lt;p&gt;In Windows PowerShell:&lt;/p&gt;

&lt;p&gt;$env:TELEGRAM_BOT_TOKEN="PASTE_YOUR_TOKEN_HERE"&lt;/p&gt;

&lt;p&gt;The environment variable exists only in the current terminal session. For production, use the secret-management feature provided by your hosting platform.&lt;/p&gt;

&lt;p&gt;Never commit a .env file containing a token to a public repository. If a token is exposed, revoke it through BotFather and generate a new one.&lt;/p&gt;

&lt;p&gt;Build the First Menu&lt;/p&gt;

&lt;p&gt;Create a file named bot.py:&lt;/p&gt;

&lt;p&gt;import os&lt;/p&gt;

&lt;p&gt;from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update&lt;br&gt;
from telegram.ext import Application, CommandHandler, ContextTypes&lt;/p&gt;

&lt;p&gt;def build_home_keyboard() -&amp;gt; InlineKeyboardMarkup:&lt;br&gt;
    keyboard = [&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "📚 Documentation",&lt;br&gt;
                callback_data="menu:docs",&lt;br&gt;
            ),&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🟢 Service Status",&lt;br&gt;
                callback_data="menu:status",&lt;br&gt;
            ),&lt;br&gt;
        ],&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🌐 Open the Bot API Docs",&lt;br&gt;
                url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
            )&lt;br&gt;
        ],&lt;br&gt;
    ]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return InlineKeyboardMarkup(keyboard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    if update.message is None:&lt;br&gt;
        return&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await update.message.reply_text(
    "Choose an option:",
    reply_markup=build_home_keyboard(),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.environ.get("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()
application.add_handler(CommandHandler("start", start))

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Run the bot:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Open the bot in Telegram and send:&lt;/p&gt;

&lt;p&gt;/start&lt;/p&gt;

&lt;p&gt;You should see two callback buttons and one URL button. The URL button already works, but the callback buttons do not yet have a handler.&lt;/p&gt;

&lt;p&gt;Understanding callback_data&lt;/p&gt;

&lt;p&gt;The callback_data value is a short string associated with a button:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Service Status",&lt;br&gt;
    callback_data="menu:status",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;When a user presses this button, Telegram sends a CallbackQuery update containing:&lt;/p&gt;

&lt;p&gt;menu:status&lt;/p&gt;

&lt;p&gt;A useful convention is to organize callback values with prefixes:&lt;/p&gt;

&lt;p&gt;menu:home&lt;br&gt;
menu:docs&lt;br&gt;
menu:status&lt;br&gt;
settings:language:en&lt;br&gt;
page:articles:2&lt;br&gt;
order:confirm:5821&lt;/p&gt;

&lt;p&gt;Telegram limits callback_data to a small payload, so it should contain an action identifier rather than a complete data object.&lt;/p&gt;

&lt;p&gt;Do not include the following information in callback data:&lt;/p&gt;

&lt;p&gt;Bot tokens&lt;br&gt;
Passwords&lt;br&gt;
API keys&lt;br&gt;
Payment information&lt;br&gt;
Email addresses&lt;br&gt;
Private user data&lt;br&gt;
Complete JSON documents&lt;br&gt;
Authorization credentials&lt;/p&gt;

&lt;p&gt;For database-backed actions, include a short record identifier and load the actual data on the server after validating the request.&lt;/p&gt;

&lt;p&gt;Add a Callback Query Handler&lt;/p&gt;

&lt;p&gt;Update the imports:&lt;/p&gt;

&lt;p&gt;from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CallbackQueryHandler,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Add the following function below start():&lt;/p&gt;

&lt;p&gt;async def handle_button(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    query = update.callback_query&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if query is None:
    return

action = query.data

if action == "menu:docs":
    await query.answer()

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                )
            ]
        ]
    )

    await query.edit_message_text(
        text=(
            "📚 Documentation\n\n"
            "Use the official Bot API reference to review "
            "available methods, objects, and update types."
        ),
        reply_markup=keyboard,
    )

elif action == "menu:status":
    await query.answer("Status refreshed")

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "🔄 Refresh",
                    callback_data="menu:status",
                ),
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                ),
            ]
        ]
    )

    await query.edit_message_text(
        text="🟢 The service is operating normally.",
        reply_markup=keyboard,
    )

elif action == "menu:home":
    await query.answer()

    await query.edit_message_text(
        text="Choose an option:",
        reply_markup=build_home_keyboard(),
    )

else:
    await query.answer(
        text="Unknown action",
        show_alert=True,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Register the callback handler in main():&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_button,&lt;br&gt;
        pattern=r"^menu:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The handler section should now look like this:&lt;/p&gt;

&lt;p&gt;application.add_handler(CommandHandler("start", start))&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_button,&lt;br&gt;
        pattern=r"^menu:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The regular expression limits this handler to callback values beginning with menu:. Future features can use separate prefixes and handlers.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_settings,&lt;br&gt;
        pattern=r"^settings:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This keeps a larger bot easier to maintain.&lt;/p&gt;

&lt;p&gt;Why query.answer() Is Required&lt;/p&gt;

&lt;p&gt;After a user presses an inline button, the Telegram client displays a loading indicator. The bot should answer the callback query even if no notification needs to be shown.&lt;/p&gt;

&lt;p&gt;await query.answer()&lt;/p&gt;

&lt;p&gt;Without this call, the loading indicator may continue spinning and make the interface appear broken.&lt;/p&gt;

&lt;p&gt;To show a short notification:&lt;/p&gt;

&lt;p&gt;await query.answer("Settings saved")&lt;/p&gt;

&lt;p&gt;To display an alert dialog:&lt;/p&gt;

&lt;p&gt;await query.answer(&lt;br&gt;
    text="You do not have permission to perform this action.",&lt;br&gt;
    show_alert=True,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Only answer each callback once. Decide whether the response should be silent, a short notification, or an alert.&lt;/p&gt;

&lt;p&gt;Telegram explains this behavior in the official CallbackQuery documentation.&lt;/p&gt;

&lt;p&gt;Complete Working Example&lt;/p&gt;

&lt;p&gt;The following version includes logging, menu navigation, filtered callback handling, and a basic error handler:&lt;/p&gt;

&lt;p&gt;import logging&lt;br&gt;
import os&lt;/p&gt;

&lt;p&gt;from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update&lt;br&gt;
from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CallbackQueryHandler,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logging.basicConfig(&lt;br&gt;
    format=(&lt;br&gt;
        "%(asctime)s - %(name)s - "&lt;br&gt;
        "%(levelname)s - %(message)s"&lt;br&gt;
    ),&lt;br&gt;
    level=logging.INFO,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;def build_home_keyboard() -&amp;gt; InlineKeyboardMarkup:&lt;br&gt;
    keyboard = [&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "📚 Documentation",&lt;br&gt;
                callback_data="menu:docs",&lt;br&gt;
            ),&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🟢 Service Status",&lt;br&gt;
                callback_data="menu:status",&lt;br&gt;
            ),&lt;br&gt;
        ],&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🌐 Open the Bot API Docs",&lt;br&gt;
                url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
            )&lt;br&gt;
        ],&lt;br&gt;
    ]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return InlineKeyboardMarkup(keyboard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    if update.message is None:&lt;br&gt;
        return&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await update.message.reply_text(
    "Choose an option:",
    reply_markup=build_home_keyboard(),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def handle_button(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    query = update.callback_query&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if query is None:
    return

action = query.data

if action == "menu:docs":
    await query.answer()

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                )
            ]
        ]
    )

    await query.edit_message_text(
        text=(
            "📚 Documentation\n\n"
            "Review the Telegram Bot API reference "
            "for available methods and objects."
        ),
        reply_markup=keyboard,
    )

elif action == "menu:status":
    await query.answer("Status refreshed")

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "🔄 Refresh",
                    callback_data="menu:status",
                ),
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                ),
            ]
        ]
    )

    await query.edit_message_text(
        text="🟢 The service is operating normally.",
        reply_markup=keyboard,
    )

elif action == "menu:home":
    await query.answer()

    await query.edit_message_text(
        text="Choose an option:",
        reply_markup=build_home_keyboard(),
    )

else:
    await query.answer(
        text="Unknown action",
        show_alert=True,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def error_handler(&lt;br&gt;
    update: object,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    logger.error(&lt;br&gt;
        "An exception occurred while processing an update.",&lt;br&gt;
        exc_info=context.error,&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.environ.get("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()

application.add_handler(CommandHandler("start", start))
application.add_handler(
    CallbackQueryHandler(
        handle_button,
        pattern=r"^menu:",
    )
)
application.add_error_handler(error_handler)

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Restart the program after saving:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Send /start again and test every button.&lt;/p&gt;

&lt;p&gt;The python-telegram-bot project also provides an official inline keyboard example that is useful when comparing implementation patterns.&lt;/p&gt;

&lt;p&gt;URL Buttons vs. Callback Buttons&lt;/p&gt;

&lt;p&gt;A URL button opens a destination:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Open Documentation",&lt;br&gt;
    url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;A callback button sends data to the bot:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Check Status",&lt;br&gt;
    callback_data="menu:status",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Use a URL button when the only goal is navigation. Use a callback button when the server needs to validate an action, query a database, modify a message, or change the menu.&lt;/p&gt;

&lt;p&gt;Do not use callback data as a substitute for server-side authorization. A user interface is not a security boundary.&lt;/p&gt;

&lt;p&gt;Editing a Message vs. Sending a New One&lt;/p&gt;

&lt;p&gt;A menu usually works best when it edits the existing message:&lt;/p&gt;

&lt;p&gt;await query.edit_message_text(&lt;br&gt;
    text="Updated menu content"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This prevents a long trail of nearly identical menu messages.&lt;/p&gt;

&lt;p&gt;For permanent results, notifications, receipts, or audit information, send a new message instead:&lt;/p&gt;

&lt;p&gt;await query.message.reply_text(&lt;br&gt;
    "Your request has been completed."&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Choose based on whether the user needs to retain the previous result.&lt;/p&gt;

&lt;p&gt;Common Problems&lt;br&gt;
The Button Keeps Loading&lt;/p&gt;

&lt;p&gt;Confirm that every callback path calls one of the following:&lt;/p&gt;

&lt;p&gt;await query.answer()&lt;br&gt;
await query.answer("Completed")&lt;br&gt;
await query.answer(&lt;br&gt;
    "Permission denied",&lt;br&gt;
    show_alert=True,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Unknown and error paths also need a callback response.&lt;/p&gt;

&lt;p&gt;Nothing Happens After a Click&lt;/p&gt;

&lt;p&gt;Check that:&lt;/p&gt;

&lt;p&gt;CallbackQueryHandler is registered.&lt;br&gt;
The callback value matches the handler pattern.&lt;br&gt;
The bot process is still running.&lt;br&gt;
The token belongs to the correct bot.&lt;br&gt;
Another process is not consuming updates.&lt;br&gt;
A webhook is not conflicting with polling.&lt;br&gt;
The terminal does not contain an exception.&lt;br&gt;
Message Is Not Modified&lt;/p&gt;

&lt;p&gt;Telegram returns this error when the new text and keyboard are identical to the current message.&lt;/p&gt;

&lt;p&gt;A refresh button should either produce changed content or handle this case without treating it as a fatal error.&lt;/p&gt;

&lt;p&gt;A User Clicks an Old Button&lt;/p&gt;

&lt;p&gt;Inline buttons can remain visible in chat history. A user may press one after the referenced database record has expired or changed.&lt;/p&gt;

&lt;p&gt;Always verify:&lt;/p&gt;

&lt;p&gt;The user is still authorized.&lt;br&gt;
The record still exists.&lt;br&gt;
The action is still valid.&lt;br&gt;
The operation has not already been completed.&lt;br&gt;
The callback belongs to the expected workflow.&lt;/p&gt;

&lt;p&gt;Return a clear alert if the action is no longer available.&lt;/p&gt;

&lt;p&gt;Duplicate Clicks Create Duplicate Operations&lt;/p&gt;

&lt;p&gt;For sensitive actions such as payments, deletions, or order creation, add server-side protection:&lt;/p&gt;

&lt;p&gt;Idempotency keys&lt;br&gt;
Transaction checks&lt;br&gt;
Permission validation&lt;br&gt;
Current-state validation&lt;br&gt;
Rate limiting&lt;br&gt;
Audit logs&lt;br&gt;
Confirmation steps&lt;/p&gt;

&lt;p&gt;Disabling or replacing a button improves the interface, but it does not replace backend safeguards.&lt;/p&gt;

&lt;p&gt;A Better Project Structure&lt;/p&gt;

&lt;p&gt;A single file is acceptable for this tutorial. A larger bot should separate menu creation, callback routing, and business logic.&lt;/p&gt;

&lt;p&gt;telegram-inline-keyboard/&lt;br&gt;
├── bot.py&lt;br&gt;
├── handlers/&lt;br&gt;
│   ├── commands.py&lt;br&gt;
│   └── callbacks.py&lt;br&gt;
├── keyboards/&lt;br&gt;
│   └── main_menu.py&lt;br&gt;
├── services/&lt;br&gt;
│   └── status_service.py&lt;br&gt;
└── requirements.txt&lt;/p&gt;

&lt;p&gt;This structure makes it easier to test functions and add new menus without turning bot.py into a large collection of unrelated conditions.&lt;/p&gt;

&lt;p&gt;Where to Go Next&lt;/p&gt;

&lt;p&gt;Once the basic menu works, useful extensions include:&lt;/p&gt;

&lt;p&gt;Paginated article lists&lt;br&gt;
Language settings&lt;br&gt;
Confirmation dialogs&lt;br&gt;
Database-backed menus&lt;br&gt;
Role-based administrator buttons&lt;br&gt;
Image and document actions&lt;br&gt;
Webhook deployment&lt;br&gt;
Persistent user preferences&lt;br&gt;
Rate limiting and audit logs&lt;/p&gt;

&lt;p&gt;The core workflow remains the same:&lt;/p&gt;

&lt;p&gt;Create an InlineKeyboardButton&lt;br&gt;
→ assign callback_data&lt;br&gt;
→ register CallbackQueryHandler&lt;br&gt;
→ validate query.data&lt;br&gt;
→ call query.answer()&lt;br&gt;
→ update or send a message&lt;/p&gt;

&lt;p&gt;Once this pattern is clear, a command-only Telegram bot can be turned into a practical interactive application.&lt;/p&gt;

&lt;p&gt;繁體中文摘要&lt;/p&gt;

&lt;p&gt;Inline Keyboard 可以在 Telegram Bot 訊息下方加入互動按鈕。使用者點擊含有 callback_data 的按鈕後，程式會收到 Callback Query。處理函式必須驗證資料並執行 query.answer()，否則按鈕可能持續顯示載入狀態。&lt;/p&gt;

&lt;p&gt;一般選單適合使用 edit_message_text() 更新原訊息；需要保留的操作結果則可以另外傳送新訊息。涉及付款、刪除或管理權限時，仍需在伺服器端進行身分與狀態驗證，不能只依賴按鈕介面。&lt;/p&gt;

&lt;p&gt;Disclosure: This tutorial was drafted with AI assistance and reviewed against the Telegram Bot API and python-telegram-bot documentation. Test the code in a development bot before using it in production.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
