Botxona

The 6 standard rules — what your bot code must look like

Botxona automatically checks every uploaded bot against 6 standards (where to see the result). These rules are not a formality — they are what lets your bot sleep and wake up, get backed up and have working analytics. For each rule below: what, why, how it is checked and ready-made fix code.

1. Token only from BOT_TOKEN

What: the bot token must not appear in the code in plain text — read it only from the BOT_TOKEN environment variable.

Why: if the token is hard-coded, changing it means re-uploading the code; and if you ever share the code with someone, the token leaks with it.

How it is checked: the system searches the code for strings that look like a real token in the form number:letters-and-symbols. If one is found — 🔴 (fail).

Fix:

import os
BOT_TOKEN = os.getenv("BOT_TOKEN")
token := os.Getenv("BOT_TOKEN")
const token = process.env.BOT_TOKEN

Common mistake: pasting the token into the code for convenience while testing and then forgetting it there. Search the code (Ctrl+F) before uploading.

2. Telegram API connection — automatic

What: there is nothing you need to do for this rule. However the bot connects to Telegram (even directly to api.telegram.org with the library's default settings), requests are automatically routed through the platform proxy.

Why: Botxona analytics (DAU, errors, response time), sleep/wake and the test run all rely on the requests that pass through this proxy — with no code changes. The proxy also provides an isolation and security layer.

How it is checked: the check is always 🟢 — "Automatic: however the bot connects to Telegram, requests go through the platform".

Code: the library's default settings are enough, no custom API address is needed:

from aiogram import Bot
bot = Bot(token=BOT_TOKEN)
import { Bot } from "grammy"
const bot = new Bot(process.env.BOT_TOKEN)
b, err := bot.New(os.Getenv("BOT_TOKEN"))

Note: the platform also provides the TELEGRAM_API_URL variable — if your code already uses it (for example via apiRoot, base_url, WithServerURL), it keeps working. But it is optional; there is no need to set it up in new code.

3. Libraries with exact versions

What: every library in requirements.txt (Python), package.json (Node.js) or go.mod (+ go.sum) (Go) must be listed with an exact version (e.g. aiogram==3.13.1, not just aiogram).

Why: an unversioned library will eventually update and may break your code — the bot works today and stops tomorrow because of incompatible versions.

Fix:

aiogram==3.13.1
python-dotenv==1.0.1
{
  "dependencies": {
    "grammy": "^1.30.0"
  }
}
module mening-botim

go 1.27

require github.com/go-telegram/bot v1.27.0

In Go, include go.sum in the archive too (if it is missing you get a warning; the build runs go mod tidy itself).

Common mistake: running pip freeze > requirements.txt in the global Python environment instead of the project's virtual environment, pulling in dozens of unneeded libraries.

4. A clear entry point

What: the system must know which file to run. Standard candidates for Python: main.py, bot.py, app.py. For Node.js: the "start" script or the main field in package.json. For Go: the main package in the project root (main.go) or exactly one cmd/<name>/ folder.

Why: without a clear entry point the system has to guess which file to run — with several candidates, it may pick the wrong one.

Fix — add a botxona.yaml to be explicit:

runtime: python
entry: main.py
runtime: node
start: node src/index.js
runtime: go
entry: ./cmd/bot

Full schema: botxona.yaml.

5. Persistent data only in DATA_DIR

What: every file your bot saves (SQLite database, Excel exports, images, etc.) must live in the folder pointed to by the DATA_DIR environment variable.

Why: when the bot container restarts (redeploy, restart), the file system outside DATA_DIR is wiped — only DATA_DIR survives and is backed up daily (Data and backups).

Fix:

import os
DATA_DIR = os.getenv("DATA_DIR", "./data")
os.makedirs(DATA_DIR, exist_ok=True)
db_path = os.path.join(DATA_DIR, "bot.sqlite3")
const DATA_DIR = process.env.DATA_DIR || "./data"
const dbPath = require("path").join(DATA_DIR, "bot.sqlite3")
dataDir := os.Getenv("DATA_DIR")
if dataDir == "" {
    dataDir = "./data"
}
os.MkdirAll(dataDir, 0o755)
dbPath := filepath.Join(dataDir, "bot.sqlite3")

If you need SQLite in Go, use the cgo-free modernc.org/sqlite: the bot is built as a static binary with CGO_ENABLED=0, so github.com/mattn/go-sqlite3 does not work.

Common mistake: writing to the current folder (./) or the project itself (. instead of ./data) — it works at build time, but in the production container the file system only allows writes to DATA_DIR.

6. Settings in .env.example

What: every environment variable your bot expects (ADMIN_IDS, CHANNEL_ID, etc.) must be listed in a .env.example file, with a comment but without values.

Why: the Botxona dashboard uses this list to show you a form for entering settings — without .env.example you have to add the variables by hand.

Fix:

# Admin Telegram IDs, comma-separated
ADMIN_IDS=

# Channel ID (for tracking channel growth)
CHANNEL_ID=

There is no need to add the variables the platform provides itself — BOT_TOKEN, TELEGRAM_API_URL, DATA_DIR, TZ, PORT — to .env.example; they are set automatically (Environment variables).

Next step

Upload your code and see the result: Reading the check results. If you are unsure about the format, the "Standardization" prompt in the prompt library adapts existing code automatically.