# Automate PDF reports from your data

PDF report automation: query your data, build HTML with SVG or Chart.js charts, add page numbers, render to PDF on a schedule and email it.

Weekly KPI summaries, monthly client reports and end-of-day sales digests are usually the same layout with new numbers. Instead of copying charts into a document every week, build the report as HTML from your data. A scheduled job then turns it into a PDF.

**Quick answer.** Build the report as HTML, then POST it to `https://html-pdf.com/v1/pdf` from a scheduled job and save or email the result.

```bash
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \
  -d '{"html":"<h1>Weekly sales report</h1><p>Revenue: $4,210</p>"}' -o weekly-report.pdf
```

## The pipeline

These are the five steps every automated report goes through.

1. **Query** your database, warehouse or analytics API.
2. **Build HTML**: tables for numbers, SVG or Chart.js for charts.
3. **Render** with `POST https://html-pdf.com/v1/pdf`.
4. **Deliver**: email the PDF, upload it to storage, or post the link to a chat channel.
5. **Schedule** the whole thing with cron, GitHub Actions or a workflow tool such as n8n.

The free [report template](https://html-pdf.com/templates/report) gives you a starting layout with a title block, KPI cards and a table.

## Build the HTML from query results (Python)

This script turns a SQL query into a report with an SVG bar chart, a table and page numbers.

```python
import os, html, sqlite3, requests

db = sqlite3.connect("shop.db")
rows = db.execute("""
  SELECT date(created_at) AS day, COUNT(*) AS orders, SUM(total) AS revenue
  FROM orders WHERE created_at >= date('now', '-7 day')
  GROUP BY day ORDER BY day
""").fetchall()

max_rev = max((r[2] for r in rows), default=0) or 1
bars = "".join(
    f'<rect x="{i*60+10}" y="{150 - r[2]/max_rev*140:.0f}" width="40" height="{r[2]/max_rev*140:.0f}" fill="#2563eb"/>'
    f'<text x="{i*60+30}" y="168" font-size="10" text-anchor="middle">{html.escape(r[0][5:])}</text>'
    for i, r in enumerate(rows))
table = "".join(f"<tr><td>{html.escape(d)}</td><td>{o}</td><td>${rev:,.2f}</td></tr>" for d, o, rev in rows)

page = f"""<!doctype html><html><head><meta charset="utf-8"><style>
  body {{ font-family: system-ui, sans-serif; color: #111; }}
  table {{ width: 100%; border-collapse: collapse; }} td, th {{ padding: 6px; border-bottom: 1px solid #eee; text-align: left; }}
  thead {{ display: table-header-group; }} tr, figure {{ break-inside: avoid; }}
</style></head><body>
  <h1>Weekly sales report</h1>
  <figure><svg width="440" height="175">{bars}</svg></figure>
  <table><thead><tr><th>Day</th><th>Orders</th><th>Revenue</th></tr></thead><tbody>{table}</tbody></table>
</body></html>"""

r = requests.post("https://html-pdf.com/v1/pdf",
    headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
    json={
        "html": page,
        "format": "A4",
        "margin": {"top": "15mm", "right": "12mm", "bottom": "18mm", "left": "12mm"},
        "footer_html": '<div style="font-size:9px;width:100%;text-align:center;color:#888">'
                       'Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
        "max_pages": 20,
    }, timeout=60)
r.raise_for_status()
open("weekly-report.pdf", "wb").write(r.content)
```

The doubled braces in the CSS are Python f-string escaping. Server-side SVG is the most predictable option for charts. There is no JavaScript to wait for, and the output is sharp at any zoom.

## Charts with Chart.js

This section shows how to use Chart.js if your team already relies on it. Load it from a CDN, turn animations off, and tell the renderer when the chart is ready with `wait_for_expression`.

```html
<canvas id="c" width="700" height="300"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
  new Chart(document.getElementById("c"), {
    type: "line",
    data: { labels: ["Mon","Tue","Wed","Thu","Fri"], datasets: [{ label: "Revenue", data: [420, 510, 380, 640, 700] }] },
    options: { animation: false, responsive: false }
  });
  window.chartReady = true;
</script>
```

Send it with `"wait_for_expression": "window.chartReady === true"`. More detail in [Chart.js to PDF](https://html-pdf.com/chartjs-to-pdf).

## Page numbers and page breaks

This section keeps multi-page reports readable. `footer_html` uses Chrome's placeholders (`pageNumber`, `totalPages`) and needs a bottom margin to have room.

CSS `@page` margin boxes such as `@bottom-center { content: counter(page) }` also work in Chromium. Keep tables readable with a repeating `thead` and `break-inside: avoid` on rows and charts.

See [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers) and [page breaks](https://html-pdf.com/html-to-pdf-page-breaks).

## Schedule it

These examples run the report script automatically every Monday at 07:00.

**cron** on any server:

```
0 7 * * 1 cd /opt/reports && HTML_PDF_KEY=... python3 weekly_report.py
```

**GitHub Actions**:

```yaml
name: weekly-report
on:
  schedule:
    - cron: "0 7 * * 1"
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install requests
      - run: python weekly_report.py
        env:
          HTML_PDF_KEY: ${{ secrets.HTML_PDF_KEY }}
      - uses: actions/upload-artifact@v4
        with: { name: weekly-report, path: weekly-report.pdf }
```

Scheduled workflows run in UTC.

**n8n** uses a Schedule Trigger, a database node, a Code node that builds the HTML, then an HTTP Request node calling the API. See the [n8n guide](https://html-pdf.com/guides/n8n).

## Email the PDF

This section covers getting the report to people. Attach the file with your mail provider's SDK or SMTP.

If you'd rather send a link, request `"response": "url"`. The API returns a download URL that stays valid for 24 hours, which suits a "this week's report" email. For anything that must live longer, upload the binary to your own storage.

## Automate reports with html-pdf.com

This is what you get with the hosted API. html-pdf.com is a Chromium-based HTML and URL to PDF API with page-based pricing. You get 100 free credits, then from $0.005 per page, and purchased credits never expire.

A 4-page weekly report costs 4 credits. Requests can be up to 20 MB and render for up to 30 seconds, and your HTML is not stored.

With the [MCP server](https://html-pdf.com/pdf-mcp-server), an AI agent can produce the report straight from data you paste or query.

> *"Take this CSV of last month's sales by region, build a two-page PDF report with a bar chart per region, a summary table and page numbers in the footer, and give me the link."*

[Get an API key](https://html-pdf.com/login)

## FAQ

### How do I automate PDF reports from a database?

Query your data, build an HTML page with tables and SVG or Chart.js charts, and POST it to `https://html-pdf.com/v1/pdf`. Run the script on a schedule with cron, GitHub Actions or n8n.

### How do I include Chart.js charts in a PDF report?

Turn animations off and set a flag such as `window.chartReady = true` once the chart is drawn. Then send `"wait_for_expression": "window.chartReady === true"` so the renderer waits for it.

### How much does an automated PDF report cost?

1 credit = 1 PDF page, so a 4-page weekly report costs 4 credits. You get 100 free credits once, then packs from $10 for 1,000 pages to $250 for 50,000 pages.

### Can I email a link instead of attaching the PDF?

Yes. Request `"response": "url"` and the API returns a download link that lasts 24 hours. For longer storage, upload the PDF to your own storage.
