Convert Markdown files to PDF with styles.
Installation
The easiest way to test md2pdf is to use uv:
$ uv tool install md2pdf[cli]
Nota bene: ensure, Weasyprint is fully functional before using md2pdf. You will find installation instructions in the project documentation: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation
Usage
As a CLI
Usage: md2pdf [OPTIONS]
Markdown to PDF conversion tool with styles… and templates!
╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────╮
│ --input -i PATH Markdown source file path (can be used multiple times). │
│ --output -o PATH PDF output file path (when a single md input is used). │
│ --css -c PATH Input CSS file. │
│ --extras -e TEXT Extra markdown extension to activate (cam be used multiple │
│ times). │
│ --config -C TEXT Markdown extensions configuration (as a JSON string). │
│ --workers -W INTEGER Number of parallel workers to start. [default: 4] │
│ --version -V Display program version. │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or │
│ customize the installation. │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
For example, try to generate the project documentation with:
$ md2pdf -i README.md
A README.pdf should have been generated.
Optionally, you may load an external style sheet:
$ md2pdf \
--css examples/custom-styles.css \
-i README.md
And activate markdown extensions from PyMdown:
$ md2pdf \
--css examples/custom-styles-with-pygments.css \
--extras 'pymdownx.emoji' \
-i README.md
Code blocks should be properly rendered when this extension is active.
As a library
If you have added md2pdf as a dependency for your python project, you can use
md2pdf in your code, like:
from md2pdf.core import md2pdf
md2pdf(pdf,
md=None,
raw=None,
css=None,
base_url=None,
extras=[],
context={"foo": 1}
)
Function arguments:
pdf: output PDF file pathraw: input markdown raw string content (can contain Jinja instructions)md: input markdown file path (can contain Jinja instructions)css: input styles path (CSS)base_url: absolute base path for markdown linked content (as images)extras: markdown extra extensions that should be activatedcontext: variables to inject to rendered Jinja template
With Docker
Considering docker is installed, pull the latest Debian-based image:
$ docker pull jmaupetit/md2pdf:latest
And try to run a smoke test with this image:
$ docker run --rm -t \
-v $PWD:/wrk \
-u "$(id -u):$(id -g)" \
-w /wrk \
jmaupetit/md2pdf:latest -i README.md
There is also a smaller Alpine-based image tagged
alpine. For a full list of available tags, check the project's DockerHub repository.
Use Jinja templates as input
Your input markdown file or raw content can include Jinja template tags, and context can be given in a frontmatter header:
---
groceries:
- name: apple
quantity: 4
- name: orange
quantity: 10
- name: banana
quantity: 6
---
# Groceries
| Item | Quantity |
| ---- | -------- |
{% for item in groceries -%}
| {{ item.name }} | {{ item.quantity }} |
{% endfor %}
Or directly as a md2pdf argument (see library usage).
You can test this example using:
$ md2pdf \
--css examples/gutenberg-modern.min.css \
-i examples/my-music.md.j2 \
-o examples/my-music.pdf
Contributing
Hacking
Clone this project first:
$ git clone git@github.com:jmaupetit/md2pdf.git
Install md2pdf along with its dependencies (using uv):
$ cd md2pdf
$ make bootstrap
Running the test suite
To run the test suite:
$ make test
Lint the code via:
$ make lint
Ease your life
If you are familiar with GNU Make, we also automate daily tasks using this lovely tool:
$ make help
License
md2pdf is released under the MIT License. See the bundled LICENSE file for
details.
Release files for md2pdf 3.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| md2pdf-3.1.1.tar.gz | 128.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| md2pdf-3.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 137.2 kB
Release files / md2pdf-3.1.1.tar.gz
| Download URL | md2pdf-3.1.1.tar.gz |
|---|---|
| Size | 128.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6772d02468c131ff315601564d60c55b8e7e5e084ea7224f8f4315958df32bcf
|
|
BLAKE2b-256 checksum How to use checksums |
8ebdc26f2b30c22466df118340faa9d08e0308feee17ca290ae3d831d5ddd12c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.
Transparency logRelease files / md2pdf-3.1.1-py3-none-any.whl
| Download URL | md2pdf-3.1.1-py3-none-any.whl |
|---|---|
| Size | 9.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1d91509954b10cb6de0d2f079aa312177a55a39e28816acbf2ac785cb498f84f
|
|
BLAKE2b-256 checksum How to use checksums |
c3652c782f72152f53a4c16c80ebeed195ffc41688b6ad2a67ae1b7185767dd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.
Transparency log