Usage examples#

Create a MarkdownReport, add content, and then render or save it.

Add common content#

from mdreport import MarkdownReport

report = (
    MarkdownReport()
    .title("Pipeline health")
    .heading("Summary")
    .text("All nightly jobs finished before 06:00.")
    .bullet_list(["12 jobs passed", "0 jobs failed"])
    .numbered_list(["Collect data", "Check data", "Publish report"])
    .code_block("select count(*) from jobs", language="sql")
)

Both list methods nest to any depth. Write a sublist as a list immediately after the item it hangs beneath.

report.bullet_list([
    "Infrastructure",
    ["Database", "Cache", ["Redis", "Memcached"]],
    "Application",
])

Use markdown for content such as a quote or an image.

report.markdown("> Numbers are provisional.")

Add a callout#

Use callout for a portable, titled block quote. Its message supports Markdown and template parameters.

from mdreport import CalloutKind

report.callout(
    "Numbers for **{{ period }}** are provisional.",
    kind=CalloutKind.WARNING,
    params={"period": "Q3"},
)

The kinds are NOTE, TIP, IMPORTANT, WARNING, and CAUTION. Pass title to replace the kind’s title.

Add figures#

Use figure for a numbered image with alternative text and an optional caption.

report = (
    MarkdownReport()
    .figure(
        "charts/revenue.png",
        alt_text="Revenue by region",
        caption="Quarterly **revenue** by region.",
    )
)

Captions receive Figure 1, Figure 2, and so on in document order during render.

By default, the image remains linked to its path or URL. Set is_embedded=True to make a local image part of the Markdown document:

report.figure(
    "charts/revenue.png",
    alt_text="Revenue by region",
    is_embedded=True,
)

Raster images become base64 data URLs. SVG files are copied into the document as inline markup, inside an accessible wrapper carrying the alternative text. Embedding reads local files only; it does not download a remote URL. Only embed trusted SVG files because their markup is preserved. Some hosted Markdown viewers disallow data URLs even though the generated document is self-contained.

Add a DataFrame#

Use table for a Markdown table. Use csv when readers need to copy the data.

import polars as pl

metrics = pl.DataFrame({
    "region": ["EMEA", "APAC"],
    "revenue": [1234.567, 890.1],
})

report.table(metrics, title="Revenue by region", decimal_places=1)
report.csv(metrics, title="Revenue data")

Both methods include every row and column. Slice a large DataFrame before you add it.

Add template values#

Pass a params mapping to fill Jinja variables.

report.text(
    "Generated on {{date}} by {{author}}.",
    params={"date": "2026-08-28", "author": "Asif"},
)

Templating runs only when you pass params. Leave it out when the content contains literal Jinja braces.

Add frontmatter#

Use frontmatter to add YAML metadata to the top of the report.

report.frontmatter(title="Q3 review", author="Asif")
report.frontmatter({"table-of-contents": True})

Later calls can add fields or replace their values.

Add a table of contents#

Place table_of_contents where you want the list. It includes headings that you add later.

report = (
    MarkdownReport()
    .title("Q3 review")
    .table_of_contents()
    .heading("Revenue")
    .heading("Costs")
)

Entries link to each heading’s anchor, so the list reads as - [Revenue](#revenue). Narrow the list with start_level and depthtable_of_contents(start_level=2, depth=2) covers level 2 and level 3 headings, skipping the title.

Render or save#

markdown = report.render()
print(report)
report.save("reports/q3.md")

render returns a string. save writes the same Markdown to a UTF-8 file.