The other answers are great. But I thought I (the OP) ought to share what I do these days (a year or two after the question).

I use Sphinx and its Markdown extension. Do the following:

TL;DR: See Gist snippet.

Sphinx-markdown-builder

You need sphinx-markdown-builder python module.

 pip install sphinx sphinx-markdown-builder;

Run Sphinx

Not the autodoc, the apidoc!

sphinx-apidoc -o Sphinx-docs . sphinx-apidoc --full -A 'Matteo Ferla'; cd Sphinx-docs;

Configuration

Fix the conf.py file, by following the following or just lazily copy paste the echo command below.

Manual

First uncomment the lines. These are otherwise commented out.

import os
import sys
sys.path.insert(0, os.path.abspath('../'))

Note the change to ../

One weirdness is that the magic methods get ignored. To override this, add this anywhere:

def skip(app, what, name, obj, would_skip, options):
    if name in ( '__init__',):
        return False
    return would_skip
def setup(app):
    app.connect('autodoc-skip-member', skip)

A thing to note: The docstrings ought to be written in restructuredtext (RST). If they are in Markdown, you need to add a mod - see this. The two are similar, but different. For example, a single backquote is required for <code> in Markdown, while two are for RST. If in doubt, several blog posts discuss the merits of RST documentation over Markdown.

Typehinting

RST typehints (:type variable: List) are obsolete as proper typehinting def foo(variable: Optional[List[int]]=None) -> Dict[str,int]: has been introduced since 3.6. To make these work:

 pip install sphinx-autodoc-typehints

And add 'sphinx_autodoc_typehints' at the end of the extensions list. Note the package has hyphens while the module has underscores.

TL;DR

Copy paste this:

echo " import os
import sys
sys.path.insert(0,os.path.abspath('../'))
def skip(app, what, name, obj,would_skip, options):
    if name in ( '__init__',):
        return False
    return would_skip
def setup(app):
    app.connect('autodoc-skip-member', skip)
extensions.append('sphinx_autodoc_typehints')
 " >> conf.py;

Showtime

Then it is showtime.

make markdown;

Copy the files and clean however you fancy.

