# Markdown & HTML to PDF — TOC, Headers, Page Numbers (`tiger19935/paged-pdf-generator`) Actor

PDF generator: turn Markdown or HTML into print-ready PDFs with automatic table of contents, headers, footers and page numbers, A4/Letter, Cyrillic-ready fonts and PDF/A-3b. Runs offline, no browser needed. Pay only per rendered page.

- **URL**: https://apify.com/tiger19935/paged-pdf-generator.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.60 / 1,000 pdf page rendereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Markdown & HTML to PDF — TOC, Headers, Page Numbers

Turn **Markdown or HTML into print-ready, paginated PDFs** — with an automatic **table of contents that shows real page numbers**, **headers and footers** (`Page 3 of 12`), A4 / Letter / A5 / Legal pages, five built-in templates, **Cyrillic and Latin-extended fonts included**, and optional **PDF/A-3b with embedded files** (for example an XML invoice). It is a paged-media renderer (WeasyPrint), not a browser: it is fast, deterministic, makes **no outgoing requests** and runs **no JavaScript**. You pay only for pages of PDFs that were actually produced.

### Perfect for

- **Reports, proposals and contracts** generated from templates or by AI agents — write Markdown, get a PDF with page numbers and a TOC.
- **Invoices and archives** that need **PDF/A-3b** with an attached XML (ZUGFeRD / Factur-X / UBL style workflows — formatting and embedding only).
- **CVs and resumes**, **e-books and handouts**, **documentation exports**, **newsletters** — in English, Ukrainian, Polish, German, Portuguese and other languages written in Latin or Cyrillic.
- **Automation**: n8n, Make, Zapier, Google Sheets, MCP-enabled AI assistants — send text in, get a link to a PDF out.
- **Batches**: up to 1,000 documents per run, each up to 5 MB of source.

### What you get

| Feature | Details |
|---|---|
| Input formats | Markdown (tables, footnotes, raw HTML allowed) or full / partial HTML with your own CSS |
| Table of contents | `toc.enabled`; depth 1–4; clickable entries with dotted leaders and the **correct page numbers** |
| Headers / footers | Text with tokens `{page}` `{pages}` `{title}` `{date}`; default footer `Page {page} of {pages}` |
| Page setup | A4, Letter, A5, Legal; portrait or landscape; margins 5–50 mm |
| Templates | `report`, `article`, `contract`, `cv`, `plain` (see below) |
| Fonts | Noto Sans, Noto Serif and Noto Sans Mono — Latin, Latin Extended, Cyrillic, Cyrillic Extended — **embedded in every PDF** |
| PDF/A | `pdfVariant: "pdf/a-3b"` plus up to 5 attachments of up to 5 MB each |
| Safe by design | Documents cannot fetch anything: `http(s)://`, `file://`, `@import`, `<link>`, `<iframe>`, `<object>` are ignored. Inline `data:` images work |
| Output | One dataset record per document + the PDF in the run's key-value store (public link in `url`) |

### How to use it

1. Open the Actor, paste your text into **Documents** (JSON list; each item has `markdown` or `html`).
2. Pick a **template**, **page setup**, switch the **table of contents** on if you want one, adjust header/footer.
3. Click **Start**. Open the `url` of each record in the dataset to download the PDF.

#### Example input — a report with table of contents and a custom footer

```json
{
  "documents": [
    {
      "title": "Quarterly Report",
      "filename": "quarterly-report.pdf",
      "markdown": "# Quarterly Report\n\n## Highlights\n\n- Revenue grew 12 %\n\n## Next steps\n\n| Task | Owner |\n|---|---|\n| Plan Q4 | Alex |\n"
    }
  ],
  "template": "report",
  "page": { "size": "A4", "orientation": "portrait", "marginMm": 20 },
  "toc": { "enabled": true, "depth": 2, "title": "Contents" },
  "header": "{title}",
  "footer": "Confidential — page {page} of {pages}"
}
```

#### Example input — PDF/A-3b invoice with an XML attachment, Ukrainian text

