Guide

Generate a report PDF in n8n

A form comes in, a week closes, a set of numbers needs sending — and someone wants a clean, branded PDF report, not a link to a dashboard. This is the honest, working version: a real n8n workflow that turns the data already moving through your workflow into a formatted report PDF and delivers it to Slack or email, with the copy-paste JSON and a step-by-step walkthrough. Reports are the recurring document, so we build both shapes — starting with how they differ.

Two shapes of a report workflow

A report isn’t quite like an invoice or a certificate. Those fire on a single event and go to one recipient; a report is usually assembled from data spread across your stack and sent on a cadence or to a channel. In n8n that lands as one of two shapes:

  • Event-driven — a form submission or a webhook arrives, and you render a report from it and post it somewhere. A submitted intake becomes a formatted PDF dropped in a Slack channel; a support form becomes an incident summary. This is the workflow embedded below.
  • Scheduled — a Schedule Trigger fires on a cadence (every Monday, month-end), you aggregate the period’s numbers from your sources, render the summary, and email it to the team. This is the variant covered near the end.

Both use the same Generate PDF node and the same Report template — only the front of the workflow (the trigger, and where the data comes from) changes. We’ll build the event-driven one end to end, then show the scheduled swap.

What you’ll need

  • An n8n instance that can install community nodes (self-hosted, or n8n Cloud on a plan that allows them).
  • The Generate PDF community node. In n8n: Settings → Community Nodes → Install, and enter the package name:
community node
n8n-nodes-generate-pdf

This is a community node with n8n verification in progress — not a verified node (yet). It installs like any community package and works today; verification only adds an in-editor badge later.

  • A pdfmill API credential. Create a key in the pdfmill dashboard (it looks like pk_live_…), add a credential of type pdfmill API, paste the key, and leave Base URL as https://api.pdfmill.dev. Click Test for a green check.

No headless Chrome, no server — pdfmill renders the document on its side and hands the binary back to your workflow. The full click-by-click is the five-minute quickstart.

The workflow, end to end

Here’s a complete, working report workflow. Copy it, and in n8n paste it straight onto the canvas (or Workflows → Import from File). It’s four nodes: manual trigger → Set (sample form data) → Generate PDF (Report) → Slack.

