html-to-markdown
html-to-markdown is a high-performance HTML→Markdown converter with a Rust core and 12 native language bindings. It converts HTML to CommonMark Markdown, Djot, or plain text in a single pass, optionally extracting metadata, tables, inline images, and a structured document tree.
Use this skill when writing code that:
- Converts HTML strings, files, or live URLs to Markdown, Djot, or plain text
- Extracts metadata (title, OG tags, JSON-LD/Microdata/RDFa, headers, links, images, language) from HTML
- Extracts structured table data (GFM markdown + cell grids) from HTML
- Extracts a structured document-structure tree
- Extracts inline images (data URIs, SVGs) from HTML
- Uses preprocessing to clean noisy HTML (ads, navigation, forms) before conversion
Capability map
| Capability |
CLI |
SDKs |
| HTML→Markdown / Djot / plain text |
html-to-markdown FILE |
convert(html, options) |
| Read HTML from stdin / file / URL |
cat f | …, FILE, --url URL |
convert(htmlString, …) |
| ~30 config options (headings, code blocks, lists, escaping, wrapping…) |
flags |
ConversionOptions |
| Metadata extraction |
--json (default-extracted) |
result.metadata |
| Table extraction |
--json → tables[] |
result.tables |
| Document structure tree |
--json --include-structure |
include_document_structure=true |
| Inline image extraction |
--json --extract-inline-images |
extract_images=true |
| HTML preprocessing |
--preprocess [--preset …] |
PreprocessingOptions |
| Extraction-only (no Markdown body) |
--json --no-content |
options + read fields |
Installation
CLI
# (Homebrew 6.0+ requires explicit trust for third-party taps)
brew trust xberg-io/tap
brew install xberg-io/tap/html-to-markdown
# or run without a persistent install (the CLI proxy package self-installs the binary):
npx @xberg-io/html-to-markdown-cli --help
uvx --from html-to-markdown-cli html-to-markdown --help
# or download a prebuilt binary from the latest GitHub release:
# https://github.com/xberg-io/html-to-markdown/releases/latest
# or build from source:
cargo install html-to-markdown-cli
Language SDKs
pip install html-to-markdown # Python
npm install @xberg-io/html-to-markdown # TypeScript / Node.js
cargo add html-to-markdown-rs # Rust (features: metadata default; full = all)
gem install html-to-markdown # Ruby
composer require xberg-io/html-to-markdown # PHP
go get github.com/xberg-io/html-to-markdown/packages/go/v3 # Go
dotnet add package XbergIo.HtmlToMarkdown # C#
npm install @xberg-io/html-to-markdown-wasm # WASM
- Java (Maven):
io.xberg:html-to-markdown
- Elixir:
{:html_to_markdown, "~> 3.8"} in mix.exs
- R:
install.packages("htmltomarkdown", repos = "https://xberg-io.r-universe.dev")
- C (FFI): pre-built
.so / .dll / .dylib from GitHub releases
CLI vs SDK — which to use
- CLI — one-shot conversions, shell pipelines, fetching a single URL, ad-hoc metadata/table extraction via
--json | jq. Flags only for conversion (FILE is positional; omit or use - for stdin); the only subcommand is mcp.
- SDK — embedding conversion in application code, batch processing, custom element conversion (visitor pattern, Rust), and tight loops where process spawn overhead matters.
- MCP server —
html-to-markdown mcp exposes convert_html and extract_metadata as agent tools, so an MCP client can convert an HTML string directly with no shell-out. This plugin auto-registers it; see the using-the-mcp-server skill.
Both share the same ConversionResult shape, so output is interchangeable.
When to use html-to-markdown vs xberg vs crawlberg
- html-to-markdown — you already have HTML (a string, a file, or a single URL) and want clean Markdown plus structured metadata/tables. No OCR, no document parsing, no crawling.
- xberg — you have documents (PDF, Office, images, email, archives) and need full text/table/metadata extraction with optional OCR. Use it when the input is not already HTML.
- crawlberg — you need to crawl or scrape many pages, follow links, and handle JS-rendered sites with a headless-Chrome fallback. It uses html-to-markdown internally for the HTML→Markdown step.
Rule of thumb: single HTML in → Markdown out = html-to-markdown. Many URLs / a site = crawlberg. Non-HTML documents = xberg.
CLI quick start
# Convert a file to stdout
html-to-markdown input.html
# Convert and save
html-to-markdown input.html -o output.md
# Read from stdin
cat page.html | html-to-markdown
# Fetch and convert a URL
html-to-markdown --url https://example.com > out.md
# Full ConversionResult as JSON (content, tables, metadata, images, warnings)
html-to-markdown --json input.html
# JSON with document structure tree
html-to-markdown --json --include-structure input.html
# Extraction-only (no Markdown body)
html-to-markdown --json --no-content input.html
# Aggressive web-page cleanup
html-to-markdown input.html --preprocess --preset aggressive
SDK quick start
Rust
use html_to_markdown_rs::convert;
let result = convert("<h1>Hello World</h1><p>A paragraph.</p>", None)?;
println!("{}", result.content.unwrap_or_default());
Python
from html_to_markdown import convert
result = convert("<h1>Hello World</h1><p>A paragraph.</p>")
print(result.content) # # Hello World\n\nA paragraph.
print(result.metadata) # title, links, headers, …
TypeScript / Node.js
import { convert } from "@xberg-io/html-to-markdown";
// Node's convert() returns a ConversionResult object directly.
const result = convert("<h1>Hello World</h1><p>A paragraph.</p>");
console.log(result.content);
ConversionResult fields
All languages return the same structure (dict, object, or struct).
| Field |
Description |
content |
Converted text (Markdown/Djot/plain). null only in extraction-only mode. |
metadata |
Title, OG, headers, links, images, structured data. |
tables |
Tables with grid (structured cells) and markdown fields. |
images |
Extracted inline images (requires inline-image extraction). |
document |
Structured document tree when structure extraction is enabled. |
warnings |
Non-fatal processing warnings (message, kind). |
Configuration
All languages expose the same ~30 options. See references/configuration.md for the complete table. Common ones:
| Option |
Values |
Default |
heading_style |
atx, underlined, atx-closed |
atx |
code_block_style |
backticks, indented, tildes |
backticks |
output_format |
markdown, djot, plain |
markdown |
wrap / wrap_width |
bool / 20–500 |
off / 80 |
autolinks (SDK) / --no-autolinks (CLI) |
bool / flag |
true (on); disable in CLI with --no-autolinks |
| preprocessing |
minimal / standard / aggressive |
off |
Rust (builder)
use html_to_markdown_rs::{convert, ConversionOptions, HeadingStyle, OutputFormat};
let options = ConversionOptions::builder()
.heading_style(HeadingStyle::Atx)
.output_format(OutputFormat::Markdown)
.wrap(true)
.wrap_width(100)
.build();
let result = convert(html, Some(options))?;
Python (dataclass)
from html_to_markdown import convert, ConversionOptions, PreprocessingOptions
html = "<h1>Title</h1><p>Body text.</p>"
result = convert(
html,
ConversionOptions(
heading_style="atx",
wrap=True,
wrap_width=100,
preprocessing=PreprocessingOptions(enabled=True, preset="aggressive"),
),
)
Metadata extraction
The library convert() extracts metadata by default; the CLI needs --json --extract-metadata (see the extracting-metadata skill). Fields include document (title, description, language, canonical_url, open_graph), headers, links (with link_type), images, and structured_data (JSON-LD/Microdata/RDFa).
Table extraction
Tables appear in result.tables, each with a pre-rendered markdown string and a structured cell grid. Markdown tables also appear inline in content. See the extracting-tables skill.
Document structure extraction
Enable structure extraction (--include-structure on the CLI, include_document_structure=true in SDKs) to get a semantic node tree under document. Node types include heading, paragraph, list, list_item, table, image, code, quote, group, metadata_block.
Common pitfalls
convert() returns a result object, not a string. Access .content for the Markdown text. This holds for Node.js too — convert() returns a ConversionResult object directly; do not JSON.parse() it.
--json outputs JSON, not Markdown. Omit --json for plain Markdown.
--include-structure, --extract-inline-images, and --no-content require --json.
- The conversion CLI is flags-only.
FILE is positional; the only subcommand is mcp (starts the MCP server).
--preset, --keep-navigation, --keep-forms require --preprocess.
Additional resources
- CLI Reference — every flag, JSON shape, exit codes
- Configuration Reference — all 30+ options with defaults
- Rust API Reference — signatures, builder, feature flags
- Python API Reference — functions, dataclasses, type hints
- TypeScript API Reference — functions, interfaces, Buffer support
- Other Bindings — Go, Ruby, PHP, Java, C#, Elixir, R, WASM, C FFI
GitHub: https://github.com/xberg-io/html-to-markdown
1---2name: html-to-markdown3description: Convert HTML to Markdown, Djot, or plain text with structured extraction. Use when writing code that calls html-to-markdown APIs in Rust, Python, TypeScript, Go, Ruby, PHP, Java, C#, Elixir, R, C, or WASM. Covers installation, conversion, configuration, metadata extraction, tables, document structure, inline images, URL fetching, and CLI usage.4license: MIT5---67<!--8AI-RULEZ :: GENERATED FILE — DO NOT EDIT9Content-Hash: blake3:e3c81b07830ab6fbd6130642dfa934baf49b44e831645bd445f722ca1577356f10Source-Hash: blake3:89def62dcbf6aaee62165d15a18f42f582219162eac46a709e65513450c88c8f11Schema-Version: v112-->1314# html-to-markdown1516html-to-markdown is a high-performance HTML→Markdown converter with a Rust core and 12 native language bindings. It converts HTML to CommonMark Markdown, Djot, or plain text in a single pass, optionally extracting metadata, tables, inline images, and a structured document tree.1718Use this skill when writing code that:1920- Converts HTML strings, files, or live URLs to Markdown, Djot, or plain text21- Extracts metadata (title, OG tags, JSON-LD/Microdata/RDFa, headers, links, images, language) from HTML22- Extracts structured table data (GFM markdown + cell grids) from HTML23- Extracts a structured document-structure tree24- Extracts inline images (data URIs, SVGs) from HTML25- Uses preprocessing to clean noisy HTML (ads, navigation, forms) before conversion2627## Capability map2829| Capability | CLI | SDKs |30| ---------- | --- | ---- |31| HTML→Markdown / Djot / plain text | `html-to-markdown FILE` | `convert(html, options)` |32| Read HTML from stdin / file / URL | `cat f \| …`, `FILE`, `--url URL` | `convert(htmlString, …)` |33| ~30 config options (headings, code blocks, lists, escaping, wrapping…) | flags | `ConversionOptions` |34| Metadata extraction | `--json` (default-extracted) | `result.metadata` |35| Table extraction | `--json` → `tables[]` | `result.tables` |36| Document structure tree | `--json --include-structure` | `include_document_structure=true` |37| Inline image extraction | `--json --extract-inline-images` | `extract_images=true` |38| HTML preprocessing | `--preprocess [--preset …]` | `PreprocessingOptions` |39| Extraction-only (no Markdown body) | `--json --no-content` | options + read fields |4041## Installation4243### CLI4445```bash46# (Homebrew 6.0+ requires explicit trust for third-party taps)47brew trust xberg-io/tap48brew install xberg-io/tap/html-to-markdown49# or run without a persistent install (the CLI proxy package self-installs the binary):50npx @xberg-io/html-to-markdown-cli --help51uvx --from html-to-markdown-cli html-to-markdown --help52# or download a prebuilt binary from the latest GitHub release:53# https://github.com/xberg-io/html-to-markdown/releases/latest54# or build from source:55cargo install html-to-markdown-cli56```5758### Language SDKs5960```bash61pip install html-to-markdown # Python62npm install @xberg-io/html-to-markdown # TypeScript / Node.js63cargo add html-to-markdown-rs # Rust (features: metadata default; full = all)64gem install html-to-markdown # Ruby65composer require xberg-io/html-to-markdown # PHP66go get github.com/xberg-io/html-to-markdown/packages/go/v3 # Go67dotnet add package XbergIo.HtmlToMarkdown # C#68npm install @xberg-io/html-to-markdown-wasm # WASM69```7071- Java (Maven): `io.xberg:html-to-markdown`72- Elixir: `{:html_to_markdown, "~> 3.8"}` in `mix.exs`73- R: `install.packages("htmltomarkdown", repos = "https://xberg-io.r-universe.dev")`74- C (FFI): pre-built `.so` / `.dll` / `.dylib` from GitHub releases7576## CLI vs SDK — which to use7778- **CLI** — one-shot conversions, shell pipelines, fetching a single URL, ad-hoc metadata/table extraction via `--json | jq`. Flags only for conversion (`FILE` is positional; omit or use `-` for stdin); the only subcommand is `mcp`.79- **SDK** — embedding conversion in application code, batch processing, custom element conversion (visitor pattern, Rust), and tight loops where process spawn overhead matters.80- **MCP server** — `html-to-markdown mcp` exposes `convert_html` and `extract_metadata` as agent tools, so an MCP client can convert an HTML string directly with no shell-out. This plugin auto-registers it; see the **using-the-mcp-server** skill.8182Both share the same `ConversionResult` shape, so output is interchangeable.8384## When to use html-to-markdown vs xberg vs crawlberg8586- **html-to-markdown** — you already have HTML (a string, a file, or a single URL) and want clean Markdown plus structured metadata/tables. No OCR, no document parsing, no crawling.87- **xberg** — you have *documents* (PDF, Office, images, email, archives) and need full text/table/metadata extraction with optional OCR. Use it when the input is not already HTML.88- **crawlberg** — you need to *crawl or scrape many pages*, follow links, and handle JS-rendered sites with a headless-Chrome fallback. It uses html-to-markdown internally for the HTML→Markdown step.8990Rule of thumb: single HTML in → Markdown out = html-to-markdown. Many URLs / a site = crawlberg. Non-HTML documents = xberg.9192## CLI quick start9394```bash95# Convert a file to stdout96html-to-markdown input.html9798# Convert and save99html-to-markdown input.html -o output.md100101# Read from stdin102cat page.html | html-to-markdown103104# Fetch and convert a URL105html-to-markdown --url https://example.com > out.md106107# Full ConversionResult as JSON (content, tables, metadata, images, warnings)108html-to-markdown --json input.html109110# JSON with document structure tree111html-to-markdown --json --include-structure input.html112113# Extraction-only (no Markdown body)114html-to-markdown --json --no-content input.html115116# Aggressive web-page cleanup117html-to-markdown input.html --preprocess --preset aggressive118```119120## SDK quick start121122### Rust123124```rust125use html_to_markdown_rs::convert;126127let result = convert("<h1>Hello World</h1><p>A paragraph.</p>", None)?;128println!("{}", result.content.unwrap_or_default());129```130131### Python132133```python134from html_to_markdown import convert135136result = convert("<h1>Hello World</h1><p>A paragraph.</p>")137print(result.content) # # Hello World\n\nA paragraph.138print(result.metadata) # title, links, headers, …139```140141### TypeScript / Node.js142143```typescript144import { convert } from "@xberg-io/html-to-markdown";145146// Node's convert() returns a ConversionResult object directly.147const result = convert("<h1>Hello World</h1><p>A paragraph.</p>");148console.log(result.content);149```150151## ConversionResult fields152153All languages return the same structure (dict, object, or struct).154155| Field | Description |156| ----- | ----------- |157| `content` | Converted text (Markdown/Djot/plain). `null` only in extraction-only mode. |158| `metadata` | Title, OG, headers, links, images, structured data. |159| `tables` | Tables with `grid` (structured cells) and `markdown` fields. |160| `images` | Extracted inline images (requires inline-image extraction). |161| `document` | Structured document tree when structure extraction is enabled. |162| `warnings` | Non-fatal processing warnings (`message`, `kind`). |163164## Configuration165166All languages expose the same ~30 options. See [references/configuration.md](references/configuration.md) for the complete table. Common ones:167168| Option | Values | Default |169| ------ | ------ | ------- |170| `heading_style` | `atx`, `underlined`, `atx-closed` | `atx` |171| `code_block_style` | `backticks`, `indented`, `tildes` | `backticks` |172| `output_format` | `markdown`, `djot`, `plain` | `markdown` |173| `wrap` / `wrap_width` | bool / 20–500 | off / `80` |174| `autolinks` (SDK) / `--no-autolinks` (CLI) | bool / flag | `true` (on); disable in CLI with `--no-autolinks` |175| preprocessing | `minimal` / `standard` / `aggressive` | off |176177### Rust (builder)178179```rust180use html_to_markdown_rs::{convert, ConversionOptions, HeadingStyle, OutputFormat};181182let options = ConversionOptions::builder()183 .heading_style(HeadingStyle::Atx)184 .output_format(OutputFormat::Markdown)185 .wrap(true)186 .wrap_width(100)187 .build();188let result = convert(html, Some(options))?;189```190191### Python (dataclass)192193```python194from html_to_markdown import convert, ConversionOptions, PreprocessingOptions195196html = "<h1>Title</h1><p>Body text.</p>"197result = convert(198 html,199 ConversionOptions(200 heading_style="atx",201 wrap=True,202 wrap_width=100,203 preprocessing=PreprocessingOptions(enabled=True, preset="aggressive"),204 ),205)206```207208## Metadata extraction209210The library `convert()` extracts metadata by default; the CLI needs `--json --extract-metadata` (see the **extracting-metadata** skill). Fields include `document` (title, description, language, canonical_url, open_graph), `headers`, `links` (with `link_type`), `images`, and `structured_data` (JSON-LD/Microdata/RDFa).211212## Table extraction213214Tables appear in `result.tables`, each with a pre-rendered `markdown` string and a structured cell `grid`. Markdown tables also appear inline in `content`. See the **extracting-tables** skill.215216## Document structure extraction217218Enable structure extraction (`--include-structure` on the CLI, `include_document_structure=true` in SDKs) to get a semantic node tree under `document`. Node types include `heading`, `paragraph`, `list`, `list_item`, `table`, `image`, `code`, `quote`, `group`, `metadata_block`.219220## Common pitfalls2212221. **`convert()` returns a result object, not a string.** Access `.content` for the Markdown text. This holds for Node.js too — `convert()` returns a `ConversionResult` object directly; do **not** `JSON.parse()` it.2232. **`--json` outputs JSON, not Markdown.** Omit `--json` for plain Markdown.2243. **`--include-structure`, `--extract-inline-images`, and `--no-content` require `--json`.**2254. **The conversion CLI is flags-only.** `FILE` is positional; the only subcommand is `mcp` (starts the MCP server).2265. **`--preset`, `--keep-navigation`, `--keep-forms` require `--preprocess`.**227228## Additional resources229230- **[CLI Reference](references/cli-reference.md)** — every flag, JSON shape, exit codes231- **[Configuration Reference](references/configuration.md)** — all 30+ options with defaults232- **[Rust API Reference](references/rust-api.md)** — signatures, builder, feature flags233- **[Python API Reference](references/python-api.md)** — functions, dataclasses, type hints234- **[TypeScript API Reference](references/typescript-api.md)** — functions, interfaces, Buffer support235- **[Other Bindings](references/other-bindings.md)** — Go, Ruby, PHP, Java, C#, Elixir, R, WASM, C FFI236237GitHub: <https://github.com/xberg-io/html-to-markdown>