HTML to PDF in n8n with the HTTP Request node — no community node, works on Cloud

27 August 2026 · every parameter listed, nothing to install

Most guides for this start by telling you to install something. That advice quietly excludes every n8n Cloud user, because community nodes only install on self-hosted instances. This page uses the HTTP Request node that ships with n8n, so the workflow runs anywhere and anyone you share it with can import and run it.

We counted what people actually use

We pulled every workflow in the public n8n template library that produces a document or an image — invoices, reports, screenshots, certificates — and counted the nodes inside them.

What we countedResult
Workflows examined382
Node instances in them3,650
Community nodes among themZero
Most common node after the sticky noten8n-nodes-base.httpRequest

Not one shared workflow that generates a document depends on a community node. That makes sense once you say it out loud: a workflow is meant to be exported, imported and run by someone else, and a dependency someone has to install first breaks that. HTTP Request is the node that always survives the trip.

The configuration, parameter by parameter

1. The credential

Create it once and reuse it. In the HTTP Request node:

Bearer authentication is header authentication — n8n's own documentation says so. There is no separate bearer option to look for, and the key stays in the credential store rather than in the workflow JSON, which matters the moment you export it.

2. The request

{
  "template": "<h1>Invoice {{ $json.number }}</h1><p>{{ $json.customer }}</p>",
  "data": {
    "number": "{{ $json.number }}",
    "customer": "{{ $json.customer }}"
  },
  "format": "A4",
  "margin": "18mm"
}

Keep the HTML out of the expression when it grows. Inline templates become unreadable fast and n8n expressions inside long HTML are painful to debug. Put the template in a Set node — or in a Code node if you are building rows in a loop — and reference it as {{ $json.html }}. Your future self will be able to read it.

3. The response

You get JSON back with a signed URL:

{
  "id": "rnd_9e9f40b63d2e",
  "url": "https://…/rnd_9e9f40b63d2e.pdf?token=…",
  "bytes": 12152,
  "units": 1,
  "expires_at": "2026-08-28T16:38:15Z"
}

Feed {{ $json.url }} straight into Google Drive, S3, Gmail or Slack. If a node downstream needs the bytes rather than a link, add a second HTTP Request with Response Format: File pointed at that URL — that is what turns it into a binary item n8n can attach.

Building the rows of a table

Templates here substitute values; they do not loop. That is deliberate — a template engine that evaluates expressions is a remote code execution surface, and the template arrives from outside. So you build the rows where you already have a loop: in a Code node.

const rows = items.map(i => `
  <tr>
    <td>${i.json.description}</td>
    <td class="num">${i.json.qty}</td>
    <td class="num">${i.json.amount}</td>
  </tr>`).join('');

return [{ json: { rows_html: rows } }];

Then inject it with triple braces, which do not escape: {{{rows_html}}}. Single braces escape by default, so a customer named <script> renders as text instead of running. When you use the triple form, escaping is on you — which is the point of making it a different syntax.

Three failures worth knowing before they happen

The table header disappears after page one

Add thead { display: table-header-group; }. Chromium then repeats it on every page. Nothing else does this for you.

A row gets cut in half

tr { break-inside: avoid; }. Same for any block you want kept together.

The render times out

Almost always an external resource: a font, an image or a stylesheet on someone else's server. Every render waits for it, and the wait counts against the 10-second limit. Embed fonts and images as data URIs and the problem disappears — along with the risk of your invoice looking wrong because a CDN had an outage.

Try it inside a workflow

100 renders a month, free, no card. Enough to wire it into a real workflow and see whether it fits before any money is involved. Limits are published: 10-second timeout, 2 MB template, 10 requests per second, files deleted after 24 hours.

Get a key   Compare the three approaches

Worth saying plainly: this is new, run by one person, and it is one endpoint — no template editor, no async rendering, no SLA. If you need those, established vendors have them and we would rather point that out now.

Questions

Do I need to install anything in n8n?

No. HTTP Request ships with n8n on both Cloud and self-hosted. That is the whole reason to prefer this approach — the workflow stays portable.

How do I attach the PDF to an email?

Add an HTTP Request node after the first, pointed at the returned url, with Response Format set to File. That produces a binary item, which the Gmail or Send Email node can attach.

Where does the API key live?

In an n8n credential, not in the workflow. Credentials are not included when a workflow is exported, so you can share the workflow without leaking the key.

Does this work in Make and Zapier too?

Yes — it is a plain HTTP POST. In Make use the HTTP module, in Zapier use Webhooks by Zapier. Same body, same header.

Related guides