<!-- This file is auto-generated by alef — DO NOT EDIT. -->
<!-- alef:hash:46d157f0c4bbda3702869d85611643634565ad72fb65121ad5009a5653132000 -->
<!-- To regenerate: alef readme -->
<!-- To verify freshness: alef verify -->

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/xberg-io/assets@v1/banner/readme-banner-dark.svg">
    <img alt="Xberg" width="420" src="https://cdn.jsdelivr.net/gh/xberg-io/assets@v1/banner/readme-banner-light.svg">
  </picture>
</p>

# html-to-markdown

<div align="center" style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin: 20px 0;">
  <a href="https://github.com/xberg-io/alef">
    <img src="https://img.shields.io/badge/built%20with-alef%20%D7%90-007ec6" alt="Built with alef">
  </a>
  <!-- Language Bindings -->
  <a href="https://crates.io/crates/html-to-markdown-rs">
    <img src="https://img.shields.io/crates/v/html-to-markdown-rs?label=Rust&color=007ec6" alt="Rust">
  </a>
  <a href="https://pypi.org/project/html-to-markdown/">
    <img src="https://img.shields.io/pypi/v/html-to-markdown?label=Python&color=007ec6" alt="Python">
  </a>
  <a href="https://www.npmjs.com/package/@xberg-io/html-to-markdown">
    <img src="https://img.shields.io/npm/v/@xberg-io/html-to-markdown?label=Node.js&color=007ec6" alt="Node.js">
  </a>
  <a href="https://www.npmjs.com/package/@xberg-io/html-to-markdown-wasm">
    <img src="https://img.shields.io/npm/v/@xberg-io/html-to-markdown-wasm?label=WASM&color=007ec6" alt="WASM">
  </a>
  <a href="https://central.sonatype.com/artifact/io.xberg/html-to-markdown">
    <img src="https://img.shields.io/maven-central/v/io.xberg/html-to-markdown?label=Java&color=007ec6" alt="Java">
  </a>
  <a href="https://pkg.go.dev/github.com/xberg-io/html-to-markdown/packages/go/v3">
    <img src="https://img.shields.io/github/v/tag/xberg-io/html-to-markdown?label=Go&color=007ec6&filter=v3*" alt="Go">
  </a>
  <a href="https://www.nuget.org/packages/XbergIo.HtmlToMarkdown/">
    <img src="https://img.shields.io/nuget/v/XbergIo.HtmlToMarkdown?label=C%23&color=007ec6" alt="C#">
  </a>
  <a href="https://packagist.org/packages/xberg-io/html-to-markdown">
    <img src="https://img.shields.io/packagist/v/xberg-io/html-to-markdown?label=PHP&color=007ec6" alt="PHP">
  </a>
  <a href="https://rubygems.org/gems/html-to-markdown">
    <img src="https://img.shields.io/gem/v/html-to-markdown?label=Ruby&color=007ec6" alt="Ruby">
  </a>
  <a href="https://hex.pm/packages/html_to_markdown">
    <img src="https://img.shields.io/hexpm/v/html_to_markdown?label=Elixir&color=007ec6" alt="Elixir">
  </a>
  <a href="https://xberg-io.r-universe.dev/htmltomarkdown">
    <img src="https://img.shields.io/badge/R-htmltomarkdown-007ec6" alt="R">
  </a>
  <a href="https://pub.dev/packages/h2m">
    <img src="https://img.shields.io/pub/v/h2m?label=Dart&color=007ec6" alt="Dart">
  </a>
  <a href="https://central.sonatype.com/artifact/io.xberg/html-to-markdown-android">
    <img src="https://img.shields.io/maven-central/v/io.xberg/html-to-markdown-android?label=Kotlin&color=007ec6" alt="Kotlin">
  </a>
  <a href="https://github.com/xberg-io/html-to-markdown/tree/main/packages/swift">
    <img src="https://img.shields.io/badge/Swift-SPM-007ec6" alt="Swift">
  </a>
  <a href="https://github.com/xberg-io/html-to-markdown/tree/main/packages/zig">
    <img src="https://img.shields.io/badge/Zig-package-007ec6" alt="Zig">
  </a>
  <a href="https://github.com/xberg-io/html-to-markdown/releases">
    <img src="https://img.shields.io/badge/C-FFI-007ec6" alt="C FFI">
  </a>

  <!-- Project Info -->
  <a href="https://github.com/xberg-io/html-to-markdown/blob/main/LICENSE">
    <img src="https://img.shields.io/badge/License-MIT-007ec6" alt="License">
  </a>
  <a href="https://docs.html-to-markdown.xberg.io">
    <img src="https://img.shields.io/badge/Docs-html--to--markdown-007ec6" alt="Documentation">
  </a>
