Generate PDFs in Zapier: the paywall and the loop that breaks
Two things decide whether this works, and most tutorials mention neither. The first is that you cannot call any API from Zapier's free plan. The second is that the step nearly everyone reaches for to build invoice rows — Looping by Zapier — cannot do that job, and gets expensive while failing.
If you are on the free plan, stop here
Zapier's help centre publishes a plan availability table for Webhooks by Zapier: ticks against Professional, Team and Enterprise, and a cross against Free. The pricing page says the same in commercial terms, listing "Connections via webhooks" as a paid-tier feature. API by Zapier, the newer alternative, carries the same restriction.
There is no workaround. The free plan also caps you at a two-step workflow — one trigger, one action — so even a generate-then-deliver sequence does not fit, whatever tool you use.
The realistic floor for anything on this page is the Professional plan, from $19.99 a month billed annually ($29.99 monthly). If a lot of guides told you webhooks start on "Starter" at $19.99, that plan name has been retired — the $19.99 entry point is now Professional.
The mistake almost every invoice tutorial makes
You have a list of line items and you need them as table rows inside one HTML document. The obvious move is Looping by Zapier: loop the items, build a row each time, join them at the end.
That last step does not exist. Loop iterations run in parallel, as separate Zap runs, with no shared state. There is no variable that survives from one iteration to the next, so there is nothing to append your rows to. Zapier's own guidance points the same way: if you want a step to run only once, put it before the looping step.
The cost makes it worse. The loop step itself is free, but every action after it is charged once per iteration. A 40-line invoice does not cost you one task — it costs 40 for each action downstream of the loop. On Professional's 750 tasks a month, a handful of large invoices can eat the month. Loops are also capped at 500 iterations, cannot be nested, and you get one looping step per Zap.
Looping is not useless — it is the wrong tool here. It earns its place when you want a real side effect per item: one CRM record per row, one email per attendee. Use it when the repetition is the point, not when you are assembling one document.
The right way: one Code step, one task
Build the whole string in a single Code by Zapier step, action Run Javascript. It costs one task no matter how many rows there are, runs in a deterministic order, and has no 500-item ceiling.
const items = inputData.items.split('|');
const rowsHtml = items.map(line => {
const [desc, qty, amount] = line.split('~');
return `<tr>
<td>${desc}</td>
<td class="num">${qty}</td>
<td class="num">${amount}</td>
</tr>`;
}).join('');
// Return a plain object, not an array. An array makes every following
// step run once per element — which is the loop problem all over again.
return { rowsHtml };
Two details that cause real confusion:
- Input fields are set in the editor, under
Input Data, and read in code asinputData.yourKey. The names are case-sensitive. - Returning an array fans out. Return one object and the rest of the Zap runs once, which is what you want here.
Code by Zapier runs Node.js in a sandbox, with a documented ceiling of 6 MB of combined input and output — exceed it and you get Scripting payload too large. Building table rows is nowhere near that. You do not need npm packages for this, which is convenient, because packages require a paid plan and ESM syntax.
A genuine surprise: Code by Zapier is available on the free plan, with a documented rate limit of 10 requests per 60 seconds. Plenty of tutorials state the opposite. It does not rescue the free plan for this job — the two-step cap and the webhook paywall still block you — but the app itself is not the reason.
Without code, and where it stops
Formatter → Utilities → Line-item to Text joins a list into a string with a separator you choose. That is genuinely useful for a comma-separated list of names. It cannot wrap each item in per-column markup, so a real multi-column table still needs either pre-built line items or the Code step above.
Zapier lists this particular action as Professional and above. That sits oddly beside Formatter being a free, task-free utility elsewhere, so confirm it against your own account before planning around it.
Making the HTTP call
Add Webhooks by Zapier. The actions are POST, GET, PUT and Custom Request — the last described by Zapier itself as "very flexible but unforgiving".
Fields on the POST action:
| Field | What to put in it |
|---|---|
URL | https://emitforge.com/v1/pdf |
Payload Type | JSON. This is the one people miss. |
Data | Your key/value pairs: template, data fields, format |
Headers | Authorization → Bearer YOUR_KEY |
Basic Auth | Leave empty. See below. |
Payload Type left on Form when the API expects JSON is the single most common cause of a 400. The dropdown offers Form, JSON, XML and Raw, and it defaults in a way that catches people out.
Do not put a bearer token in the Basic Auth field. That field is documented for username-and-password style credentials, and it produces an Authorization: Basic header with the value base64-encoded — which the API rejects. Zapier documents Headers as the place to send specific headers; the Bearer convention itself comes from HTTP, not from Zapier's docs.
If you use Custom Request instead, note that Zapier does not parse or format the Data field: it is sent exactly as entered. A stray comma ships a malformed body with no warning.
API by Zapier, and why it does not rescue the free plan
There is a newer app, API by Zapier, with an API Request action covering GET, POST, PUT, PATCH and DELETE. Its credentials live in an app connection rather than in the Zap, which is the better security posture.
Two catches. It carries the same Professional-and-above restriction, so it is not a free-plan route. And it requires an app connection whose domain filter must match the URL, so it cannot call an arbitrary address. If you need to reach several unrelated domains from one Zap, Webhooks remains the right tool despite being the older one.
The 30-second ceiling
Zapier documents a 30-second limit on actions and searches, in the editor and in production alike. There is no longer-running step type to escape to. Any render you call from a Zap has to finish inside that window, including the network round trip.
For what it is worth on our side: the invoices we test with — 45 line items, two pages — come back in 1.3 to 4.1 seconds. Our own published timeout is 10 seconds, so the request fails on our end well before Zapier's ceiling is reached. If your document is far heavier than that, this constraint applies to every PDF vendor with a Zapier action, not just us.
Getting the finished PDF into Gmail or Drive
Zapier file fields accept either a mapped file or a URL — but the URL has to be publicly reachable and a direct download. Zapier's own test is to open it in a private window: it should download immediately, not show a login, a preview or a viewer page. Google Drive share links and Slack file URLs fail that test, which is a common source of "the attachment is empty".
We checked ours against that rule so you do not have to: the signed URL we return answers 200 with no authentication header, Content-Type: application/pdf, and the file bytes directly. You can map it straight into a file field.
Three more things worth knowing before you build:
- Watch which field you map. Some apps expose a metadata twin next to the real file field — an
Attachmentsand anAttachments File. The file field is the one with the file icon. - Several files into one field become a .zip. Rarely what you wanted.
- Do not send bytes inline. Webhook action payloads cap at 5 MB and return a 413 above that, so base64 is out for anything but a tiny file. Passing a URL sidesteps the limit entirely, because file content is fetched separately rather than counted against the payload. Design for files under 100 MB.
What this actually costs in tasks
The task model is more generous than it looks, in ways that change how you build:
- Triggers never consume a task. Neither do Filters or Paths.
- Steps that do not run are not charged — filtered out, or halted by an error.
- The built-in utilities are free: Formatter, Delay, Looping, Digest, Zapier Manager and Storage.
So the standard cost control is to filter immediately after the trigger, letting only qualifying events reach the billable steps. A Zap that generates and delivers one document typically costs two or three tasks: the Code step, the HTTP call, and the delivery action.
One trap: replaying a run recharges steps that had already succeeded. Replaying a busy day is not free.
When you should not use us
We are one endpoint that turns HTML into a file. That is the wrong shape for several common situations, and it is cheaper for you to find out here:
- You are on the free plan. Nothing here works. Either move to Professional, or pick a PDF app with a native Zapier integration and build a two-step Zap.
- We have no native Zapier app. You will configure Webhooks by hand. Vendors like PDFMonkey, Documint, APITemplate, CraftMyPDF and PDF.co are in the directory as apps, which is less work to wire up.
- You would rather not maintain HTML. Template-editor products exist precisely so a non-technical colleague can change the wording without touching markup. That is a real advantage and we do not have it.
- You need to do things to a PDF, not just make one. Merging, watermarking, filling forms, parsing — PDF.co and CraftMyPDF cover that ground. We only generate.
- Your source is already a Google Doc. Create Document From Template plus Drive's Export File needs no PDF vendor at all. It is three steps, so it still needs a paid plan, but the apps are free.
- Your render will not fit in 30 seconds. Then no Zapier action works, ours included.
We compare published prices, including the ones cheaper than us, in what an HTML-to-PDF API costs.
If the shape fits, try it
100 renders a month, free, no card. Enough to build the Zap and see it work end to end before any money is involved. Limits are published: 10-second timeout, 2 MB template, 10 requests per second, files deleted after 24 hours.
Plainly: you will pay Zapier for Professional before you pay us anything, you write the HTML yourself, there is no template editor and no SLA, and we have no native Zapier app — so the Webhooks step is manual work. Our free tier is the largest of the vendors we compared, but that is a small consolation if the points above are dealbreakers.
Questions
Can I do this on the free plan?
Not by calling an API. Zapier lists Webhooks by Zapier as Professional, Team and Enterprise only, with a cross against Free, and API by Zapier has the same restriction. The free plan is also limited to one trigger plus one action. The floor is Professional, from $19.99 a month billed annually.
Why can't I build my table rows with Looping?
Iterations run in parallel as separate Zap runs with no shared state, so there is no string to append to across them. And every action after the loop is charged once per iteration. Build the rows in one Code by Zapier step instead: one task, any number of rows.
Why does my request come back as a 400?
Check Payload Type first — if it is on Form and the API expects JSON, that alone explains it. Then check that your bearer token is in Headers rather than the Basic Auth field, which produces a Basic header instead.
How do I attach the PDF to an email?
Map the returned URL into the file field of the Gmail step. The URL must be public and a direct download; ours returns the PDF bytes with no authentication header, so it works as-is. Do not send the file as base64 in the webhook body — action payloads cap at 5 MB.
How current is this?
Plan names, prices and limits were read from Zapier's own help centre and pricing page on 1 September 2026. Zapier moves features between tiers and retires plan names — Starter is gone, for one — so check anything that matters against their pages before committing.