Library / SDK
matthewwithanm/python-markdownify avatar
matthewwithanm/python-markdownify

markdownify: HTML-to-Markdown conversion in Python with configurable heading and tag handling

Convert HTML to Markdown

2,254 stars210 forksPythonMIT

At a glance

What is it?
markdownify is an MIT-licensed Python package that parses HTML with BeautifulSoup4 and converts it to Markdown. Its pyproject.toml classifiers stop at Python 3.8 while the build-system requires setuptools>=61.2, which does not support Python 2, making the 2.x classifier entries internally inconsistent.
Who is it for?
markdownify fits when converting HTML to Markdown in a Python workflow, and the options for heading style, bullet characters, text wrapping and code block languages handle most practical cases. Skip it when subscript, superscript or table header inference must work without configuration; those defaults require explicit settings. six (>=1.15,<2) is a runtime dependency added to any install, regardless of Python version.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 103 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Conversion through a BeautifulSoup4 parse tree, with six and bs4 as runtime dependencies

Conversion starts with BeautifulSoup4. markdownify passes the HTML string to bs4, which builds a parse tree, and then traverses that tree to produce Markdown output.

2 runtime dependencies appear in pyproject.toml: beautifulsoup4>=4.9,<5 and six>=1.15,<2. Python 2.5, 2.6 and 2.7 appear in the classifiers alongside Python 3.6, 3.7 and 3.8. six, a Python 2 and 3 compatibility package, is present as a runtime requirement; its continued inclusion means any project installing markdownify also pulls in six.

BeautifulSoup4 can use the standard library html.parser or optional lxml and html5lib parsers if installed separately. bs4_options accepts a string or list for parser selection, or a dictionary for the full BeautifulSoup constructor kwargs. Parser behavior is configurable per call rather than locked in at library level.

pip install markdownify and a first conversion

On PyPI, the package is under the name markdownify. Installation is one command:

bash
pip install markdownify

After that, the basic usage imports markdownify aliased as md:

python
from markdownify import markdownify as md
md('<b>Yay</b> <a href="http://github.com">GitHub</a>')  # > '**Yay** [GitHub](http://github.com)'

A bold element and a link go in, and the inline comment in the README shows the expected output, with bold notation and link notation respectively. Passing strip=['a'] removes anchor tags while keeping their text content. Passing convert=['b'] processes only bold tags and lets anchor tags fall through as plain text. strip and convert cannot be combined in the same call.

strip and convert are mutually exclusive choices, not complementary filters

When most elements in the HTML should convert to Markdown except a specific few, strip is the simpler parameter: named tags become plain text with no notation. When only specific tags should produce Markdown output and everything else should pass through unchanged, convert is the correct choice.

Combining them in a single call is not supported. Under the strip option documentation, the README states 'This option can't be used with the convert option', and the same sentence appears in the convert section. What error the library raises when both are passed simultaneously is not documented.

For HTML with many elements where only a handful need Markdown conversion, convert produces a shorter parameter list. For pages where most tags should convert and a few should not, strip keeps the configuration smaller. Both paths require knowing the tag schema of the input HTML in advance.

heading_style defaults to UNDERLINED and 4 accepted values exist

heading_style controls how heading elements appear in the output. 4 values are accepted: ATX, ATX_CLOSED, SETEXT and UNDERLINED, where UNDERLINED is an alias for SETEXT. Default is UNDERLINED.

ATX style uses leading hash characters, one per heading level. ATX_CLOSED adds matching trailing hashes. SETEXT underlines h1 text with equals signs and h2 text with hyphens. SETEXT applies to h1 and h2 only; the README gives no description of what markdownify produces for h3 through h6 under SETEXT mode.

Most Markdown pipelines that accept ATX headings will handle SETEXT for h1 and h2 without trouble. When the output feeds a renderer or a tool that expects ATX specifically, setting heading_style='ATX' explicitly avoids a silent format mismatch from the UNDERLINED default.

Subscripts, superscripts and tables without headers require explicit configuration

Subscript and superscript text reaches the Markdown output as plain text by default. sub_symbol and sup_symbol both default to empty strings, because subscript and superscript notation is non-standard in Markdown, a reason the README names explicitly. Configuring sub_symbol to '~' and sup_symbol to '^' produces the patterns that some Markdown interpreters recognize for those element types.

