Skip to content

Repository files navigation

TelegramFreeRich

TelegramFreeRich

Telegram's rich text editor — for everyone, without Premium.

Because bots should not have more rights than humans.

Version 5.5.0 Bot API 10.1 Electron 35 React 19 TipTap 2 265 tests MIT

🇮🇷 فارسی  •  Quick start  •  Checklists  •  The window  •  Features  •  Architecture  •  Security  •  Troubleshooting  •  Development

TelegramFreeRich — editor and live preview


Why this exists

In June 2026 Telegram shipped a rich text editor — and locked it behind Premium. The very same formatting is available to every bot, for free, through Bot API 10.1.

Telegram Premium A bot + this app
Headings, quotes, tables, code blocks 💎 Premium ✅ Free
Spoilers, marked text, sub/superscript 💎 Premium ✅ Free
Checklists, collapsibles, formulas 💎 Premium ✅ Free
Monthly cost 💸 0

No coding, no server, no subscription. Point the app at a bot token and a chat, and write.


🚀 Quick start

1 — Get a bot. Message @BotFather, send /newbot, copy the token. Add the bot to your channel or group as an administrator.

2 — Install. Grab a package from the latest release:

Platform File Notes
Windows 10/11 TelegramFreeRich-Setup-*.exe Installs for the current user — no admin rights
Windows 10/11 TelegramFreeRich-*-portable.exe Runs as-is, nothing to install
Linux TelegramFreeRich-*.AppImage chmod +x it and run

Or start from source:

git clone https://github.com/Aporis3674/TelegramFreeRich.git
cd TelegramFreeRich
npm install
npm run dev

3 — Configure. Click the ⚙ gear in the title bar, paste the token and the chat ID (@channel or a numeric ID), press Test connection, then Save.

Write your message and press Ctrl+Enter. That's it.


🌐 Blocked country? Use your proxy

Telegram is blocked in some countries, and people reach it through v2rayN, Nekoray or a similar client. Those clients offer “Set system proxy”, which configures Windows — and therefore Chromium — but not Node. Any Electron app that sends with Node's https module ignores it and times out.

This app sends every Telegram request through Chromium's network stack (net.request on the default session), so the system proxy is honoured, PAC scripts included.

Settings → Connection:

Mode When to use it
System proxy (default) Your VPN client has “Set system proxy” enabled — nothing else to do
Manual proxy The client only exposes a port. One click fills in v2rayN's defaults: SOCKS5 127.0.0.1:10808 or HTTP 127.0.0.1:10809
No proxy Direct connection

Check connection to Telegram reports which proxy Chromium resolved and whether api.telegram.org answered, so a blocked network is never mistaken for a bad token. Manual proxies may carry a username and password; the password is encrypted next to the bot token and never reaches the window you type in.


☑ Checklists

A checklist travels inside the message. Rich HTML has its own task-list form — <ul><li><input type="checkbox" checked>done</li></ul> — so Telegram draws real checkboxes, in a channel, a group or a private chat alike, with the ticks you set in the editor preserved. Nothing extra to configure; write a checklist and send it.

Telegram also has a second, different thing: an interactive checklist, sent with the sendChecklist method, whose boxes readers can tick themselves. That one is restricted to a bot acting on behalf of a connected business account, in a private chat — and it comes with its own limits, 30 tasks and 100 characters each, and no way to send a task already ticked (markChecklistTasksAsDone does that afterwards).

The app picks between them for you:

Settings ▸ Business connection ID Chat What gets sent
empty (the normal case) anything, channels included checkboxes inside the message
set private (numeric ID) an interactive checklist — the app asks for its title
set channel or group checkboxes inside the message, with a note saying why

🪟 The window

A faithful rebuild of Telegram Desktop's composer: frameless, dark, hairline-outlined pill buttons and one salmon send button.

