How to generate PDFs from Airtable records (no code)
Airtable doesn't generate PDFs on its own — the built-in print/export view is fine for a one-off, but it can't apply a branded layout or run unattended. The fix is an Airtable automation that fires on a trigger (record created, updated, or a button click), calls a PDF API with the record's fields, and writes the resulting file back onto the record as an attachment. You can make that HTTP call directly from Airtable's "Run a script" action, or hand it off to a webhook step in Zapier, Make, or n8n if you'd rather not write JavaScript.
The pattern in one sentence
Trigger on the record, POST its fields to a PDF-generation API, get a file back, attach it to the record. Everything below is one implementation of that pattern using Airtable Automations and the PDF generation API; the field-mapping specifics live on the Airtable integration reference if you want the fuller version.
Step 1 — pick the trigger
Three triggers cover almost every use case:
- Record matches conditions — fires when a status field flips to "Approved" or similar. Good for invoices and contracts that need a paper trail the moment they're finalized.
- Record created — fires on new rows. Good for receipts or certificates generated as soon as the row lands.
- Button field clicked — a manual "Generate PDF" button on the record. Good when you want a human to trigger it deliberately, e.g. re-issuing a quote after edits.
Whichever trigger you pick, add the fields the PDF needs (customer name, line items, totals, dates) as input variables on the next step — that's how they get into the script.
Step 2 — call the API from a "Run a script" action
Airtable Automations don't ship a generic "send an HTTP request" action, so the direct route is the "Run a script" action, which does support fetch. Store the API key as an input variable on the script step rather than pasting it into the script body — Airtable has no separate secrets vault for automations, so treat the script editor as visible to anyone with edit access to the base.
// Airtable Automation → "Run a script" action
// Input variables configured on this step: recordId, customerName,
// invoiceNumber, lineItemsJson, total, apiKey
const config = input.config();
const res = await fetch("https://docweave.dev/api/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
source: {
type: "template",
templateId: "invoice-v1",
data: {
customerName: config.customerName,
invoiceNumber: config.invoiceNumber,
lineItems: JSON.parse(config.lineItemsJson),
total: config.total,
},
},
// Reuse the same key on retries so an automation re-run
// doesn't render (and bill for) a duplicate document.
idempotencyKey: `airtable-${config.recordId}-${config.invoiceNumber}`,
}),
});
const result = await res.json();
if (!result.ok) {
throw new Error(`Docweave render failed: ${result.error?.message ?? res.status}`);
}
output.set("pdfUrl", result.url);
The template itself — the HTML with {{customerName}}, {{lineItems}}, and so on — is created once, outside Airtable, and referenced by templateId. This is the same source type used for invoices and quotes: design the layout once, feed it different JSON on every run.
Step 3 — write the file back onto the record
Add an "Update record" action after the script step. Map its Attachment field (create one if you don't have it — call it "Invoice PDF" or similar) to the script's pdfUrl output variable. Airtable will fetch that URL and store the file as a real attachment on the record, visible in grid view and downloadable from there.
This only works because the API is returning a JSON body with a hosted url, not a raw byte stream — Airtable's attachment field needs something it can fetch itself. Save the Accept: application/pdf raw-bytes response for cases where your own code is downloading the file directly (see the test below), not for handing off to Airtable.
If you'd rather not write JavaScript
Skip the script entirely and let an automation platform own the HTTP call: an Airtable trigger fires, hands the record to Zapier (or Make, or n8n), which has a native HTTP step that POSTs to the same endpoint, then a final step writes the returned URL back to Airtable via its native "update record" action. It's an extra hop and an extra subscription, but it means the whole pipeline is drag-and-drop with no code at all.
Testing the template without touching Airtable
Before wiring anything into an automation, confirm the template renders correctly on its own:
curl https://docweave.dev/api/v1/pdf \
-H "Authorization: Bearer dw_live_your_key" \
-H "Content-Type: application/json" \
-H "Accept: application/pdf" \
-d '{
"source": {
"type": "template",
"templateId": "invoice-v1",
"data": { "customerName": "Acme Co", "total": "1,240.00" }
}
}' \
--output test.pdf
If you're testing from Node instead of curl, request the same header and write out the buffer:
const res = await fetch("https://docweave.dev/api/v1/pdf", {
method: "POST",
headers: {
Authorization: "Bearer dw_live_your_key",
"Content-Type": "application/json",
Accept: "application/pdf",
},
body: JSON.stringify({
source: { type: "template", templateId: "invoice-v1", data: { customerName: "Acme Co" } },
}),
});
const buf = Buffer.from(await res.arrayBuffer());
require("fs").writeFileSync("test.pdf", buf);
Open test.pdf, fix the template, repeat. Once it looks right, the automation is just plumbing.
Common pitfalls
- Forgetting idempotencyKey — an automation that retries on transient errors will otherwise render (and bill) the same document twice.
- Bulk imports — pasting 500 rows into Airtable fires the trigger 500 times at once; check your plan's concurrency before doing that (see /pricing).
- Attachment field expects a URL, not bytes — if you request
Accept: application/pdfin the script step by mistake, you'll get a binary blob Airtable's Update Record action can't consume. - Script step timeouts — very large templates (hundreds of line items) can push a script action close to its execution limit; keep the script itself thin and let the API do the rendering work.
Full field-by-field mapping guidance, including how to handle Airtable's linked records and multi-select fields inside a template, is on the Airtable integration page. General API reference — auth, error shapes, rate limits — is in the docs.
FAQ
Does Airtable have a native "send HTTP request" automation action?
No. Airtable Automations' built-in action list covers things like creating/updating records, sending email, and posting to Slack, but not a generic outbound HTTP call. The "Run a script" action supports fetch, so that's the direct route for calling a third-party API like Docweave from inside Airtable itself.
Can I let users trigger PDF generation manually instead of on every edit?
Yes — use a Button field type as the automation trigger instead of "record created" or "record matches conditions." That gives you a literal "Generate PDF" button on each row.
How do I stop the same record from generating duplicate PDFs?
Pass an idempotencyKey built from the record ID (and something that changes when the content changes, like an updated-at field). Docweave records one usage event per idempotency key, so a retried automation run won't render or bill twice.
Can the output be a branded document instead of a plain field dump?
Yes — use the template + JSON source type rather than raw HTML. You design the layout once (logo, fonts, table styling) with placeholders, then every automation run just supplies the record's data.
Related
Generate your first PDF in minutes.
Get an API key