```json
{
  "documents": [
    {
      "html": "<h1>Рахунок № 2026-001</h1><p>Сума до сплати: <b>1 234,50 ₴</b></p>",
      "filename": "invoice-2026-001.pdf",
      "attachments": [
        { "name": "invoice.xml", "contentBase64": "PEludm9pY2U+PElEPjIwMjYtMDAxPC9JRD48L0ludm9pY2U+", "mimeType": "application/xml", "description": "Machine-readable invoice" }
      ]
    }
  ],
  "template": "plain",
  "pdfVariant": "pdf/a-3b",
  "language": "uk",
  "footer": "Сторінка {page} з {pages}"
}
```

#### Example output (one dataset record)

```json
{
  "recordType": "pdf",
  "index": 0,
  "filename": "quarterly-report.pdf",
  "title": "Quarterly Report",
  "template": "report",
  "pdfVariant": "none",
  "pages": 3,
  "bytes": 41873,
  "url": "https://api.apify.com/v2/key-value-stores/abc123/records/pdf-7c26db334c6493fd.pdf",
  "kvKey": "pdf-7c26db334c6493fd.pdf",
  "status": "ok",
  "reason": null,
  "message": null,
  "warnings": [],
  "charged": { "event": "pdf-page", "units": 3 },
  "processedAt": "2026-10-02T18:00:00.000Z"
}
```

Run-level totals (documents, pages, charged events, failures by reason, warnings) are written to the key-value store record `OUTPUT`; a short Markdown list is in `RESULTS_MD`.

### Input reference

| Field | Meaning |
|---|---|
| `documents` | List of up to 1,000 documents. Each: **exactly one** of `markdown` / `html`; optional `title`, `filename`, `css`, `language`, `attachments`, and per-document `template`, `page`, `toc`, `header`, `footer` that override the run-level values |
| `template` | `report` (default), `article`, `contract`, `cv`, `plain` |
| `page` | `{size: A4 \| Letter \| A5 \| Legal, orientation: portrait \| landscape, marginMm: 5–50}` |
| `toc` | `{enabled, depth 1–4 (default 3), title (default "Contents")}` |
| `header`, `footer` | Text with tokens; `footer` defaults to `Page {page} of {pages}`; write `none` to disable |
| `language` | BCP-47 code (default `en`) — hyphenation and quotation marks |
| `pdfVariant` | `none` (default) or `pdf/a-3b` |
| `attachments` | Up to 5 files (≤ 5 MB each) embedded in every PDF; a document can carry its own list. Each: `name`, `contentBase64`, optional `mimeType` (otherwise guessed from the file name) and `description` |
| `maxPagesPerDocument` | 1–500 (default 300). Longer documents are rejected, free of charge |

#### Templates

| Template | Look |
|---|---|
| `report` | Sans-serif business report, accent headings, zebra tables, quoted callouts |
| `article` | Serif, justified, hyphenated long-form reading |
| `contract` | Formal serif, compact, centred title, bordered tables, signature blocks keep together |
| `cv` | Compact one-column resume with accent section rules |
| `plain` | Minimal styling — use with HTML that brings its own CSS |

All templates keep headings with the following paragraph, never split table rows, and repeat table headers on each page.

#### Header / footer tokens

| Token | Replaced by |
|---|---|
| `{page}` | Current page number |
| `{pages}` | Total number of pages |
| `{title}` | Document title (`title`, else the HTML `<title>`, else the first heading) |
| `{date}` | Generation date, `YYYY-MM-DD` (UTC) |

Anything else is printed literally and safely escaped — text cannot break out of the header or inject styles.

### Pricing

**$0.007 per page** of a PDF that was produced successfully (event `pdf-page`). Nothing else is charged. $0.007 is the base (Free-plan) rate; paid Apify plans get lower per-page rates automatically.

| Job | Pages | Cost |
|---|---|---|
| One-page invoice | 1 | $0.007 |
| 20-page report | 20 | **$0.14** |
| 100 invoices of 2 pages | 200 | $1.40 |
| 300-page manual | 300 | $2.10 |

Failed, rejected, duplicate and over-budget documents are **never charged**. If you set a **maximum cost per run**, the Actor never starts a document that its pages cannot pay for and stops with a clear note in `OUTPUT`.

### When NOT to use it