┌ ⚙ ▣ ¶ ────────────────────────────────────────────  ─  □  ✕ ┐
│  ( ↶ )( ↷ )   ( Aa )( B )( ☰ )( ▦ )( 🔗 )( 🖼 )( Σ )         │
│                                                              │
│  Message                                    │  Live preview  │
│                                             │                │
│  ( ✦ )                       342 / 32,768  ( 🗑 )      ( ➤ ) │
└──────────────────────────────────────────────────────────────┘
Button Click it and you get
↶ ↷ Undo / redo — the full ProseMirror history
Aa Formatting → Heading (opens H1–H6), Text, Quote, Pull quote, Attribution (the <cite> a quote may name), Code block, Footer, Divider
B Text style → Bold, Italic, Underline, Strikethrough, Spoiler, Subscript, Superscript, Marked
Insert list → Ordered list, Bullet list, Checklist (see Checklists), Details
Insert table → an editable 3×3 table; a floating bar adds and removes rows and columns, and column widths drag on the borders
🔗 Insert link → a Create link panel with Text and URL fields
🖼 Insert media → Photo or video (two or more become a slideshow), Audio file, Location, Caption… (the <figcaption> under a media item)
Σ Insert formula → asks for the formula, drops in a math block
The insert palette — search every block and format by name
🗑 ➤ Clear the message · Send it (right-click to pick rich message, draft or edit)

The title bar holds the settings gear with a connection dot, the live-preview toggle and the right-to-left switch for the message you are writing.

No emoji button. Custom emoji are a Premium-only entity that bots cannot send through the rich-message API. Plain Unicode emoji typed from your keyboard travel as ordinary text.


✍️ What you can send

Text Bold · Italic · Underline · Strikethrough · Spoiler · Marked · Subscript · Superscript · Monospace · Inline formula · Links
Blocks Heading H1–H6 · Paragraph · Quote (with attribution) · Pull quote (with attribution) · Code block with language · Divider · Collapsible · Footer · Formula block
Lists Ordered · Bulleted · Checklist (see Checklists)
Tables Header row, editable cells, drag-to-resize columns
Media Photo · Video · Audio · Slideshow (‹ › carousel) · Collage · Location — each with an optional caption

And while you write:

  • Live preview — a real Telegram bubble, rendered from the same document you are editing
  • Markdown shortcuts## , > , - , 1. , ```, **bold**, `code`
  • Insert palette — one search box over every block and format
  • Dark and light themes, English and Persian UI, full right-to-left support
  • Character counter against Telegram's 32,768 limit

Send modes

Right-click the send button:

Mode What it does
Rich message sendRichMessage — the normal path
Draft sendRichMessageDraft — a 30-second preview, private chats only
Edit editMessageText — rewrites an existing message (ID comes from Settings)

Keyboard shortcuts

Ctrl+Enter Send Ctrl+K Link Ctrl+P Preview Ctrl+, Settings
Ctrl+B Bold Ctrl+I Italic Ctrl+U Underline Ctrl+E Monospace
Ctrl+Shift+X Strikethrough Ctrl+Shift+P Spoiler Ctrl+Z Undo Ctrl+Shift+Z Redo

🧩 How it works

sendRichMessage takes rich_message: { html | markdown } — exactly one of the two. The RichBlock array is the receiving shape: what a bot reads back from Message.rich_message, not what it sends. So the app serializes the editor into Telegram's Rich HTML dialect:

TipTap (ProseMirror) document
        │
        ▼
HTML serializer  src/shared/html-serializer.js
   ├─ keeps    <b> <i> <u> <s> <code> <mark> <sub> <sup> <a>
   │           <p> <h1>…<h6> <ul> <ol> <li> <pre> <blockquote> <footer> <hr/>
   │           <table> <details> <img> <video> <audio>
   ├─ maps     spoiler → <tg-spoiler>      pull quote → <aside>
   │           math → <tg-math> / <tg-math-block>
   │           gallery → <tg-collage> / <tg-slideshow>
   │           location → <tg-map lat long zoom/>
   └─ drops    every tag and URL scheme the API does not document
        │
        ▼
   rich_message: { html, is_rtl?, skip_entity_detection? }
        │
        ├──► sendRichMessage        rich messages
        ├──► sendRichMessageDraft   30-second drafts (private chats, needs draft_id)
        ├──► editMessageText        rewrite an existing message
        └──► sendChecklist          interactive checklist, business only

