npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tg-markdown-converter

v1.1.1

Published

A robust, AST-based converter from Markdown to Telegram MarkdownV2

Readme

tg-markdown-converter

Deno JSR Score npm version bundle size License: MIT


A robust, AST-based converter that transforms standard Markdown (and GFM) into Telegram's MarkdownV2 format.

🚀 Features

  • AST-Driven: Powered by remark-parse and remark-gfm.
  • Configurable: Customize bullet points, heading styles, and separators.
  • Safe Escaping: Context-aware escaping logic (separate rules for plain text, code, and URLs).
  • Smart Splitting: Automatically splits long messages into chunks (e.g., 4096 chars) ensuring valid Markdown syntax at boundaries.

📦 Installation

Usage in Deno (JSR)

deno add jsr:@ghotriw/tg-markdown-converter

Usage in Node.js (NPM)

npm install tg-markdown-converter
# or
yarn add tg-markdown-converter
# or
pnpm add tg-markdown-converter

Usage in Bun

bun add tg-markdown-converter

⚡ Quick Start

import { converter } from 'tg-markdown-converter';

const markdown = `
# Hello World
Check out this [link](https://example.com).
- Item 1
- Item 2
`;

const telegramSafe = converter(markdown);

console.log(telegramSafe);
// Output:
// *⭐️ HELLO WORLD*
// Check out this [link](https://example.com)\.
// • Item 1
// • Item 2

⚙️ Configuration

const options = {
  olSeparator: ')',  // 1) instead of 1.
  ulMarker: '-',     // Use dashes instead of dots
  imgMarker: '🎨',   // 🎨 instead of 🖼
  thematicBreak: '* * *',
  headingEmojis: {
    h1: '🔥',
    h2: '✨',
    // ...
  },
  splitAt: 4096
};

converter(myMarkdown, options);

| Option | Type | Default | Description | |-----------------|----------|------------------|-------------| | olSeparator | string | . | Separator for ordered lists. | | ulMarker | string | | Marker for unordered lists. | | imgMarker | string | 🖼 | Marker used before image alt text. | | thematicBreak | string | ▬▬▬▬▬▬▬▬▬▬▬▬▬▬ | String for horizontal rules. | | headingEmojis | object | { h1: 📌... } | Emojis prefixed to headings. | | splitAt | number | undefined | Max characters per output chunk (e.g. 4096). |

✂️ Message Splitting (Chunking)

Telegram has a limit of 4096 characters per chunk. If you pass the splitAt option, the converter automatically returns an array of strings (string[]) instead of a single string.

It ensures that the split happens between blocks (paragraphs, lists, headers), preserving valid Markdown syntax for each chunk.

Thanks to TypeScript conditional types, the return type is inferred automatically based on the options provided.

const longMarkdown = `... very long text ...`;

// 1. Usage with splitting
// TypeScript infers 'chunks' as string[] automatically
const chunks = converter(longMarkdown, { splitAt: 4000 }); // <-- chunks

for (const chunk of chunks) { // <-- chunk
  await bot.sendMessage(chatId, chunk, { parse_mode: 'MarkdownV2' }); // <-- chunk
}

// 2. Standard usage
// TypeScript infers 'singleChunk' as string
const singleChunk = converter(longMarkdown); // <-- singleChunk
await bot.sendMessage(chatId, singleChunk, { parse_mode: 'MarkdownV2' });

🧑‍💻 Advanced Usage (Extensibility)

The library uses a Visitor Pattern, allowing users to override the rendering logic for any node type (e.g., heading, link, table) by passing custom handler functions. This is useful for specific formatting tweaks or supporting custom Markdown extensions.

For example, to convert bold text (**text**) into Telegram's italic (_text_), you can override the strong handler:

import { converter } from 'tg-markdown-converter';
import type { HandlersMap } from 'tg-markdown-converter/dist/types'; // Import types for safety

// 1. Define a custom handler map
const customHandlers: HandlersMap = {
  // Override the 'strong' node renderer
  strong: (node, ctx, traverse) => {
    // 2. Use underscore for italic instead of asterisk for bold
    return `_${traverse((node as any).children, ctx)}_`;
  },

  // You can also add handlers for types not covered by default (e.g., 'customElement')
};

const markdown = "This is **important** text.";

// 3. Pass the custom handlers object to the converter
const result = converter(markdown, {}, customHandlers);

console.log(result);
// Output:
// "This is _important_ text\."

🧪 Run tests:

deno task test

📜 License

MIT ©