# Generate PDFs from Airtable records

Generate PDFs from Airtable records with an automation script that renders HTML to PDF and writes the link back, plus the limits of the native options.

Airtable is often the database behind quotes, invoices, certificates and work orders. Turning a record into a polished PDF is a frequent need, and Airtable's built-in tools only get you part of the way.

**Quick answer:** in an Airtable automation, add a **Run a script** action that posts the record's HTML to the API and saves the returned link.

```js
const res = await fetch("https://html-pdf.com/v1/pdf", {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify({ html, response: "url" }),
});
```

The full script is in Step 3 below.

## Native options and their limits

This section covers what Airtable can do on its own, and where it stops.

- **Page designer extension.** Lets you lay out fields visually and print or save as PDF. It's manual. Someone opens the extension and prints, one record or a batch at a time, and you can't trigger it from an automation.
- **Printing a record or view.** Fine for internal use, not for branded documents.
- **Marketplace extensions and integrations.** Some document-generation services connect to Airtable, usually with their own template editors and pricing.

If you want PDFs created automatically when a record changes, with your own HTML and CSS, use an automation script and an HTML-to-PDF API.

## How the automation works

Here is the whole flow in four steps.

1. A trigger fires (record enters a view, a checkbox is ticked, a status changes).
2. A **Run a script** action builds HTML from the record's fields.
3. The script sends it to `POST https://html-pdf.com/v1/pdf` with `"response": "url"`.
4. The API returns a link to the PDF, and the script writes it into a URL field (or an attachment field) on the record.

Automation scripts run on Airtable's servers and can call `fetch` directly. You only need `remoteFetchAsync` in the Scripting extension.

Each run has limits on execution time and the number of fetch requests (Airtable documents 50 per run). Process one record per run.

## Step 1: prepare the table

Start by adding two fields to hold the result.

Say you have an `Invoices` table with `Number`, `Customer`, `Amount` and `Status`. Add these two fields.

- `PDF` (URL field) for the link.
- `PDF file` (Attachment field, optional) if you want Airtable to keep a copy.

Get an API key at https://html-pdf.com/login (100 free credits, 1 credit = 1 PDF page).

## Step 2: create the automation

Next, set up the trigger and the script action.

1. **Automations → Create automation.**
2. Trigger: **When record matches conditions** (for example `Status` is `Ready`) or **When record enters view**.
3. Action: **Run a script**.
4. In the script's left panel, add an input variable `recordId` set to the trigger record's **Airtable record ID**.

The script below keeps the API key in a constant. Anyone who can edit the base's automations can see it, so use a dedicated key and rotate it if collaborators change.

## Step 3: the script

Paste this script into the action and replace the API key.

```js
const { recordId } = input.config();
const apiKey = "YOUR_API_KEY";

const table = base.getTable("Invoices");
const record = await table.selectRecordAsync(recordId);
if (!record) throw new Error("Record not found");

const esc = (v) => String(v ?? "").replace(/[&<>"]/g, (c) =>
  ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));

const number = record.getCellValueAsString("Number");
const customer = record.getCellValueAsString("Customer");
const amount = Number(record.getCellValue("Amount") || 0);

const html = `<!doctype html><html><head><meta charset="utf-8"><style>
  @page { size: A4; margin: 18mm; }
  body { font-family: system-ui, sans-serif; font-size: 12px; color: #111; }
  header { display: flex; justify-content: space-between; margin-bottom: 32px; }
  .total { font-size: 20px; margin-top: 24px; }
</style></head><body>
  <header><h1>Invoice ${esc(number)}</h1><strong>Your Company</strong></header>
  <p><strong>Bill to:</strong> ${esc(customer)}</p>
  <p class="total">Total: $${amount.toFixed(2)}</p>
</body></html>`;

const res = await fetch("https://html-pdf.com/v1/pdf", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html,
    prefer_css_page_size: true,
    print_background: true,
    filename: `invoice-${number}.pdf`,
    response: "url",
  }),
});

const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);

await table.updateRecordAsync(recordId, {
  "PDF": data.url,
  // Optional: let Airtable download and keep its own copy
  "PDF file": [{ url: data.url, filename: `invoice-${number}.pdf` }],
});

output.set("pdfUrl", data.url);
```

`getCellValueAsString` handles linked records, lookups and single selects, so you can pull data from related tables without extra queries.

### About the 24-hour link

With `"response": "url"` the link is valid for 24 hours. For a permanent copy, write it to an **attachment field** as shown. Airtable fetches the file from the URL and stores it.

If you only need to email the PDF right away, the URL field is enough. Add a **Send email** action after the script and use the `pdfUrl` output.

## Line items from linked records

This snippet turns linked line items into table rows for your invoice.

Read the linked IDs from the `Line items` field, query that table and build the rows.

```js
const links = record.getCellValue("Line items") || [];
const itemsTable = base.getTable("Line items");
const items = await itemsTable.selectRecordsAsync({ recordIds: links.map((l) => l.id), fields: ["Name", "Qty", "Price"] });
const rows = items.records.map((r) =>
  `<tr><td>${esc(r.getCellValueAsString("Name"))}</td><td>${r.getCellValue("Qty")}</td><td>${r.getCellValue("Price")}</td></tr>`).join("");
```

Then insert `rows` into a `<table>` in your template. A full invoice layout with repeated headers and page-break rules is at https://html-pdf.com/generate-invoice-pdf, and more designs are in https://html-pdf.com/templates.

## No-code alternative: Make or Zapier

You can build the same flow without writing a script.

Use an Airtable trigger in Make or Zapier, an HTTP step that calls the API, and an Airtable "update record" step that writes the URL back. Step-by-step guides are at https://html-pdf.com/guides/make and https://html-pdf.com/guides/zapier.

## Options and limits

These are the options you'll use most, and the limits to plan around.

Add `format` (A3 to A6, Letter, Legal, Tabloid), `landscape`, `margin`, or `header_html`/`footer_html` with `pageNumber` and `totalPages` for page numbers. Requests can be up to 20 MB with 30 seconds per render.

If you run out of credits, the API returns `402 insufficient_credits` and the automation run fails. You'll see it in the run history.

## FAQ

### Can Airtable generate PDFs automatically?

Not with its built-in tools, since the Page designer extension is manual. An automation with a **Run a script** action that calls an HTML-to-PDF API can create a PDF every time a record changes.

### How do I attach the PDF to an Airtable record?

Write the returned URL to an attachment field as `[{ url, filename }]`. Airtable downloads the file and keeps its own copy, so it outlives the 24-hour link.

### Is there a free way to make PDFs from Airtable?

You get 100 free credits once you confirm your email, with no card. One credit is one PDF page, so a one-page invoice costs one credit.

### Do credits expire?

No. Purchased credits never expire, and failed renders cost nothing.

Full reference: https://html-pdf.com/docs.