Before sending, the serializer counts what it produced and checks the documented limits: 32,768 characters, 500 blocks, 50 media files, 20 table columns.

The live preview keeps its own path — block-parser.js and inline-parser.js build the RichBlock / RichText shapes Telegram sends back, which is what the bubble renders.

Editor → Rich HTML
In the editor On the wire
heading H1–H6 <h1><h6>
paragraph <p>
quote <blockquote> — plus <cite> when it names a source
pull quote <aside> — plus <cite> when it names a source
code block <pre><code class="language-…">
divider <hr/>
bullet / ordered list <ul> / <ol start="…">
table <table bordered> with <th> / <td>
collapsible <details open><summary>
footer <footer>
photo / video / audio <img src> / <video src> / <audio src> — plus <figcaption> when captioned
slideshow / collage <tg-slideshow> / <tg-collage>
location <tg-map lat long zoom/>
formula block / inline <tg-math-block> / <tg-math>
checklist <ul><li><input type="checkbox">… — or lifted out for sendChecklist
Inline marks
Mark On the wire Telegram entity
bold / italic / underline / strikethrough <b> <i> <u> <s> RichTextBold, RichTextItalic, RichTextUnderline, RichTextStrikethrough
spoiler <tg-spoiler> RichTextSpoiler
marked <mark> RichTextMarked
monospace <code> RichTextCode
subscript / superscript <sub> / <sup> RichTextSubscript / RichTextSuperscript
link <a href> RichTextUrl
inline formula <tg-math> RichTextMathematicalExpression

Links keep only http, https, mailto, tel, tg and in-document #anchor targets; media accepts http(s) only, exactly as the API requires.


🔒 Security

The bot token is the one secret in this app, and it never reaches the window you type in.

Renderer (React)                     Main process (Electron)
─────────────────                    ───────────────────────
✗ never holds the token              ✓ token encrypted with safeStorage
✗ never calls the network   ──IPC──► ✓ every Telegram request starts here
✓ knows only "a token exists"        ✓ validates method, chat ID, language
                                     ✓ 30 s timeout · 1 MB response cap
Layer Protection
Token storage safeStorage — OS keychain encryption, on disk as settings.enc; the proxy password sits beside it
IPC bridge contextBridge with contextIsolation — no Node APIs in the renderer
Input validation Allowlists for API methods, chat IDs, languages and the business connection ID
HTTP 30-second timeout, 1 MB response limit
URLs javascript:, data: and vbscript: schemes are refused
Rendering The preview builds React elements — never innerHTML
CSP Content-Security-Policy meta tag in index.html

🧯 When something goes wrong

What you see What it means
Network error: net::ERR_TUNNEL_CONNECTION_FAILED The proxy is set but not answering. Start your VPN client, or check the host and port in Settings ▸ Connection
Network error: net::ERR_CERT_AUTHORITY_INVALID A proxy is terminating TLS. Install its certificate, or pick a different proxy
Check connection works but sending fails Read the message after Network error:. Anything beginning Bad Request came from Telegram and is about the message itself, not the network
Bad Request: chat not found The bot is not in that chat, or the chat ID is wrong. A channel needs @username or its -100… ID, and the bot must be an administrator
Bad Request: not enough rights Give the bot permission to post in the channel or group
Windows: Failed to uninstall old application files A leftover TelegramFreeRich.exe is holding the files. Since 5.1.1 the installer closes it itself; on an older version, end the process in Task Manager first, or use the portable build
The window is blank Delete settings.enc from %APPDATA%/TelegramFreeRich (or ~/.config/TelegramFreeRich) and start again — you will have to re-enter the token