mv _build/markdown/* ../; rm -r Sphinx-docs;

Repeat Apidoc for new files

It should be noted that when new files are added, the apidoc command needs to be repeated. Nevertheless, I highly recommend generating documentation midway as I often realise I am doing something wrong when I see the docs.

But briefly, apidoc will add for each file a automodule command, so this could be added manually or even expanded:

.. automodule:: my_module
   :members:
   :inherited-members:
   :undoc-members:
   :show-inheritance:

There's also the commands autoclass, autofunction, autoexception, for specific cases. In the case of autoclass if the class inherits many base classes in separate files to rightfully keep filesizes under 250 lines, the property :inherited-members: is a nice addition to this —thus avoiding having to describe the private base classes.

Read the docs: the common way

It should be said that there's a trend to not have documentation in GitHub but in Read the docs. My guess is because:

  • avoids this docstrings-to-markdown business
  • some users get confused by GitHub
  • looks nicer
  • other do it

Despite this, it requires some set up due to the module requirements. In another SO post is a long list of pitfalls and tricks —briefly IMO users, such as myself, make three mistakes:

  1. missing modules or the target module
  2. forget to hard refresh the browser
  3. enabling the sphinx.ext.autodoc extension

However, if one has written markdown documentation in GitHub these can be imported too. Formerly, the m2r2 (a fix of the deprecated m2r) was a good solution, but the divergence of its dependency mistune, which would require it to be frozen at version 0.8.4 as opposed to being at 2.0.0, which breaks other sphinx modules, therefore a new split works best and better: sphinx-mdinclude. This is pip installed as sphinx-mdinclude but included as sphinx_mdinclude and allows md files to be read alongside rst files. So a simple workaround in the docs/source/config.py file is to copy the files from the project root to the folder of config.py One issue is that links may need to be checked, especially if files moved around or are relative to the base URL (slash prefixed), eg. Foo.

Answer from Matteo Ferla on Stack Overflow
Top answer
1 of 5
39

The other answers are great. But I thought I (the OP) ought to share what I do these days (a year or two after the question).

I use Sphinx and its Markdown extension. Do the following:

TL;DR: See Gist snippet.

Sphinx-markdown-builder

You need sphinx-markdown-builder python module.

 pip install sphinx sphinx-markdown-builder;

Run Sphinx

Not the autodoc, the apidoc!

sphinx-apidoc -o Sphinx-docs . sphinx-apidoc --full -A 'Matteo Ferla'; cd Sphinx-docs;

Configuration

Fix the conf.py file, by following the following or just lazily copy paste the echo command below.

Manual

First uncomment the lines. These are otherwise commented out.

import os
import sys
sys.path.insert(0, os.path.abspath('../'))

Note the change to ../

One weirdness is that the magic methods get ignored. To override this, add this anywhere:

def skip(app, what, name, obj, would_skip, options):
    if name in ( '__init__',):
        return False
    return would_skip
def setup(app):
    app.connect('autodoc-skip-member', skip)

A thing to note: The docstrings ought to be written in restructuredtext (RST). If they are in Markdown, you need to add a mod - see this. The two are similar, but different. For example, a single backquote is required for <code> in Markdown, while two are for RST. If in doubt, several blog posts discuss the merits of RST documentation over Markdown.

Typehinting

RST typehints (:type variable: List) are obsolete as proper typehinting def foo(variable: Optional[List[int]]=None) -> Dict[str,int]: has been introduced since 3.6. To make these work:

 pip install sphinx-autodoc-typehints

And add 'sphinx_autodoc_typehints' at the end of the extensions list. Note the package has hyphens while the module has underscores.

TL;DR

Copy paste this:

echo " import os
import sys
sys.path.insert(0,os.path.abspath('../'))
def skip(app, what, name, obj,would_skip, options):
    if name in ( '__init__',):
        return False
    return would_skip
def setup(app):
    app.connect('autodoc-skip-member', skip)
extensions.append('sphinx_autodoc_typehints')
 " >> conf.py;

Showtime

Then it is showtime.

make markdown;

Copy the files and clean however you fancy.

mv _build/markdown/* ../; rm -r Sphinx-docs;

Repeat Apidoc for new files

It should be noted that when new files are added, the apidoc command needs to be repeated. Nevertheless, I highly recommend generating documentation midway as I often realise I am doing something wrong when I see the docs.

But briefly, apidoc will add for each file a automodule command, so this could be added manually or even expanded:

.. automodule:: my_module
   :members:
   :inherited-members:
   :undoc-members:
   :show-inheritance:

There's also the commands autoclass, autofunction, autoexception, for specific cases. In the case of autoclass if the class inherits many base classes in separate files to rightfully keep filesizes under 250 lines, the property :inherited-members: is a nice addition to this —thus avoiding having to describe the private base classes.

Read the docs: the common way

It should be said that there's a trend to not have documentation in GitHub but in Read the docs. My guess is because:

  • avoids this docstrings-to-markdown business
  • some users get confused by GitHub
  • looks nicer
  • other do it

Despite this, it requires some set up due to the module requirements. In another SO post is a long list of pitfalls and tricks —briefly IMO users, such as myself, make three mistakes:

  1. missing modules or the target module
  2. forget to hard refresh the browser
  3. enabling the sphinx.ext.autodoc extension

However, if one has written markdown documentation in GitHub these can be imported too. Formerly, the m2r2 (a fix of the deprecated m2r) was a good solution, but the divergence of its dependency mistune, which would require it to be frozen at version 0.8.4 as opposed to being at 2.0.0, which breaks other sphinx modules, therefore a new split works best and better: sphinx-mdinclude. This is pip installed as sphinx-mdinclude but included as sphinx_mdinclude and allows md files to be read alongside rst files. So a simple workaround in the docs/source/config.py file is to copy the files from the project root to the folder of config.py One issue is that links may need to be checked, especially if files moved around or are relative to the base URL (slash prefixed), eg. Foo.

2 of 5
9

I've found pydoc-markdown quite easy to use. The first command will install the library and the second one will create a README from your module named MY_MODULE:

pip install pydoc-markdown
pydoc-markdown -m MY_MODULE -I $(pwd) > README.md
🌐
PyPI
pypi.org › project › pydoc-markdown
pydoc-markdown · PyPI
Pydoc-Markdown is a tool to create Python API documentation in Markdown format.
🌐
Medium
rob-blackbourn.medium.com › documenting-python-with-markdown-9dc52fb448a3
Documenting Python With Markdown. I have a love hate relationship with… | by Rob Blackbourn | Medium
February 3, 2020 - I’m using a docstring parser project from here. A couple of formats are supported, but the one I’ve settled on is the google docs style. ... def makeExtension(*args, **kwargs) -> Extension: """Make the extension This hook *function* gets picked up by the markdown processor when the extension is listed ```python output = markdown.markdown( content, extensions=[ "admonition", "codehilite", "jetblack_markdown.autodoc", ]) print(output) ``` Returns: Extension: The extension """ return AutodocExtension(*args, **kwargs)
🌐
Dsbowen
dsbowen.github.io › docstr-md
Docstring-Markdown
Let's convert it to markdown. from docstr_md.python import PySoup, compile_md from docstr_md.src_href import Github src_href = Github('https://github.com/dsbowen/docstr-md/blob/master') soup = PySoup(path='test.py', parser='sklearn', src_href=src_href) compile_md(soup, compiler='sklearn', outfile='test.md') You'll now have a test.md file in your current directory.
🌐
PyPI
pypi.org › project › python-docstring-markdown
python-docstring-markdown·PyPI
A Python module and CLI that walks a Python package/directory and outputs a Markdown file from all docstrings in the package.
      » pip install python-docstring-markdown
    
Published: Feb 24, 2025
Version: 0.3.1
🌐
PyPI
pypi.org › project › docstring-to-markdown
docstring-to-markdown · PyPI
On the fly conversion of Python docstrings to markdown
      » pip install docstring-to-markdown
    
Published: May 02, 2025
Version: 0.17
🌐
GitHub
github.com › coldfix › doc2md
GitHub - coldfix/doc2md: Extract python docstrings and save as markdown file [very lightweight and designed for my personal use case] · GitHub
Extract python docstrings and save as markdown file [very lightweight and designed for my personal use case] - coldfix/doc2md
Starred by 57 users
Forked by 24 users
Languages: Python
🌐
Real Python
realpython.com › python-project-documentation-with-mkdocs
Build Your Python Project Documentation With MkDocs – Real Python
July 9, 2026 - In this tutorial, you'll learn how to build professional documentation for a Python package using MkDocs and mkdocstrings. These tools allow you to generate nice-looking and modern documentation from Markdown files and, more importantly, from your code's docstrings.
Find elsewhere
🌐
GitHub
github.com › criccomini › python-docstring-markdown
GitHub - criccomini/python-docstring-markdown: Generates Markdown documentation from Python module dosctrings · GitHub
A Python module and CLI that walks a Python package/directory and outputs a Markdown file from all docstrings in the package.
Author: criccomini
🌐
GitHub
github.com › ml-tooling › lazydocs
GitHub - ml-tooling/lazydocs: 📖 Generate markdown API documentation from Google-style Python docstring. The lazy alternative to Sphinx.
📖 Generate markdown API documentation from Google-style Python docstring. The lazy alternative to Sphinx. - ml-tooling/lazydocs
Author: ml-tooling
🌐
Dsbowen
dsbowen.github.io › docstr-md › python › basic_use
Basic use - Docstring-Markdown
This class parses raw Python code for easy conversion to markdown. Create a python file with parseable docstrings.
🌐
GitHub
github.com › cmry › markdoc
GitHub - cmry/markdoc: Convert NumPy-styled Python docstring to Markdown. · GitHub
This piece of code can be used to convert NumPy-styled Python docstrings (example), such as those used in scikit-learn, to Markdown with minimum dependencies.
Starred by 13 users
Forked by 4 users
Languages: Python
🌐
GitHub
gist.github.com › rochacbruno › 1689c849f3ef54086772c410d77a82de
Using markdocs to Python documentation (markdown) - Idea - WIP · GitHub
August 25, 2020 - """! # this is a documentation written in markdown As it has only one `!` at the top, it is considered the module documentation I can include module documentation along the file and will be merged in to the top level documentation """ from foo import bar """!! # This is an object documentation, can be used for any object but most for functions and classes It is defined before the object and not on the `__doc__` docstring, as markdocs does not conflicts with it.
Top answer
1 of 4
28

Sphinx's Autodoc extension emits an event named autodoc-process-docstring every time it processes a doc-string. We can hook into that mechanism to convert the syntax from Markdown to reStructuredText.

Unfortunately, Recommonmark does not expose a Markdown-to-reST converter. It maps the parsed Markdown directly to a Docutils object, i.e., the same representation that Sphinx itself creates internally from reStructuredText.

Instead, I use Commonmark for the conversion in my projects. Because it's fast — much faster than Pandoc, for example. Speed is important as the conversion happens on the fly and handles each doc-string individually. Other than that, any Markdown-to-reST converter would do. M2R2 would be a third example. The downside of any of these is that they do not support Recommonmark's syntax extensions, such as cross-references to other parts of the documentation. Just the basic Markdown.

To plug in the Commonmark doc-string converter, make sure that package is installed (pip install commonmark) and add the following to Sphinx's configuration file conf.py:

import commonmark

def docstring(app, what, name, obj, options, lines):
    md  = '\n'.join(lines)
    ast = commonmark.Parser().parse(md)
    rst = commonmark.ReStructuredTextRenderer().render(ast)
    lines.clear()
    lines += rst.splitlines()

def setup(app):
    app.connect('autodoc-process-docstring', docstring)

Meanwhile, Recommonmark was deprecated in May 2021. The Sphinx extension MyST, a more feature-rich Markdown parser, is the replacement recommended by Sphinx and by Read-the-Docs. With MyST, one could use the same "hack" as above to get limited Markdown support. Though in February 2023, the extension Sphinx-Autodoc2 was published, which promises full (MyST-flavored) Markdown support in doc-strings, including cross-references.

A possible alternative to the approach outlined here is using MkDocs with the MkDocStrings plug-in, which would eliminate Sphinx and reStructuredText entirely from the process.

2 of 4
1

I had to extend the accepted answer by john-hen to allow multi-line descriptions of Args: entries to be considered a single parameter:

def docstring(app, what, name, obj, options, lines):
  wrapped = []
  literal = False
  for line in lines:
    if line.strip().startswith(r'```'):
      literal = not literal
    if not literal:
      line = ' '.join(x.rstrip() for x in line.split('\n'))
    indent = len(line) - len(line.lstrip())
    if indent and not literal:
      wrapped.append(' ' + line.lstrip())
    else:
      wrapped.append('\n' + line.strip())
  ast = commonmark.Parser().parse(''.join(wrapped))
  rst = commonmark.ReStructuredTextRenderer().render(ast)
  lines.clear()
  lines += rst.splitlines()

def setup(app):
  app.connect('autodoc-process-docstring', docstring)
🌐
Niklasrosenstein
niklasrosenstein.github.io › pydoc-markdown
Home - Pydoc Markdown
The Python version compatibility of the package you are looking to generate documentation for is irrelevant. Understands multiple documentation styles (Sphinx, Google, Pydoc-Markdown specific) and converts them to properly formatted Markdown · Can parse docstrings for variables thanks to docspec (#: block before or string literal after the statement)
🌐
Davide Nunes
davidenunes.com › mkgendocs
Python Autodocs with MkGenDocs - Davide Nunes
December 21, 2020 - mkgendocs is a Python package for automatically generating documentation pages in markdown from Python source files, by parsing Google-style docstring.
🌐
Pdoc3
pdoc3.github.io › pdoc
pdoc – Auto-generate API documentation for Python projects
Auto-generate API documentation for Python projects from docstrings in numpydoc, Google, or plain Markdown format.
🌐
piwheels
piwheels.org › project › python-docstring-markdown
piwheels - python-docstring-markdown
February 20, 2025 - The piwheels project page for python-docstring-markdown: Generates Markdown documentation from Python module dosctrings
🌐
Arch Linux
archlinux.org › packages › extra › any › python-docstring-to-markdown
Arch Linux - python-docstring-to-markdown 0.17-2 (any)
View the file list for python-docstring-to-markdown · View the soname list for python-docstring-to-markdown · Copyright © 2002-2026 Judd Vinet, Aaron Griffin and Levente Polyák. The Arch Linux name and logo are recognized trademarks. Some rights reserved.