- **Your page needs JavaScript** (charts drawn by scripts, single-page apps, client-side rendering). No script is executed. Pre-render charts as SVG or PNG (`data:` URLs work).
- **You want a screenshot of a live web page or a URL.** This Actor does not download anything; it converts the text you send. Use a browser-based screenshot Actor for that.
- **You need pixel-identical Chrome output.** The engine implements CSS Paged Media (page margin boxes, running headers, page counters), which browsers do not; some modern web-layout features (e.g. complex CSS Grid in older edge cases) can differ.
- **You need e-invoice validation, signing or delivery** (Peppol, KSeF, ZUGFeRD checkers). PDF/A-3b mode formats and embeds your file; it does not validate its content or send it anywhere.

### FAQ

**Are failures free?** Yes. `invalid_input`, `too_large`, `render_error`, `timeout`, `too_many_pages`, `budget` and `duplicate` produce a dataset record with the reason and cost nothing.

**Which fonts are used? Cyrillic?** Noto Sans / Serif / Sans Mono with Latin, Latin Extended and Cyrillic (including Ukrainian ґ є і ї). They are embedded in every PDF, so it looks the same everywhere. Your own CSS can use only these families (other fonts cannot be downloaded).

**Can I use my own images?** Yes, as `data:` URLs (inline base64). Remote images are not fetched.

**What happens with identical documents in one run?** The second one is marked `duplicate`, is not rendered again and is not charged.

**Where is my data stored?** Only in your own run storage (dataset and key-value store). Nothing is sent to third parties; documents are never allowed to load external resources (every URL except inline `data:` images and the bundled fonts is refused).

**How do I get the PDF file?** Open the `url` field of the record (public link to the key-value store record `pdf-….pdf`, content type `application/pdf`), or download from the Storage tab.

**Why does a document fail with `too_many_pages`?** It would produce more pages than `maxPagesPerDocument` (default 300, maximum 500). Raise the limit or split the document.

