python-markdownify

Convert HTML into clean, configurable Markdown from Python with fine-grained control over tags, escaping, and formatting.

Library
PyPI
v1.2.3
2,256 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
50 /100 Fair
Development Activity 12
Maintenance 32
Community 56
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
74 /100 Good
Architecture 74
Code Quality 78
Innovation 58
Learning Curve 85

Markdownify is a focused Python library that turns HTML into Markdown, exposing a simple markdownify() function alongside a subclassable MarkdownConverter class for anyone who needs more control than a one-shot conversion offers. It wraps BeautifulSoup for HTML parsing, then walks the resulting tree tag by tag, dispatching to a convert_<tagname> method for each element type, which makes the codebase easy to extend by simply overriding or adding methods on a subclass.

The library is opinionated about correctness at the edges: it ships options for heading style (ATX, ATX-closed, or underlined/Setext), bullet rotation across nested lists, strong/emphasis symbol choice, table header inference, and multiple escaping modes for characters that would otherwise be misread as Markdown syntax. This attention to escaping and edge-case handling is what distinguishes it from simpler HTML-stripping approaches, and it is why the project is a common dependency wherever HTML content (emails, CMS fields, scraped pages, LLM tool output) needs to become readable Markdown.

What You Get

  • A markdownify() function that converts an HTML string to Markdown in one call, with dozens of formatting options exposed as keyword arguments.
  • A MarkdownConverter base class with one convert_<tag> method per supported HTML element, so custom tag handling is a matter of overriding a single method in a subclass.
  • A markdownify CLI entry point for converting HTML files or piped stdin to Markdown from the shell, sharing the same option set as the Python API.
  • Configurable heading styles (ATX, ATX_CLOSED, UNDERLINED), bullet character rotation for nested lists, and a choice of * or _ for strong/emphasis symbols.
  • Multiple escaping modes (escape_asterisks, escape_underscores, escape_misc) that prevent converted text from accidentally producing unintended Markdown syntax.
  • Support for converting directly from a BeautifulSoup object via convert_soup(), avoiding a redundant parse when the caller already has a soup tree.

Common Use Cases

  • Converting HTML email bodies or CMS rich-text fields into Markdown for storage, diffing, or downstream rendering.
  • Feeding scraped or fetched web page HTML into an LLM pipeline as clean Markdown context instead of raw markup.
  • Building documentation tooling that needs to migrate HTML content (e.g. exported wiki pages) into a Markdown-based static site generator.
  • Writing custom exporters that need selective control over which HTML tags are converted versus stripped entirely.

Under The Hood

Architecture

Markdownify is a small, single-module library centered on MarkdownConverter in markdownify/__init__.py. It parses input HTML with BeautifulSoup, then recursively walks the resulting tag tree, converting each node’s children first and passing the accumulated text plus a parent_tags set into a per-tag convert_<tagname> method (heading levels 1-6 are unified through a convert_hN dispatcher via a regex on the method name). This dispatch-by-naming-convention pattern is the core extension point: subclasses override or add convert_* methods to change or add tag behavior without touching the traversal logic itself. A separate main.py module wraps the same markdownify() entry point in an argparse CLI, keeping the library and CLI concerns cleanly split.

Tech Stack

The library targets Python and depends on beautifulsoup4 for HTML parsing (with a pluggable underlying parser via bs4_options, defaulting to the stdlib html.parser) and six for legacy Python 2/3 compatibility helpers, reflecting the project’s long history dating to 2012. Packaging uses a modern pyproject.toml with setuptools and setuptools_scm for version derivation from git tags, and the project ships a py.typed marker plus a hand-written .pyi stub file so consumers get static type checking despite the implementation itself not being fully type-annotated.

Code Quality

Tests are organized by concern across dedicated files (test_basic.py, test_conversions.py, test_tables.py, test_lists.py, test_escaping.py, test_advanced.py, test_args.py, test_custom_converter.py) and run with pytest through tox, alongside flake8 linting and restructuredtext-lint for the README. A second CI job runs mypy and mypy --strict against the stub-covered surface, giving the project real type-checking coverage even though the runtime code favors plain Python over inline type hints. Error handling is minimal by design — this is a text-transformation library with few failure modes beyond malformed input, which BeautifulSoup already tolerates.

What Makes It Unique

What sets Markdownify apart from simpler HTML-to-text strippers is its systematic handling of Markdown’s own syntax collisions: dedicated regexes escape sequences that would otherwise be misread as headings, list markers, or emphasis when they appear in converted text (e.g. a line starting with digits and a period, or a run of # characters). Combined with the override-one-method extension model, this makes it a common building-block dependency for projects that need correct, customizable Markdown output rather than a quick-and-dirty text extraction.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers