bm.md: a Markdown formatter built around WeChat public accounts
更好用的 Markdown 排版助手|一键适配微信公众号、网页与图片。
At a glance
- What is it?
- bm.md (npm package bmmd) reformats Markdown for WeChat public accounts and other HTML targets, with a CLI, REST API and MCP integration. It is a self-hosted TanStack Start app, not a hosted editor, and its own docs are the main thing to read before committing.
- Who is it for?
- Adopt bm.md if you publish Markdown to WeChat public accounts and want the formatting step scriptable through the bmmd CLI, the REST API or MCP rather than done by hand in a browser editor. Do not adopt it if you need an actively released, versioned product, if you cannot run Node 20 or newer, or if your pipeline is not JavaScript or TypeScript, because the CLI ships only bin/bmmd.mjs and no standalone binary.
- Can I use it commercially?
- Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 15 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What bm.md solves, and for whom
Markdown that looks fine in a repository often looks wrong once it lands in a publishing system. WeChat public accounts are the sharpest case: the article body is pasted as styled HTML, inline styles matter, and code blocks, tables and diagrams have to survive the trip. bm.md is a formatter aimed at exactly that step. The README describes it as a Markdown typesetting assistant with dedicated WeChat public account adaptation, and it also supports generic HTML output plus export to JPEG, PNG and paginated PDF.
The intended user is someone who already writes in Markdown and needs a repeatable way to produce the final article. The repository backs that up with three integration surfaces rather than one: a web editor, a CLI named bmmd, and a REST API plus MCP protocol integration, listed in the README under developer-friendly features. That combination suggests a workflow where a writer drafts in Markdown, a script renders it, and a human pastes or uploads the result. If you only ever publish to a static site generator, the WeChat-specific styling work is overhead you will not use.
How the rendering pipeline is put together
The stack is TanStack Start on React 19 with TanStack Router, built by Vite 8, styled with Tailwind CSS 4 and shadcn/ui, and deployed through Nitro. The README lists Nitro targets including Cloudflare Workers, Vercel, Netlify, Docker, Alibaba ESA and Tencent EdgeOne. The repository layout matches that description: a src/ directory, a Dockerfile, a docs/ folder with separate files for features, architecture, design and an agent UI skill, and a tsdown.cli.config.ts that builds the CLI separately from the web build.
The editor is CodeMirror 6 based, with @codemirror/lang-markdown and @codemirror/view among the dependencies in package.json, which is consistent with the README's claim of a live preview. Diagrams come from Mermaid and AntV Infographic, both present in the dependency list. The formatting layer is a separate package surface: the CLI is built from tsdown.cli.config.ts into bin/bmmd.mjs, and the npm package named bmmd declares that single file as its bin entry. So the same core Markdown processing is reachable from the browser app and from a terminal, which is the design decision that matters most here. The README does not explain how much of the renderer is shared between the two, and docs/architecture.md is the file to read if that boundary decides your adoption.
Installing bm.md and rendering one WeChat article
The README states prerequisites of Node.js >= 20 and pnpm 11.11.0. The CLI package itself declares node >= 20 in its engines field, so the two agree. Clone and install first.
git clone https://github.com/miantiao-me/bm.md.git
cd bm.md
pnpm install
pnpm devThe dev server runs on port 2663, because the dev script is vite dev --port 2663. Open http://localhost:2663 and you get the editor. For a production build locally, the README gives pnpm build followed by pnpm preview.
If you only want the formatter, skip the app and use the CLI. The README shows a global install, a one-off run through pnpm dlx, and a pipe:
pnpm add -g bmmd
bmmd render article.md --platform wechat --output article.html
cat article.md | bmmd extract
bmmd lint article.md --fixThe render command reads a file, targets the wechat platform and writes HTML to the named output. The extract command reads from stdin and writes to stdout by default. The lint command rewrites the source file in place when --fix is passed, so run it on a committed file or a copy. For local development of the CLI itself, the README points at pnpm build:cli with bin/bmmd.mjs as the entry.
Self-hosting the web app is a container away, since the Dockerfile builds with pnpm and runs the Nitro output on port 3000:
docker build -t bmmd .
docker run -p 3000:3000 bmmdThe runtime image is gcr.io/distroless/nodejs24-debian13 and the command is /app/.output/server/index.mjs, so there is no shell inside the container to debug with. The commented debug variant in the Dockerfile is the escape hatch if you need one.
Storage, environment variables and the DC fallback
Every environment variable is optional according to the README, and .env.example shows the shape. Two variables are client-visible: VITE_APP_URL and VITE_API_URL. Analytics is server-side through ANALYTICS_SCRIPT_URL and ANALYTICS_SITE_ID.
The interesting part is uploads. S3 is enabled only when S3_ENDPOINT, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are all configured, with S3_BUCKET, S3_REGION and S3_PUBLIC_BASE_URL setting the target and the public address. The README is explicit about what happens otherwise: when the configuration S3 needs is incomplete, the storage service falls back to the DC image host at DC_UPLOAD_URL. That is a real operational decision, not a footnote. A partial S3 configuration does not fail loudly, it silently routes uploads somewhere else, and the .env.example file ships with placeholder credentials filled in. Copy it without editing and you have three non-empty strings that look like a complete S3 setup. Verify which backend is actually receiving files before you publish anything through a self-hosted instance.
Where bm.md is the wrong tool
The first limitation is release cadence. The repository has no retrieved releases, and package.json carries version 0.3.5 under the name bmmd. The last push to master was on 2026-09-10, so the project is being worked on, but a 0.x version with no published release notes means you should expect the CLI surface to move. Anything you script against bmmd render should be pinned to an exact version rather than a range.
The second is that the CLI is a Node artifact. The bin entry is bin/bmmd.mjs and the npm files array contains only bin, so there is no compiled binary for a Go or Rust pipeline to call without a Node runtime present. The REST API is the alternative there, but the README points at https://bm.md/docs for the reference rather than embedding it, so you cannot evaluate the API contract from the repository alone.
The third is scope. The README lists 16 typesetting styles and 14 code themes, which is a lot of surface for a tool whose job is producing one HTML string. If your target is a static site or a plain HTML email, the WeChat adaptation and the style gallery are weight you carry without benefit, and a Markdown-to-HTML library in your existing language will be simpler. Document import is similarly broad, covering Office, OpenDocument, extractable-text PDF, RTF, CSV and EPUB via @firecrawl/anydoc-wasm, and the README's qualifier about extractable text is worth taking literally.
Alternatives and the actual difference in approach
The README credits Kami (github.com/tw93/Kami) as the inspiration for the Kami typesetting style, which makes it a fair comparison point on the styling side. The difference in approach is where the logic lives. A style-oriented tool tends to be a set of themes applied at render time, and the output is whatever the theme produces. bm.md puts the same core processing behind a CLI, a REST API and an MCP server, so the formatting step can be called by an agent or a build script rather than by a person clicking a theme. The MCP integration is listed in the repository topics alongside mcp and skills, and there is a .mcp.json at the top level plus a skills/ directory and skills-lock.json, so agent integration is a first-class part of the repository rather than an afterthought.
That is the honest dividing line. If you want to pick a look and paste the result, a theme gallery is enough and bm.md is more machinery than you need. If you want the formatting to happen inside an automated publishing step, or to be invoked by a coding agent through MCP, the CLI and API surfaces are the reason to accept the extra stack.
Licence terms and what an upgrade costs
The project is LGPL-3.0, and package.json states LGPL-3.0-only. That is a copyleft licence with a linking exception rather than a permissive one. Running the unmodified bmmd package as a dependency or invoking the CLI is the ordinary case the licence is designed to allow. Modifying the covered source and distributing the result is where obligations attach. This is not legal advice, and if you plan to fork the renderer into a product, that is a question for your own counsel.
Upgrade cost is dominated by the absence of releases. With no retrieved release notes and a 0.x version, there is no changelog to read before bumping. The practical approach the repository supports is pinning bmmd to an exact version in your lockfile, and if you self-host, treating the Dockerfile as the contract: it installs with pnpm install --frozen-lockfile, builds with pnpm run build, and runs the Nitro server output. The devDependency list is broad, with CodeMirror packages, AntV Infographic, Tailwind and the ESLint config all present, so a major bump in any of them is a real upgrade event even if the formatter's own behaviour does not change.
Editorial conclusion
Adopt bm.md if you publish Markdown to WeChat public accounts and want the formatting step scriptable through the bmmd CLI, the REST API or MCP rather than done by hand in a browser editor. Do not adopt it if you need an actively released, versioned product, if you cannot run Node 20 or newer, or if your pipeline is not JavaScript or TypeScript, because the CLI ships only bin/bmmd.mjs and no standalone binary. Before deploying, verify the storage path: either confirm all three of S3_ENDPOINT, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are set, or accept the fallback to DC_UPLOAD_URL. Then read docs/architecture.md against src/ so you know which parts of the renderer you are depending on, since LGPL-3.0 means modifications to those parts carry obligations.
Frequently asked questions
What is bm.md and who is it for?
bm.md is a Markdown typesetting assistant with dedicated adaptation for WeChat public accounts, and it also outputs generic HTML plus JPEG, PNG and PDF. It targets people who write in Markdown and need a repeatable formatting step before publishing.
How do I install and run the bmmd CLI?
The README shows pnpm add -g bmmd for a global install, or pnpm dlx bmmd to run it without installing. A typical call is bmmd render article.md --platform wechat --output article.html, and it also accepts stdin for the extract command.
What are the prerequisites for running bm.md locally?
The README states Node.js >= 20 and pnpm 11.11.0. The bmmd package's engines field also declares node >= 20, and the dev server starts on port 2663.
What happens if my S3 configuration is incomplete?
The README states that S3 is enabled only when S3_ENDPOINT, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are all configured, and that when the required configuration is incomplete the storage service falls back to the DC image host set by DC_UPLOAD_URL.
Is bm.md free to use and modify?
It is licensed under LGPL-3.0, stated as LGPL-3.0-only in package.json. That permits ordinary use of the package, but modifications to the covered source carry distribution obligations, so check with your own counsel before forking it into a product.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/miantiao-me-bm-md)