### Use it from code, n8n, Make or an AI assistant

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~paged-pdf-generator/run-sync-get-dataset-items" -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" -d '{"documents":[{"title":"Hello","markdown":"# Hello\n\nFirst PDF"}],"toc":{"enabled":false}}'
```

```js
const run = await client.actor('YOUR_USERNAME/paged-pdf-generator').call({ documents: [{ title: 'Notes', markdown: '# Notes\n\nText' }], footer: 'Page {page} of {pages}' });
```

- **n8n / Make:** HTTP Request or Apify module → send `documents` built from a previous step (CRM record, Google Doc text, form answer) → read `url` from the dataset → attach or email the PDF.
- **MCP / AI agents:** *"Use paged-pdf-generator to turn this Markdown into a contract PDF with a table of contents and footer 'Page {page} of {pages}', template contract, Letter size, and give me the link."* Every input field has a description the agent can read.

### Limits

Up to 1,000 documents per run, 5 MB of source per document, 120 s per document, 500 pages per document, 5 attachments of 5 MB. Documents are rendered one at a time with less than 2 GB of run memory and two at a time with 2 GB or more (always one at a time when you set a maximum cost per run). Very large or complicated documents need more memory: if one does not fit, it fails with `render_error` (not charged) and the other documents continue - start the run with more memory or split the document.

### More tools by the same author

- [PDF Text and Table Extractor](https://apify.com/tiger19935/pdf-text-table-extractor): Text and tables from PDFs, with OCR
- [Website Screenshot API](https://apify.com/tiger19935/website-screenshot-api): Full-page, mobile and WebP screenshots

# Changelog

This Actor's version history is a separate document: https://apify.com/tiger19935/paged-pdf-generator/changelog.md

# Actor input Schema

## `documents` (type: `array`):

List of documents to convert (max 1000, each max 5 MB). Every item is an object with exactly one of "markdown" or "html", and optionally "title", "filename", "css" (extra stylesheet), "language", "attachments" and per-document overrides of "template", "page", "toc", "header" and "footer". Example: \[{"title": "Report", "markdown": "# Hello\n\nText"}]

## `template` (type: `string`):

Built-in design. report = business report (sans-serif, accent headings), article = long-form reading (serif, justified), contract = formal legal text, cv = compact resume, plain = minimal styling for HTML that brings its own CSS. Can be overridden per document.

## `page` (type: `object`):

Page size and margins: {"size": "A4" | "Letter" | "A5" | "Legal", "orientation": "portrait" | "landscape", "marginMm": 5-50}. Missing keys use the defaults. Can be overridden per document.

## `toc` (type: `object`):

Automatic table of contents built from the headings: {"enabled": true/false, "depth": 1-4 (heading levels, default 3), "title": "Contents"}. Entries are clickable links and show the real page numbers. Can be overridden per document.

## `header` (type: `string`):

Text printed at the top centre of every page. Tokens: {page} current page, {pages} total pages, {title} document title, {date} generation date (YYYY-MM-DD). Empty = no header. Can be overridden per document.

## `footer` (type: `string`):

Text printed at the bottom centre of every page. Tokens: {page}, {pages}, {title}, {date}. Use the single word none for no footer. Can be overridden per document.

## `language` (type: `string`):

BCP-47 language code of the documents (en, uk, de, pt-PT ...). Used for hyphenation and quotation marks. Default en.

## `pdfVariant` (type: `string`):

none = regular PDF. pdf/a-3b = archival PDF/A-3b, which can carry attached files (for example an XML invoice). The Actor only formats and embeds - it does not validate invoices or deliver them.

## `attachments` (type: `array`):

Files to embed in every PDF (max 5, each max 5 MB), as \[{"name": "invoice.xml", "contentBase64": "...", "mimeType": "application/xml", "description": "..."}]. Mainly used with PDF/A-3b. A document can carry its own "attachments" list instead.

## `maxPagesPerDocument` (type: `integer`):

Safety limit: a document that would produce more pages is rejected (not charged). Between 1 and 500.

## Actor input object example

```json
{
  "documents": [
    {
      "title": "Quarterly Report",
      "filename": "quarterly-report.pdf",
      "markdown": "# Quarterly Report\n\nThis PDF was generated from Markdown.\n\n## Highlights\n\n- Revenue grew 12 %\n- Support tickets down 8 %\n\n## Next steps\n\n| Task | Owner |\n|---|---|\n| Plan Q4 | Alex |\n| Hiring | Sam |\n"
    }
  ],
  "template": "report",
  "page": {
    "size": "A4",
    "orientation": "portrait",
    "marginMm": 20
  },
  "toc": {
    "enabled": false,
    "depth": 3,
    "title": "Contents"
  },
  "footer": "Page {page} of {pages}",
  "language": "en",
  "pdfVariant": "none",
  "attachments": [],
  "maxPagesPerDocument": 300
}
```

# Actor output Schema

## `documents` (type: `string`):

One record per input document with the PDF link, page count and status.

## `summary` (type: `string`):

Counts, total pages, charged events, failures by reason and warnings.

## `report` (type: `string`):

Markdown list of the produced PDFs.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "documents": [
        {
            "title": "Quarterly Report",
            "filename": "quarterly-report.pdf",
            "markdown": "# Quarterly Report\n\nThis PDF was generated from Markdown.\n\n## Highlights\n\n- Revenue grew 12 %\n- Support tickets down 8 %\n\n## Next steps\n\n| Task | Owner |\n|---|---|\n| Plan Q4 | Alex |\n| Hiring | Sam |\n"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/paged-pdf-generator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "documents": [{
            "title": "Quarterly Report",
            "filename": "quarterly-report.pdf",
            "markdown": """# Quarterly Report

This PDF was generated from Markdown.

## Highlights

- Revenue grew 12 %
- Support tickets down 8 %

## Next steps

| Task | Owner |
|---|---|
| Plan Q4 | Alex |
| Hiring | Sam |
""",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/paged-pdf-generator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "documents": [
    {
      "title": "Quarterly Report",
      "filename": "quarterly-report.pdf",
      "markdown": "# Quarterly Report\\n\\nThis PDF was generated from Markdown.\\n\\n## Highlights\\n\\n- Revenue grew 12 %\\n- Support tickets down 8 %\\n\\n## Next steps\\n\\n| Task | Owner |\\n|---|---|\\n| Plan Q4 | Alex |\\n| Hiring | Sam |\\n"
    }
  ]
}' |
apify call tiger19935/paged-pdf-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/paged-pdf-generator"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/15Zn0p7wKQ8cLA7b3/builds/cPvj5xhzJL2DMSGvb/openapi.json