When either symbol value starts with '<' and ends with '>', markdownify treats it as an HTML tag and inserts a closing version after the text. Setting sub_symbol to '<sub>' preserves raw HTML sub markup in the Markdown output rather than producing non-standard notation.

table_infer_header defaults to False. HTML tables without a thead element or th cells produce a Markdown table with an empty header row. Setting table_infer_header to True promotes the first body row to the header position. Images inside table cells default to alt text; adding 'td' to keep_inline_images_in preserves Markdown image syntax for those cells.

Python classifiers stop at 3.8 and the build requirement contradicts the 2.x entries

pyproject.toml classifiers list Python 2.5, 2.6, 2.7, 3.6, 3.7 and 3.8 as supported versions. Python versions beyond 3.8 do not appear, meaning the published metadata does not claim compatibility with the most common current development targets.

A contradiction sits in the same file. Build-system dependencies require setuptools>=61.2. Setuptools 61.x requires Python 3.7 or higher, which makes the Python 2.5, 2.6 and 2.7 classifier entries internally inconsistent with the build requirements. Those 2.x entries cannot be accurate for a build system that refuses to run under Python 2.

Default branch for this repository is develop rather than main or master. pyproject.toml lists a Download URL pointing to master, which does not match the default branch and may reference an older state of the repository.

MarkdownConverter subclasses and code_language_callback for fenced code blocks

Configuration options can be passed as kwargs to the markdownify function for one-off calls, or set in a nested Options class inside a MarkdownConverter subclass for reuse. Subclassing suits projects that apply the same configuration across many pages without repeating keyword arguments.

code_language_callback is the option that benefits most from a custom function. It receives a BeautifulSoup element representing a pre tag and returns the code language as a string, or None. An example from the README:

python
def callback(el):
    return el['class'][0] if el.has_attr('class') else None

Reading the first CSS class from the pre element as the language means a block with class='language-python' gets a fenced code block labelled python. Without this callback, all fenced blocks come out with an empty language label.

bs4_options accepts a string or list for parser selection, or a dictionary for full BeautifulSoup constructor kwargs. Encoding-specific HTML is handled by passing {'from_encoding': 'iso-8859-8'} as the bs4_options value, which routes the encoding argument directly to BeautifulSoup.

Editorial conclusion

markdownify fits when converting HTML to Markdown in a Python workflow, and the options for heading style, bullet characters, text wrapping and code block languages handle most practical cases. Skip it when subscript, superscript or table header inference must work without configuration; those defaults require explicit settings. six (>=1.15,<2) is a runtime dependency added to any install, regardless of Python version. Version 1.2.3 was last pushed on 2026-06-30. Before adopting on Python beyond 3.8, verify the package works with your runtime, since the pyproject.toml classifiers do not list those versions.

Frequently asked questions

What is markdownify?

markdownify is an MIT-licensed Python package that converts HTML to Markdown using BeautifulSoup4. Options cover heading style, tag filtering, bullet characters and code block language annotation.

How do I install markdownify?

pip install markdownify adds the package from PyPI under the name markdownify. Runtime dependencies beautifulsoup4>=4.9,<5 and six>=1.15,<2 are installed automatically.

How do I convert only specific HTML tags with markdownify?

convert accepts a list of tag names to convert to Markdown; everything else passes through as plain text. strip accepts a list of tags to drop entirely. The 2 options cannot be combined in the same call.

What Python versions does markdownify support?

pyproject.toml classifiers list Python 2.5, 2.6, 2.7, 3.6, 3.7 and 3.8. Python versions beyond 3.8 are not in the classifier list. The build system requires setuptools>=61.2, which contradicts the Python 2.x classifier entries since setuptools 61.x requires Python 3.7 or higher.

How does markdownify handle HTML tables?

Tables without a thead element or th cells produce an empty header row by default. Setting table_infer_header to True uses the first body row as the header row instead.

Official sources

  1. Issues
  2. License: MIT
  3. matthewwithanm/python-markdownify on GitHub
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/matthewwithanm-python-markdownify.svg)](https://hysenlabs.com/projects/matthewwithanm-python-markdownify)