A secure, efficient command-line tool for managing file uploads to Telegram channels with multi-account support.
- Bulk file uploads - Upload single files or entire directories to channels
- Telegram storage - Store, download, and list files using a Telegram channel as cloud storage with automatic file splitting for large files
- Multi-account support - Manage multiple Telegram accounts
- Automatic retry - Handles rate limits and flood waits with exponential backoff
- Progress tracking - Real-time upload/download progress bars
- Smart image processing - Automatic resizing for Telegram dimension limits
- Video optimization - MP4 files with proper metadata extraction
- Resume support - Interrupted storage uploads can be resumed
- Secure configuration - Environment-based credential management with session file protection
- Comprehensive logging - Detailed logs with rotation
- Input validation - Secure path handling, sanitization, and validation
- Process locking - Prevents multiple instances from running with the same account
- Node.js 22 or higher
- FFmpeg (for video processing)
- Sharp dependencies (automatic with
npm install) - TypeScript (installed as dev dependency)
- Clone the repository:
git clone https://github.com/yourusername/tgmanager.git
cd tgmanager- Install dependencies:
npm install- Create your environment file:
cp .env.example .env- Edit
.envwith your Telegram credentials:
# Account: your_account_name
YOUR_ACCOUNT_API_ID=123456
YOUR_ACCOUNT_API_HASH=your_api_hash_here
YOUR_ACCOUNT_PHONE=+1234567890
YOUR_ACCOUNT_PASSWORD=your_password_here- Build the TypeScript code:
npm run build:ts- Visit https://my.telegram.org
- Log in with your phone number
- Go to "API Development Tools"
- Create a new application
- Copy your
api_idandapi_hash
| Variable | Description | Default |
|---|---|---|
LOG_LEVEL |
Logging level (error, warn, info, debug) | info |
MAX_CONCURRENT_UPLOADS |
Number of concurrent uploads | 1 |
UPLOAD_TIMEOUT |
Upload timeout in milliseconds | 600000 |
SESSION_DIR |
Directory for session storage | sessions (in project) |
UPLOAD_DIR |
Default upload directory | uploads (in project) |
TGMANAGER_CONFIG |
Path to a custom .env file |
Auto-detected |
TGMANAGER_HOME |
Base directory for sessions and config | Project directory / ~/.tgmanager |
TGMANAGER_DEFAULT_ACCOUNT |
Default account name (skip -a flag) |
None |
Set a default account to skip the -a flag:
export TGMANAGER_DEFAULT_ACCOUNT=myaccountUse short command aliases with positional arguments:
# Upload to storage (store = upload-storage)
./uploader-linux store /path/to/file.zip /backups/file.zip
# Download from storage (get = download-storage)
./uploader-linux get /backups/file.zip --output-path ./restored.zip
# List storage files (ls = list-storage)
./uploader-linux ls /backups/
# Aliases work with flags too
./uploader-linux store /path/to/file.zip /backups/file.zip --delete-source --waitAll original flag-based syntax remains fully supported.
For development (with TypeScript via tsx):
npm run dev -- -a your_account -c upload -i @channel_username -f /path/to/file.mp4For production (compiled JavaScript):
npm start -- -a your_account -c upload -i @channel_username -f /path/to/file.mp4Or using standalone binaries (see Building Binaries):
./uploader-linux -a your_account -c upload -i @channel_username -f /path/to/file.mp4Upload a single file:
node dist/index.js -a myaccount -c upload -i @mychannel -f /path/to/file.mp4Upload a directory (all files uploaded concurrently):
node dist/index.js -a myaccount -c upload -i @mychannel -f /path/to/directory/Upload with source deletion after success:
node dist/index.js -a myaccount -c upload -i @mychannel -f video.mp4 --delete-sourceUpload to a specific chat ID:
node dist/index.js -a myaccount -c upload -i -1001234567890 -f document.pdfnode dist/index.js -a myaccount -c create -n "My New Channel"Uploads a file to a dedicated storage channel. Large files are automatically split into chunks. Files are identified by a virtual path (like a filesystem).
node dist/index.js -a myaccount -c upload-storage -f /path/to/largefile.zip --virtual-path /backups/largefile.zipWith a specific storage channel:
node dist/index.js -a myaccount -c upload-storage -f data.db --virtual-path /databases/data.db --storage-channel -1001234567890Downloads a previously stored file by its virtual path. Chunks are downloaded with retry logic and merged automatically.
node dist/index.js -a myaccount -c download-storage --virtual-path /backups/largefile.zipDownload to a specific location:
node dist/index.js -a myaccount -c download-storage --virtual-path /backups/largefile.zip --output-path /tmp/restored.zipForce overwrite existing file:
node dist/index.js -a myaccount -c download-storage --virtual-path /backups/largefile.zip --forceList all stored files:
node dist/index.js -a myaccount -c list-storageFilter by virtual path prefix:
node dist/index.js -a myaccount -c list-storage --virtual-path /backups/| Option | Description | Required |
|---|---|---|
-a, --account <name> |
Account name from config | Yes |
-c, --command <cmd> |
Command to execute | Yes |
-i, --chat-id <id> |
Chat ID or @username | For upload |
-f, --file-path <path> |
File or directory path | For upload, upload-storage |
-n, --name <name> |
Channel name | For create |
--delete-source |
Delete files after successful upload | No |
--virtual-path <path> |
Virtual path for storage operations | For upload-storage, download-storage |
--output-path <path> |
Output path for downloads | For download-storage |
--storage-channel <id> |
Storage channel ID (auto-creates if omitted) | No |
--force |
Force overwrite existing files | No |
| Command | Description |
|---|---|
upload |
Upload files or directories to a Telegram channel |
create |
Create a new Telegram broadcast channel |
upload-storage / store |
Upload a file to Telegram storage with automatic splitting |
download-storage / get |
Download a file from Telegram storage by virtual path |
list-storage / ls |
List all files stored in Telegram storage |
- Videos: MP4 files with automatic metadata extraction (width, height, duration)
- Images: JPG, JPEG, PNG, GIF with automatic resizing when exceeding Telegram limits
- Documents: All other file types uploaded as-is
| Account Type | Direct Upload Limit | Storage Upload |
|---|---|---|
| Regular | 2 GB per file | Unlimited (auto-split) |
| Premium | 4 GB per file | Unlimited (auto-split) |
- Image dimensions: Max 5000x5000 pixels (auto-resized if exceeded)
- Storage uploads: Files exceeding the account limit are automatically split into chunks and reassembled on download
- Never commit credentials - Use environment variables
- Secure your .env file - Set proper file permissions
- Use strong passwords - Enable 2FA on Telegram
- Rotate API keys - Regenerate periodically
- Monitor logs - Check for unauthorized access
Logs are stored in the logs/ directory:
app.log- All application logserror.log- Error logs only
Log rotation is automatic after 10MB.
# Development with hot reload
npm run dev
# Build TypeScript to JavaScript
npm run build:ts
# Type checking without building
npm run type-check
# Run ESLint
npm run lint
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Full build (TypeScript + native binaries + Docker)
npm run build
# Start production version
npm startBuild TypeScript and create binaries for all platforms:
npm run buildBuild for specific platform:
# First build TypeScript
npm run build:ts
# Then package for your platform
# Windows
pkg dist/index.js --target node22-win-x64 --output dist/tgmanager.exe
# macOS
pkg dist/index.js --target node22-macos-x64 --output dist/tgmanager
# Linux
pkg dist/index.js --target node22-linux-x64 --output dist/tgmanagerThe compiled executables support multiple configuration methods:
- Environment file (
.env) in multiple locations - System environment variables
- Custom config path via
TGMANAGER_CONFIG
See STANDALONE.md for detailed instructions on using standalone executables with credentials.
Build the Docker image:
docker build -t tgmanager .Run with Docker:
docker run -v ~/.tgmanager:/root/.tgmanager tgmanager -a account -c upload -i @channel -f /path/to/fileThe storage feature uses a dedicated Telegram channel as a file storage backend. Files are addressed by virtual paths (e.g., /backups/db.sql) and tracked via JSON manifests.
- Upload: Files exceeding the Telegram size limit are split into chunks automatically. Each chunk is uploaded as a separate message. A manifest message tracks all chunks, checksums, and metadata.
- Download: The manifest is fetched by virtual path, chunks are downloaded with retry logic and integrity verification (SHA-256), then merged back into the original file.
- List: Searches the storage channel for manifest messages and displays stored files grouped by directory.
- If
--storage-channelis not specified, TGManager auto-creates a channel named "TGManager Storage" - The channel is reused across sessions (found by title)
- Do not delete the storage channel or its messages manually
If a storage upload is interrupted, re-running the same command will skip already-uploaded chunks and resume from where it left off.
Below are practical workflows for managing files with Telegram storage. All examples use node dist/index.js — replace with ./uploader-linux (or npm start --) if using binaries or production mode.
# Upload today's database dump
node dist/index.js -a myaccount -c upload-storage \
-f /var/backups/postgres-2026-02-05.sql.gz \
--virtual-path /backups/db/postgres-2026-02-05.sql.gz
# List all database backups
node dist/index.js -a myaccount -c list-storage --virtual-path /backups/db/
# Restore a specific backup
node dist/index.js -a myaccount -c download-storage \
--virtual-path /backups/db/postgres-2026-02-05.sql.gz \
--output-path /tmp/restore.sql.gz# Upload a large video (auto-splits if it exceeds Telegram's limit)
node dist/index.js -a myaccount -c upload-storage \
-f ~/Videos/recording-4k.mkv \
--virtual-path /videos/recording-4k.mkv \
--delete-source
# Verify it's stored
node dist/index.js -a myaccount -c list-storage --virtual-path /videos/
# Download it later on another machine
node dist/index.js -a myaccount -c download-storage \
--virtual-path /videos/recording-4k.mkv \
--output-path ~/Downloads/recording-4k.mkv# Upload multiple project archives with a folder structure
node dist/index.js -a myaccount -c upload-storage \
-f ./project-v1.tar.gz --virtual-path /archives/myapp/v1.0.0.tar.gz
node dist/index.js -a myaccount -c upload-storage \
-f ./project-v2.tar.gz --virtual-path /archives/myapp/v2.0.0.tar.gz
# List only files under /archives/myapp/
node dist/index.js -a myaccount -c list-storage --virtual-path /archives/myapp/By default, TGManager auto-creates a channel. If you want to use a specific channel (e.g., to separate personal and work storage):
# Upload to a specific channel
node dist/index.js -a myaccount -c upload-storage \
-f ./report.pdf \
--virtual-path /work/reports/q1-2026.pdf \
--storage-channel -1001234567890
# List and download from the same channel
node dist/index.js -a myaccount -c list-storage \
--storage-channel -1001234567890
node dist/index.js -a myaccount -c download-storage \
--virtual-path /work/reports/q1-2026.pdf \
--storage-channel -1001234567890# Start uploading a 10 GB file — gets interrupted at 60%
node dist/index.js -a myaccount -c upload-storage \
-f ~/iso/ubuntu-server.iso \
--virtual-path /iso/ubuntu-server.iso
# ^C (interrupted)
# Re-run the exact same command — skips already uploaded chunks
node dist/index.js -a myaccount -c upload-storage \
-f ~/iso/ubuntu-server.iso \
--virtual-path /iso/ubuntu-server.iso
# Resuming upload: 6/10 chunks already uploaded, uploading remaining 4...# First download
node dist/index.js -a myaccount -c download-storage \
--virtual-path /backups/db/latest.sql.gz \
--output-path ./latest.sql.gz
# Download again — fails because file already exists
node dist/index.js -a myaccount -c download-storage \
--virtual-path /backups/db/latest.sql.gz \
--output-path ./latest.sql.gz
# Error: Output file already exists. Use --force to overwrite.
# Force overwrite
node dist/index.js -a myaccount -c download-storage \
--virtual-path /backups/db/latest.sql.gz \
--output-path ./latest.sql.gz \
--force-
Authentication failed
- Check your API credentials
- Ensure phone number format includes country code
- Verify 2FA password if enabled
-
File upload fails
- Check file size limits
- Verify file permissions
- Ensure sufficient disk space
-
Rate limiting
- Tool handles this automatically
- Increase flood wait multiplier in config if needed
-
Session errors
- Delete session folder and re-authenticate
- Check session directory permissions
-
Storage download fails
- Verify the virtual path is correct with
list-storage - Ensure the storage channel and its messages haven't been deleted
- Use
--forceif the output file already exists
- Verify the virtual path is correct with
-
Another instance already running
- A process lock prevents concurrent use of the same account
- Wait for the other instance to finish, or check for stale lock files in the
locks/directory
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Language: TypeScript 5.x with ES modules
- Runtime: Node.js 22+
- Type Safety: Full TypeScript support with strict mode
- Telegram API: GramJS
- Image Processing: Sharp
- Video Processing: FFmpeg
- Logging: Winston with rotation
- CLI: Commander.js
- Testing: Vitest
- Build: esbuild (bundling) + pkg (native binaries)
By default, all data is stored within the project directory:
- Sessions:
./sessions/- Telegram session files - Uploads:
./uploads/- Default directory for files to upload - Logs:
./logs/- Application and error logs
You can customize these locations using environment variables to use absolute paths or different relative paths.
For issues and feature requests, please use the GitHub issue tracker.