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.mdRuff 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:
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.