Skip to content

How to Format Python Code Blocks in Markdown Files

Ruff 0.16.0 formats Python code blocks inside Markdown files, so the samples in a README or a docs page follow the same style as the rest of the project.

This happens by default. ruff format . now discovers .md files alongside .py files, which means the first format run after upgrading to 0.16 can rewrite documentation that has never been formatted before. Ruff 0.15 could format a Markdown file named explicitly under --preview, but never picked .md up when scanning a directory.

Format a Markdown File

Pass the file to ruff format the same way as a Python file:

uv run ruff format README.md

Ruff reaches inside the recognized fences and reports 1 file reformatted. Prose, headings, and non-Python blocks are untouched, so this:

```python
x = {  "a":1,   "b":2 }
```

becomes x = {"a": 1, "b": 2}, still inside its fence.

Formatting a whole tree picks up Markdown automatically, so this reformats .py and .md together:

uv run ruff format .

The line-length setting in pyproject.toml applies to the code inside a block.

Which Code Fences Does Ruff Format?

Ruff matches on the fence’s info string.

Info string Formatted as
python, py, python3, py3 Regular Python source
pyi Type stub
pycon REPL session, including >>> prompts

Quarto’s curly form of any of those tags matches too, so {python} and {pycon} are formatted. MyST {code-cell} fences are not. Attributes after the tag still match, so python title="example.py" works, as do ~~~ fences.

Everything else is left alone, including fences with no info string and tags such as bash, console, and ipython. An unrecognized tag is skipped whether or not the code inside it is valid Python.

Note

Ruff also skips any block it cannot parse or cannot safely reformat, reporting the file as unchanged and exiting 0 with no warning. An unchanged block is not proof that it was already formatted. A >>> session inside a python fence is the case most people hit; tag it pycon to have it formatted.

Preview Changes Before Writing Them

Use --diff to print a unified diff without touching the file, or --check to fail without writing:

uv run ruff format --diff README.md
uv run ruff format --check .

Both exit 1 when a file needs reformatting, which makes either one usable as a CI gate. In 0.16, --check prints an annotated diff for each file that would change.

Warning

ruff check does not lint Markdown. Lint rules apply only to .py files; the formatter is the only half that reads your code blocks. A green ruff check therefore says nothing about the code in your docs, and in a project that has .py files there is no warning to tip you off.

Exclude Markdown from a Format Run

To keep Markdown out of Ruff entirely, exclude it in pyproject.toml:

[tool.ruff]
extend-exclude = ["*.md"]

To scope the exclusion to the formatter and leave the rest of the configuration alone:

[tool.ruff.format]
exclude = ["*.md"]

Both are ignored for a path passed explicitly on the command line, so ruff format README.md rewrites the file even when it is excluded. Pass --force-exclude to close that gap. The ruff-format pre-commit hook already sets the flag.

Skip a Single Code Block

To keep formatting on and protect one block, wrap the fence in HTML comments:

<!-- fmt: off -->
```python
ROTATION = [1,  0,  0,
            0,  1,  0]
```
<!-- fmt: on -->

An off comment with no matching on covers the rest of the file. Ruff accepts <!-- blacken-docs:off --> and <!-- blacken-docs:on --> as equivalents, so a project moving off blacken-docs can leave those comments in place.

The # fmt: off and # fmt: skip pragma comments also work inside a code block, but they stay visible in the rendered sample. Reach for them when the suppression belongs to one statement. See how to disable the Ruff formatter for a block of code.

Format Markdown in pre-commit

The upstream ruff-format hook ships types_or: [python, pyi, jupyter], so pre-commit never hands it a Markdown file. Override types_or to add markdown:

.pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.16.0
    hooks:
      - id: ruff-format
        types_or: [python, pyi, jupyter, markdown]

Without the override the hook passes and Markdown drifts out of format.

Format .qmd and Other Extensions

Ruff discovers .md by default. Map any other extension to the Markdown parser with the extension setting:

[tool.ruff]
extension = { "qmd" = "markdown", "markdown" = "markdown" }

ruff format . then reformats report.qmd alongside everything else. The value must be markdown; passing md fails with unknown variant.

Learn More

Last updated on