</div>

<div align="center" style="display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin: 28px 0 24px;">
  <a href="https://discord.gg/xt9WY3GnKR">
    <img height="22" src="https://img.shields.io/badge/Discord-Chat-007ec6?logo=discord&logoColor=white" alt="Join Discord">
  </a>
  <a href="https://docs.html-to-markdown.xberg.io/demo/">
    <img height="22" src="https://img.shields.io/badge/Live%20Demo-Open-007ec6?logo=webassembly&logoColor=white" alt="Live Demo">
  </a>
</div>

High-performance HTML to Markdown converter for Node.js and Bun with full TypeScript support.
Native NAPI-RS bindings to the Rust core with a type-safe API.

## What This Package Provides

- **Same renderer as every binding** — output matches Rust, Python, Node.js, Ruby, PHP, Go, Java, .NET, Elixir, R, Dart, Swift, Zig, C FFI, and WASM.
- **Structured conversion result** — Markdown plus metadata, links, headings, images, tables, and warnings where the binding exposes them.
- **Production defaults** — HTML is parsed with the Rust core, sanitized by default, and rendered without runtime-specific Markdown drift.
- **Node-first TypeScript API** — native NAPI package, typed options/results, async-friendly integration for Node.js and Bun.

## Installation

```bash
pnpm add @xberg-io/html-to-markdown
```

Requires Node.js 18+ or Bun. Native bindings provide superior performance.

**npm:**

```bash
npm install @xberg-io/html-to-markdown
```

**pnpm:**

```bash
pnpm add @xberg-io/html-to-markdown
```

**yarn:**

```bash
yarn add @xberg-io/html-to-markdown
```

**bun:**

```bash
bun add @xberg-io/html-to-markdown
```

Alternatively, use the WebAssembly version for browser/edge environments:

```bash
npm install @xberg-io/html-to-markdown-wasm
```

## Performance Snapshot

**Apple M4** · `convert()` · Real Wikipedia documents

| Document | Size | Latency | Throughput |
|----------|------|---------|------------|
| Lists (Timeline) | 129KB | 0.58ms | 222 MB/s |
| Tables (Countries) | 360KB | 1.89ms | 190 MB/s |
| Mixed (Python wiki) | 656KB | 4.21ms | 156 MB/s |

## Quick Start

Basic conversion:

```typescript
import { convert } from "@xberg-io/html-to-markdown";

const result = convert("<h1>Hello World</h1>");
const markdown = result.content ?? "";
console.log(markdown); // # Hello World
```

With conversion options:

```typescript
import { convert, ConversionOptions, HeadingStyle } from "@xberg-io/html-to-markdown";

const options: ConversionOptions = {
  headingStyle: HeadingStyle.Atx,
  listIndentWidth: 2,
  wrap: true,
};

const result = convert("<h1>Title</h1><p>Content</p>", options);
const markdown = result.content;
```

## Architecture

The converter routes each input through one of three tiers based on a fast prescan of the byte stream:

1. **Tier-1 — single-pass byte scanner.** Handles 110+ HTML tags directly. Bails on any construct it cannot prove byte-equivalent to Tier-2.
2. **Tier-2 — DOM walker.** Picks up Tier-1 bails and inputs the classifier rejected up front.
3. **Tier-3 — standards-conformant parser.** Engaged for malformed HTML requiring full HTML5 repair.

The dispatcher is invisible to the caller. Output is byte-identical across tiers — enforced by a 116-snapshot oracle.

## Capabilities

- **16 languages, one Rust core.** Rust, Python, Node.js, WASM, Java, Go, C#, PHP, Ruby, Elixir, R, Dart, Kotlin (Android), Swift, Zig, C ABI.
- **CommonMark-compatible Markdown** with GFM-style tables.
- **Djot output**: set `output_format = "djot"` (see Djot Output Format section below).
- **Real-HTML robust**: unclosed tags, CDATA, custom elements, malformed entities, nested tables, mixed encodings handled without losing content.
- **Metadata extraction**, **visitor API**, **inline images**, **configurable preprocessing presets**.
- **Per-group regression gates in CI**: every PR runs the bench harness against per-group thresholds.

## API Reference

### Core Function

**`convert(html: string, options?: ConversionOptions): ConversionResult`**

Converts HTML to Markdown. Returns a `ConversionResult` object with all results in a single call.