🛠 Development

npm run dev           # Vite + Electron with hot reload
npm test              # 265 unit tests (Vitest)
npm run lint          # ESLint, zero warnings
npm run format        # Prettier
npm run build         # Windows: dist/TelegramFreeRich-Setup-5.4.0.exe + …-portable.exe
npm run build:linux   # Linux: dist/TelegramFreeRich-5.4.0.AppImage
Project layout
installer/installer.nsh          NSIS customisation — closes the app before upgrading

src/
├── main/                       Electron main process
│   ├── main.js                 window, IPC handlers, encrypted settings
│   ├── preload.js              contextBridge — the only renderer surface
│   ├── net/proxy.js            system / manual / direct proxy resolution
│   ├── net/request.js          requests over Chromium's stack, proxy-aware errors
│   └── security/validation.js  method / chat ID / language / business ID allowlists
│
├── renderer/                   React UI
│   ├── App.jsx                 shell: state, send logic, shortcuts
│   ├── components/
│   │   ├── TitleBar.jsx        frameless window chrome
│   │   ├── Toolbar.jsx         the seven-button strip + menus
│   │   ├── ActionMenu.jsx      menu renderer, two levels deep
│   │   ├── Popover.jsx         anchored floating panel
│   │   ├── TableBubble.jsx     floating table row/column controls
│   │   ├── InsertPalette.jsx   searchable palette
│   │   ├── BottomBar.jsx       palette · counter · clear · send
│   │   ├── Preview.jsx         live Telegram bubble
│   │   ├── Settings.jsx        token, chat, business connection, proxy, theme
│   │   ├── Dialog.jsx          promise-based prompt / confirm / link panel
│   │   ├── Toast.jsx           notifications
│   │   ├── Icons.jsx           the SVG set
│   │   ├── useTfrEditor.js     TipTap setup
│   │   └── extensions.js       custom nodes and marks
│   ├── lib/editor-actions.js   one registry behind every menu
│   ├── i18n/                   en · fa · provider
│   └── styles/                 theme · app · toolbar · menu · editor · preview
│
└── shared/                     used by both processes
    ├── block-types.js          BlockType / InlineType enums
    ├── block-parser.js         DOM → Block State
    ├── inline-parser.js        inline DOM → styled segments
    ├── html-serializer.js      editor DOM → Rich HTML + request bodies
    ├── block-manager.js        CRUD + undo/redo
    ├── constants.js            limits and defaults
    └── utils.js                sanitizeUrl, validation, helpers

tests/unit/                     265 tests — HTML serializer, proxy, packaging, docs, parsers,
                                i18n parity, stylesheets, validation

Adding a command means adding one entry to src/renderer/lib/editor-actions.js; the menus, the palette and the tests all read from that registry.

Custom TipTap extensions
Extension Kind Renders as
Spoiler mark <span data-spoiler>
InlineMath mark <span data-inline-math>
PullQuote node <blockquote data-pullquote>
Details node <details> with an editable body
Footer node <footer>
MathBlock node div.tg-math
MediaBlock node <video> / <audio>
GalleryBlock node div.tg-gallery — slideshow or collage
MapBlock node div.tg-map

📦 Tech stack

Layer Choice
Desktop shell Electron 35 — frameless window, safeStorage, contextBridge
UI React 19 + Vite 6
Editor TipTap 2 (ProseMirror)
Data model Editor DOM → Telegram Rich HTML (rich_message.html)
Tests Vitest 2 + jsdom
Packaging electron-builder — NSIS installer, Windows portable, AppImage
CI GitHub Actions — lint, test, build

ChangelogDesign specBot API docs

MIT © Aporis3674

The best things in life are free. The second-best are also free, if you use a bot.

About

Free Rich Text Editor for Telegram

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages