Generate PDFs in Make (make.com) with the HTTP module
If you are following a tutorial and the fields on your screen do not match the ones being described, you are not doing anything wrong. Make ships two versions of the HTTP app, and almost everything written about generating PDFs describes the older one. This page uses the version you actually have.
The version problem, first, because it wastes the most time
Make now publishes the older HTTP app separately as HTTP (legacy), and describes the new one as version 4 — with simpler setup, keychain credential storage and native pagination. If you need the old behaviour, you can pick version 3 from the version dropdown on the module.
Several fields were renamed in the move. This is the whole reason tutorials stop matching your screen:
| What tutorials call it (v3) | What your screen says (v4) |
|---|---|
Body type | Body content type |
Request content | Body input method, then the content field |
Query string | Query parameters |
| Credentials typed into the module | Authentication type + Credentials (keychain) |
| Module Get a file | Module Download a File |
Parse response, Headers, URL and Method kept their names in both. Everything below is written against v4.
The configuration, field by field
1. The credential
In the HTTP > Make a request module, set Authentication type and store the key in Credentials rather than typing it into a header on the module itself. That is the practical reason to prefer v4: the key lives in the keychain instead of sitting in the scenario body, which matters the first time you export a blueprint or share the scenario with someone.
If you add the header manually instead, it is:
- Name:
Authorization - Value:
Bearer YOUR_KEY
2. The request
- URL:
https://emitforge.com/v1/pdf - Method: POST
- Body content type: JSON (application/json)
- Body input method: Raw / JSON string
- Parse response: Yes
{
"template": "<h1>Invoice {{1.number}}</h1><p>{{1.customer}}</p>",
"data": {
"number": "{{1.number}}",
"customer": "{{1.customer}}"
},
"format": "A4",
"margin": "18mm"
}
Do not add a Content-Type header by hand. Make sets it for you from the body content type you picked. Setting it twice is one of the most common sources of a request that fails for no visible reason.
Turn Parse response on. Without it the reply arrives as a string and every later module has to unpack it — which usually means adding a Parse JSON module, and that is one more credit on every single run.
3. The response
{
"id": "rnd_9e9f40b63d2e",
"url": "https://…/rnd_9e9f40b63d2e.pdf?token=…",
"bytes": 12152,
"units": 1,
"expires_at": "2026-08-28T16:38:15Z"
}
With Parse response on, {{2.url}} is available to every module downstream. If something further along needs the bytes rather than a link — an email attachment, an upload — add HTTP > Download a File pointed at that URL. That produces the binary the Gmail or Google Drive module expects.
What each approach costs you in credits
This is where Make differs from self-hosted n8n in a way worth planning around. Make bills in credits — the billing unit since August 2025 — and each module action that executes counts as one. The free plan includes up to 1,000 credits a month; Core starts at $9 for 10,000.
So the shape of your scenario, not just the price of the PDF service, decides what a document costs you:
| Scenario shape | Modules per document | Credits |
|---|---|---|
| Generate, keep the link (write to a sheet, post to Slack) | 1 | 1 |
| Generate, then download to attach to an email | 2 | 2 |
Generate without Parse response, then Parse JSON | 2 | 2 |
| Iterate line items, aggregate, then generate | Modules inside the loop run once per line | scales with rows |
The practical lesson: if you only need a link, do not download the file. Halving the modules halves the cost, and a signed URL is enough for a spreadsheet cell, a Slack message or a database row.
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 the rows get built before the request.
In Make the canonical route is Iterator → Text aggregator. Point the aggregator's Source Module at the Iterator, and set the row markup as the aggregated text:
<tr><td>{{2.description}}</td><td class="num">{{2.qty}}</td><td class="num">{{2.amount}}</td></tr>
Then inject the aggregated result 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. With the triple form escaping is on you — which is exactly why it is a different syntax.
Watch the credits on this one. Everything between the Iterator and the aggregator runs once per row. On an invoice with 40 line items that is a real number, on every run. If the data already arrives as one bundle from an API or a database query, building the row markup at the source and passing it through as a single field is dramatically cheaper than iterating it inside Make.
The free routes, honestly
You may not need a PDF service at all, and it would be strange for us to hide that.
Google Docs, then export
The Create a Document from a Template module copies a Google Doc and replaces {{Placeholder}} tags with your data; Google Drive then exports it as PDF. Cost beyond credits: nothing.
It is genuinely good for letters, contracts and text-heavy documents where the layout is fixed. It struggles where most invoicing work lives: tables whose row count changes per document, precise control over page breaks, and repeating a table header across pages. If your document is prose with a few merge fields, start here and stop reading.
The dedicated PDF apps
Make lists several as first-class apps — PDFMonkey, CraftMyPDF, APITemplate, PDF.co, PDF Generator API and Documint among them. Most pair a drag-and-drop or HTML template editor with a native Make module, so there is no HTTP configuration at all.
If you want a visual editor that a non-technical colleague can edit without touching HTML, one of those is a better fit than us, and we would rather say so than pretend otherwise. The trade is that your template lives in their account rather than in your repository.
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 does this for you by default.
A row gets cut in half at the page break
tr { break-inside: avoid; }. Same for any block you want kept whole, such as a totals box.
The render times out
Almost always an external resource: a font, a logo or a stylesheet on someone else's server. Every render waits for it, and that 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 scenario
100 renders a month, free, no card. Enough to wire it into a real scenario 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 Generating invoice PDFs
Worth saying plainly: this is new, run by one person, and it is one endpoint — no template editor, no async rendering, no SLA, and no native Make app, so you configure HTTP yourself. If you need those, the vendors above have them and we would rather point that out now than after you have built on it.
Questions
Why does the module look different from the tutorial I found?
Because Make ships two versions and most tutorials show the old one. Version 4 is current; the previous app is published as HTTP (legacy). Field names changed — Body type became Body content type, and Get a file became Download a File. You can select version 3 from the version dropdown if you need the old fields.
How many credits does one PDF cost?
One per module action that runs. Generating and keeping the link is one credit. Adding a download so you can attach the file makes it two. Loops multiply it — the modules inside run once per item.
Do I need to add a Content-Type header?
No. Make sets it from the body content type you chose. Adding it manually as well is a frequent cause of requests that fail without an obvious reason.
Can I do this without paying for a PDF service?
Yes — Google Docs Create a Document from a Template plus a Drive export costs nothing beyond credits. It is a good fit for text-heavy documents and a poor one for tables with a variable number of rows or strict page-break control.
Does the same request work in n8n and Zapier?
Yes — it is a plain HTTP POST. In n8n use the HTTP Request node, in Zapier use Webhooks by Zapier. Same body, same header.