```typescript
import { convert, ConversionOptions } from "@xberg-io/html-to-markdown";

const result = convert(html);
const markdown = result.content; // Converted Markdown string
const metadata = result.metadata; // Metadata (when extractMetadata: true)
const tables = result.tables; // Structured table data (needs includeDocumentStructure: true)
const document = result.document; // Document-level info (needs includeDocumentStructure: true)
const warnings = result.warnings; // Any conversion warnings
```

### Options

**`ConversionOptions`** – Key configuration fields:

- `headingStyle`: Heading format (`"Underlined"` | `"Atx"` | `"AtxClosed"`) — default: `"Atx"`
- `listIndentWidth`: Spaces per indent level — default: `2`
- `bullets`: Bullet characters cycle — default: `"-*+"`
- `wrap`: Enable text wrapping — default: `false`
- `wrapWidth`: Wrap at column — default: `80`
- `codeLanguage`: Default fenced code block language — default: none
- `extractMetadata`: Enable metadata extraction into `result.metadata` — default: `true`
- `outputFormat`: Output markup format (`"Markdown"` | `"Djot"` | `"Plain"`) — default: `"Markdown"`

## Djot Output Format

The library supports converting HTML to [Djot](https://djot.net/), a lightweight markup language similar to Markdown but with a different syntax for some elements. Set `output_format` to `"djot"` to use this format.

### Syntax Differences

| Element        | Markdown   | Djot       |
| -------------- | ---------- | ---------- |
| Strong         | `**text**` | `*text*`   |
| Emphasis       | `*text*`   | `_text_`   |
| Strikethrough  | `~~text~~` | `{-text-}` |
| Inserted/Added | N/A        | `{+text+}` |
| Highlighted    | N/A        | `{=text=}` |
| Subscript      | N/A        | `~text~`   |
| Superscript    | N/A        | `^text^`   |

### Example Usage

```typescript
import { convert, OutputFormat } from "@xberg-io/html-to-markdown";

const html = "<p>This is <strong>bold</strong> and <em>italic</em> text.</p>";

// Default Markdown output
const markdownResult = convert(html);
const markdown = markdownResult.content;
// Result: "This is **bold** and *italic* text."

// Djot output
const djotResult = convert(html, { outputFormat: OutputFormat.Djot });
const djot = djotResult.content;
// Result: "This is *bold* and _italic_ text."
```

Djot's extended syntax allows you to express more semantic meaning in lightweight text, making it useful for documents that require strikethrough, insertion tracking, or mathematical notation.

## Plain Text Output

Set `output_format` to `"plain"` to strip all markup and return only visible text. This bypasses the Markdown conversion pipeline entirely for maximum speed.

```typescript
import { convert, OutputFormat } from "@xberg-io/html-to-markdown";

const html = "<h1>Title</h1><p>This is <strong>bold</strong> and <em>italic</em> text.</p>";

const result = convert(html, { outputFormat: OutputFormat.Plain });
const plain = result.content;
// Result: "Title\n\nThis is bold and italic text."
```

Plain text mode is useful for search indexing, text extraction, and feeding content to LLMs.

## Metadata Extraction

The metadata extraction feature enables comprehensive document analysis during conversion. Extract document properties, headers, links, images, and structured data in a single pass — all via the standard `convert()` function.

**Use Cases:**

- **SEO analysis** – Extract title, description, Open Graph tags, Twitter cards
- **Table of contents generation** – Build structured outlines from heading hierarchy
- **Content migration** – Document all external links and resources
- **Accessibility audits** – Check for images without alt text, empty links, invalid heading hierarchy
- **Link validation** – Classify and validate anchor, internal, external, email, and phone links

**Zero Overhead When Disabled:** Metadata extraction adds negligible overhead and happens during the HTML parsing pass. Pass `extract_metadata: true` in `ConversionOptions` to enable it; the result is available at `result.metadata`.

### Example: Quick Start

```typescript
import { convert } from "@xberg-io/html-to-markdown";

const html = '<h1>Article</h1><img src="test.jpg" alt="test">';
const result = convert(html, { extractMetadata: true });

console.log(result.content); // Converted Markdown
console.log(result.metadata?.document?.title); // Document title
console.log(result.metadata?.headers); // All h1-h6 elements
console.log(result.metadata?.links); // All hyperlinks
console.log(result.metadata?.images); // All images with alt text
console.log(result.metadata?.structuredData); // JSON-LD, Microdata, RDFa
```

## Visitor Pattern

The visitor pattern enables custom HTML→Markdown conversion logic by providing callbacks for specific HTML elements during traversal. Pass a visitor as the third argument to `convert()`.

**Use Cases:**

- **Custom Markdown dialects** – Convert to Obsidian, Notion, or other flavors
- **Content filtering** – Remove tracking pixels, ads, or unwanted elements
- **URL rewriting** – Rewrite CDN URLs, add query parameters, validate links
- **Accessibility validation** – Check alt text, heading hierarchy, link text
- **Analytics** – Track element usage, link destinations, image sources

**Supported Visitor Methods:** 40+ callbacks for text, inline elements, links, images, headings, lists, blocks, and tables.

### Example: Quick Start

```typescript
import { convert, NodeContext, VisitResult } from "@xberg-io/html-to-markdown";

// `visitor` is a plain object of camelCase callbacks — there is no exported
// `Visitor` type. `VisitResult` is a type, not a runtime enum: return the
// lowercase string `"continue"` / `"skip"` / `"preserve_html"` for the
// built-in behaviors, or `{ custom: "..." }` to replace a node's output.
const visitor = {
  visitLink(ctx: NodeContext, href: string, text: string, title?: string) {
    // Rewrite CDN URLs
    if (href.startsWith("https://old-cdn.com")) {
      href = href.replace("https://old-cdn.com", "https://new-cdn.com");
    }
    return { custom: `[${text}](${href})` };
  },

  visitImage(ctx: NodeContext, src: string, alt?: string, title?: string): VisitResult {
    // Skip tracking pixels
    return src.includes("tracking") ? "skip" : "continue";
  },
};

const html = '<a href="https://old-cdn.com/file.pdf">Download</a>';
const result = convert(html, { visitor });
const markdown = result.content;
```

## Links

- **GitHub:** [github.com/xberg-io/html-to-markdown](https://github.com/xberg-io/html-to-markdown)
- **npm:** [npmjs.com/@xberg-io/html-to-markdown](https://www.npmjs.com/package/@xberg-io/html-to-markdown)
- **WASM:** [npmjs.com/@xberg-io/html-to-markdown-wasm](https://www.npmjs.com/package/@xberg-io/html-to-markdown-wasm)
- **Discord:** [discord.gg/xt9WY3GnKR](https://discord.gg/xt9WY3GnKR)

## Part of Xberg.io

- [Xberg](https://github.com/xberg-io/xberg) — the open-source content-intelligence engine: text, tables, and metadata from 101 formats (115 file extensions), with OCR, transcription, and code intelligence. MIT.
- [Xberg Pro](https://xberg.io) — a complete self-hosted content-intelligence backend in a single container. Commercial.
- [Xberg Enterprise](https://xberg.io) — the distributed, governed content-intelligence platform, scaled on Kubernetes with team governance and support. Commercial.
- [crawlberg](https://github.com/xberg-io/crawlberg) — web crawling and scraping with HTML→Markdown and headless-Chrome fallback.
- [html-to-markdown](https://github.com/xberg-io/html-to-markdown) — fast, lossless HTML→Markdown engine.
- [liter-llm](https://github.com/xberg-io/liter-llm) — universal LLM API client with native bindings for 14 languages and 165 providers.
- [tree-sitter-language-pack](https://github.com/xberg-io/tree-sitter-language-pack) — tree-sitter grammars and code-intelligence primitives.
- [alef](https://github.com/xberg-io/alef) — the polyglot binding generator that produces every per-language binding across the 5 polyglot repos.

## Contributing

We welcome contributions! Please see our [Contributing Guide](https://github.com/xberg-io/html-to-markdown/blob/main/CONTRIBUTING.md) for details on:

- Setting up the development environment
- Running tests locally
- Submitting pull requests
- Reporting issues

All contributions must follow our code quality standards (enforced via pre-commit hooks):

- Proper test coverage (Rust 95%+, language bindings 80%+)
- Formatting and linting checks
- Documentation for public APIs

## License

MIT License – see [LICENSE](https://github.com/xberg-io/html-to-markdown/blob/main/LICENSE). Copyright © Kreuzberg, Inc.

## Support

If you find this library useful, consider [sponsoring the project](https://github.com/sponsors/xberg-io).

Have questions or run into issues? We're here to help:

- **GitHub Issues:** [github.com/xberg-io/html-to-markdown/issues](https://github.com/xberg-io/html-to-markdown/issues)
- **Discord Community:** [discord.gg/xt9WY3GnKR](https://discord.gg/xt9WY3GnKR)