n8n workflow — copy, then paste onto the canvas
{
  "name": "pdfmill — Form to report PDF, posted to Slack",
  "nodes": [
    {
      "parameters": {},
      "id": "13-c776027f-25b6-4153-837d-c31bb6092299",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "{\n  \"kicker\": \"Monthly operations report\",\n  \"title\": \"Fulfillment Performance — July 2026\",\n  \"subtitle\": \"Order processing, shipping SLAs and automation coverage across all channels\",\n  \"periodStart\": \"2026-07-01\",\n  \"periodEnd\": \"2026-07-31\",\n  \"generatedAt\": \"2026-08-01\",\n  \"author\": \"Operations Automation Bot\",\n  \"summary\": \"July processed 4,812 orders with a 99.2% on-time dispatch rate. Automation now covers 87% of order flows end to end; the two remaining manual flows (B2B bulk and gift wrapping) are scheduled for August. Carrier exceptions fell 31% after the address-validation step was added to the intake workflow.\",\n  \"kpis\": [\n    {\n      \"label\": \"Orders processed\",\n      \"value\": \"4,812\",\n      \"delta\": \"+9.4%\"\n    },\n    {\n      \"label\": \"On-time dispatch\",\n      \"value\": \"99.2%\",\n      \"delta\": \"+0.6 pt\"\n    },\n    {\n      \"label\": \"Automation coverage\",\n      \"value\": \"87%\",\n      \"delta\": \"+5 pt\"\n    },\n    {\n      \"label\": \"Carrier exceptions\",\n      \"value\": \"142\",\n      \"delta\": \"−31%\",\n      \"down\": false\n    }\n  ],\n  \"sections\": [\n    {\n      \"heading\": \"Order intake\",\n      \"body\": \"Intake volume peaked in week 3 (1,401 orders) driven by the mid-summer promotion. The n8n intake workflow held p95 processing latency under 40 seconds throughout, including PDF confirmation generation and warehouse notification. No manual interventions were required for standard flows.\"\n    },\n    {\n      \"heading\": \"Shipping and carriers\",\n      \"body\": \"DHL handled 61% of shipments, UPS 27%, regional couriers 12%. The new address-validation step rejected 87 malformed addresses before label purchase, saving an estimated €610 in return fees. Two carrier API outages (July 9, July 22) were absorbed by the retry queue with zero lost shipments.\"\n    },\n    {\n      \"heading\": \"Exceptions and follow-ups\",\n      \"body\": \"142 carrier exceptions were raised, of which 129 auto-resolved through the exception workflow. The remaining 13 required human contact with the carrier. August focus: automate the B2B bulk flow and extend the exception workflow to regional couriers.\"\n    }\n  ],\n  \"table\": {\n    \"caption\": \"Key metrics vs June 2026\",\n    \"rows\": [\n      {\n        \"label\": \"Orders processed\",\n        \"current\": \"4,812\",\n        \"previous\": \"4,399\",\n        \"change\": \"+9.4%\",\n        \"direction\": \"up\"\n      },\n      {\n        \"label\": \"On-time dispatch rate\",\n        \"current\": \"99.2%\",\n        \"previous\": \"98.6%\",\n        \"change\": \"+0.6 pt\",\n        \"direction\": \"up\"\n      },\n      {\n        \"label\": \"Avg. dispatch time\",\n        \"current\": \"3.1 h\",\n        \"previous\": \"3.8 h\",\n        \"change\": \"−18%\",\n        \"direction\": \"up\"\n      },\n      {\n        \"label\": \"Carrier exceptions\",\n        \"current\": \"142\",\n        \"previous\": \"206\",\n        \"change\": \"−31%\",\n        \"direction\": \"up\"\n      },\n      {\n        \"label\": \"Manual interventions\",\n        \"current\": \"13\",\n        \"previous\": \"41\",\n        \"change\": \"−68%\",\n        \"direction\": \"up\"\n      },\n      {\n        \"label\": \"Return rate\",\n        \"current\": \"2.4%\",\n        \"previous\": \"2.2%\",\n        \"change\": \"+0.2 pt\",\n        \"direction\": \"down\"\n      }\n    ]\n  }\n}",
        "options": {}
      },
      "id": "14-40049ae8-4fc3-4ce4-9384-9dba930818e7",
      "name": "Form submission (sample)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "operation": "template",
        "template": "report",
        "data": "={{ $json }}",
        "format": "pdf",
        "binaryPropertyName": "data",
        "options": {}
      },
      "id": "15-78fe9b28-edb7-4802-8837-b374e614002b",
      "name": "Generate report PDF",
      "type": "n8n-nodes-generate-pdf.generatePdf",
      "typeVersion": 1,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "upload",
        "binaryPropertyName": "data",
        "options": {
          "fileName": "=Report - {{ $json.title }}.pdf"
        },
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#reports"
        }
      },
      "id": "16-7ead2163-c10e-40aa-ac0b-c88a70ff6e71",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "content": "## 📝 Form → Report PDF → Slack\nWhen a form/webhook fires, build a formatted PDF report and drop it into a Slack channel.\n\n**Setup:** a **pdfmill** credential on the Generate node and a **Slack** credential on the upload node.",
        "height": 200,
        "width": 560,
        "color": 5
      },
      "id": "17-9a22b4b5-9436-4de8-b6bc-0b5873e6009b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        40
      ]
    },
    {
      "parameters": {
        "content": "### In production\nSwap the manual trigger for a **Webhook** or **n8n Form Trigger** and map the submission into the report’s fields, KPIs and table.",
        "height": 200,
        "width": 300,
        "color": 4
      },
      "id": "18-8ca7e943-41b7-446b-bfac-eea2d2aa13f0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        220,
        520
      ]
    }
  ],
  "connections": {
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Form submission (sample)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Form submission (sample)": {
      "main": [
        [
          {
            "node": "Generate report PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate report PDF": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [
    {
      "name": "pdfmill"
    }
  ],
  "versionId": "66a2b9d1-3377-44f6-9e14-3ce166405b8d"
}

Walking it left to right:

  • When clicking ‘Execute workflow’ — a manual trigger, so you can run it once and see a real PDF. In production you swap this for a Form Trigger, a Webhook, or a Schedule (last two sections).
  • Form submission (sample) — a Set node holding a sample report payload as JSON (title and reporting period, a KPI strip, a summary, a few narrative sections, and a comparison table). This stands in for whatever your real trigger produces.
  • Generate report PDF — the pdfmill node. Operation: Generate from Template; Template: Report; Data: {{ $json }} (the incoming item); Format: PDF; Put Output File in Field: data. Out comes the report as a binary on data, plus a small JSON summary (pages, bytes, requestId, file name).
  • Post to Slack — a Slack node set to upload a file: it takes the data binary, names it Report - {{ $json.title }}.pdf, and posts it to the #reports channel. Swap Slack for Gmail, Google Drive, or S3 without touching the Generate step.

The two sticky notes in the JSON say the same thing on the canvas — one explains the flow and which credentials to add, the other how to swap the manual trigger for a Webhook or an n8n Form Trigger in production. Add your pdfmill credential to the Generate node and a Slack credential to the upload node, then Execute workflow — a rendered report lands in the channel.

The Report template and its data shape

Generate from Template means you supply data, not layout — the built-in Report starter handles the design (an accented cover, a KPI strip, a highlighted summary, narrative sections, and a comparison table). The Data field defaults to {{ $json }}, which passes the whole incoming item through; to feed specific fields instead, map them with an expression. The Report template reads these fields:

  • Coverkicker, title, subtitle, periodStart, periodEnd, generatedAt, author.
  • KPI stripkpis[], each with label, value, an optional delta, and down (set true when a fall is bad, to colour it red).
  • Summary & sections — a single summary paragraph up top, then sections[], each a heading and a body, for the narrative.
  • Comparison tabletable.caption plus table.rows[], each with label, current, previous, change, and direction (up/down) to colour the change.

One rendered report counts as one toward your plan, however many pages it runs to. Want a different look entirely — your own branding, a chart, a bespoke layout? That’s the next section.

A fully custom report layout (raw HTML)

The Report starter is the fast path, but a report is the document most likely to need your look — a logo, brand colours, a chart, a layout that matches the rest of your reporting. For that, switch the node’s Operation from Generate from Template to Generate from HTML and pass your own markup:

  • The HTML field takes any HTML and supports {{handlebars}} variables filled from the Data field — so {{title}}, {{#each kpis}}…{{/each}}, and the like resolve against your workflow data just as a template would.
  • Build the markup however you like — inline CSS, an embedded chart image (a base64 <img> from a charting step), explicit page breaks, headers and footers. pdfmill renders it in real Chrome, so modern CSS works.
  • Usually the HTML arrives as an expression that maps markup from an earlier node (a Code node or an HTML-template step), which keeps the layout out of the node itself.

Same data in, same PDF-or-PNG out, same binary on data — only the layout source changes. Template for speed, HTML for full control; the templates guide covers both, and the other starters (invoice, quote, report, certificate, packing slip) are there when you want a different document.

Delivering the report — Slack, email, Drive

Because the report comes out as a binary on data, anything downstream that takes a binary can send it. Point that property wherever the report needs to go:

  • Post to Slack — a Slack file upload (the workflow above does this), dropping the PDF into a channel for the team to see.
  • Email it — Gmail / SMTP / Outlook, with data as the attachment (the scheduled variant below does this).
  • Store it — Google Drive, S3, or Dropbox, uploading the data binary to an archive of past reports.

These are just different last nodes on the same flow — the Generate step never changes.

The scheduled variant — a weekly summary, emailed

The workflow above is event-driven: a form fires it. The other common report shape is scheduled — no form, just a cadence. A second gallery workflow, pdfmill — Scheduled weekly report PDF, emailed, is exactly that, and it reuses the same Generate step:

  • Every Monday 8am — a Schedule Trigger (weekly, Monday, 08:00) in place of the manual/form trigger.
  • Weekly metrics (sample) — the same Report shape, standing in for the numbers you’d gather; in production you replace it with your real sources.
  • Generate weekly report PDF — the identical Generate PDF node, Report template, PDF out.
  • Email the report — a Gmail node that attaches the data binary and titles the mail Weekly report — {{ $json.periodStart }} to {{ $json.periodEnd }}.

To make it real, swap the sample-data node for your sources — an HTTP Request to an analytics API, a Postgres query, a Google Sheets read — then map the results into the report’s KPIs, sections, and table (a Set or Code node does the shaping). Change the Schedule Trigger to your cadence — month-end, daily, or a cron expression. Everything from Generate onward is unchanged.

Going to production

For real use, the only thing that changes is the front of the workflow — the trigger and where the data comes from. Map your source into the same report shape the template expects:

  • n8n Form Trigger — turn a submitted form straight into a report PDF (the intake-to-report pattern).
  • Webhook — render a report when another system posts an event.
  • Schedule + a query (Postgres, Google Sheets, an API) — the recurring summary, aggregated and sent on a cadence.

Pulling from several sources? Fan them in with a Merge or a Code node before Generate, so the report’s KPIs, sections, and table are all populated from one item.

pdfmill is flat-priced: a permanent Free tier of 75 documents a month (no card), then $9/500, $19/2,000, and $49/10,000 per month. The plan is a hard cap — at the limit the node returns a clear QUOTA_EXCEEDED (402) with an upgrade prompt, never a silent overage or a surprise invoice of its own. Current numbers are on the pricing page. Still weighing options? See how to generate PDFs in n8n and the best PDF API for n8n.

FAQ

How do I create an automated PDF report in n8n?

Install the community node n8n-nodes-generate-pdf, add a pdfmill API credential, then drop a Generate PDF node into your workflow. Choose the Report template, leave Data as {{ $json }}, and set Format to PDF. Put the node after whatever produces your data — an n8n Form Trigger, a Webhook, or a Schedule Trigger with a query — and it returns the finished report as a binary on the output field (default "data"), ready to post to Slack, email, or save to Drive. n8n has no native PDF-generation node, so a community node (or a document API) is how you make one.

Can I post the report to Slack?

Yes. The Generate PDF node outputs the report as a binary on the "data" property; add a Slack node set to upload a file, point it at that binary, and pick the channel. The sample workflow on this page does exactly that — it renders the report and uploads Report - {{ $json.title }}.pdf to #reports. Swap Slack for Gmail, Google Drive, or S3 without touching the Generate step.

How do I schedule a weekly report PDF?

Start the workflow with a Schedule Trigger instead of a form or webhook. The scheduled gallery variant fires every Monday at 8am, takes the week’s metrics, renders the Report template to PDF, and emails it to the team. Set the Schedule Trigger to your cadence (weekly, monthly, or a cron expression), pull the numbers with an HTTP Request / Postgres / Google Sheets node, map them into the report’s fields, and send.

Can I use custom HTML for the report layout?

Yes. Switch the node’s Operation to Generate from HTML and pass your own markup instead of a template. The HTML supports {{handlebars}} variables filled from the Data field, so you can build a fully branded, custom report layout — your logo, your colours, sections and a chart image as HTML — and still map workflow data into it. Use the built-in Report template when you want a good layout for free; use raw HTML when you need full control.

Is the pdfmill n8n node verified?

It is a community node (n8n-nodes-generate-pdf) with n8n verification in progress — not yet verified. You install it via Settings → Community Nodes like any community package. Everything in this guide works today; verification only adds an in-editor badge and a listing on n8n.io.

The workflow above is the real gallery template that ships with the node — checked July 30, 2026. Options and prices change; this guide links the pricing and comparison pages rather than repeating numbers that go stale here.

See for yourself

Render your first PDF from an n8n workflow in about five minutes — the Free plan is 75 documents a month, no card required.