# HTML to PDF in Google Apps Script

HTML to PDF in Google Apps Script with the native getAs() call or UrlFetchApp to a Chromium API. Save the PDF to Drive or send it with Gmail.

Apps Script is the glue behind a lot of Google Workspace automation, like a Sheet of customers, a Drive folder of invoices or a Gmail notification. Turning HTML into a PDF is a common step, and there are two ways to do it.

**Quick answer.** Send your HTML to the html-pdf.com API with one `UrlFetchApp.fetch` call and save the returned blob. Store your key as the script property `HTML_PDF_KEY` first.

```js
const pdf = UrlFetchApp.fetch('https://html-pdf.com/v1/pdf', {
  method: 'post',
  contentType: 'application/json',
  headers: { Authorization: 'Bearer ' + PropertiesService.getScriptProperties().getProperty('HTML_PDF_KEY') },
  payload: JSON.stringify({ html: '<h1>Hello</h1>' })
}).getBlob().setName('hello.pdf');
DriveApp.createFile(pdf);
```

## The native way: getAs('application/pdf')

This section shows the built-in converter and where it falls short. Apps Script can turn an HTML blob into a PDF without any external service.

```js
function nativeHtmlToPdf() {
  const html = '<h1>Invoice #042</h1><p>Total: <strong>$120.00</strong></p>';
  const pdf = Utilities.newBlob(html, 'text/html', 'invoice.html')
    .getAs('application/pdf')
    .setName('invoice-042.pdf');
  DriveApp.createFile(pdf);
}
```

`HtmlService.createHtmlOutput(html).getBlob().getAs('application/pdf')` works the same way, and you can also fill a Google Docs template with `DocumentApp` and export the document as PDF.

This is fine for plain documents, but the converter is not a browser. Here is what you lose.

- **Limited CSS.** Flexbox, grid, web fonts, `@page` rules and many modern properties are ignored or rendered differently than in Chrome.
- **No JavaScript.** Charts, client-side templating and anything rendered by a script never appear.
- **Little control.** No reliable page size, margins, headers, footers or page numbers.
- **The DocumentApp route** gives you Google Docs layout, not your HTML and CSS.

If your HTML looks right in Chrome and you want the PDF to match, send it to a headless Chromium renderer instead.

## The API way: UrlFetchApp to a Chromium renderer

This section gives you a reusable function that renders real Chrome output. html-pdf.com renders HTML or a public URL with headless Chromium and returns the PDF. From Apps Script, that is one `UrlFetchApp.fetch` call.

### 1. Store your API key in script properties

Get a key at https://html-pdf.com/login (100 free credits, 1 credit = 1 PDF page). In the Apps Script editor go to **Project Settings → Script properties** and add `HTML_PDF_KEY`. Don't hard-code it in the script.

```js
function getKey_() {
  return PropertiesService.getScriptProperties().getProperty('HTML_PDF_KEY');
}
```

### 2. Render HTML to a PDF blob

```js
function htmlToPdf(html, filename) {
  const res = UrlFetchApp.fetch('https://html-pdf.com/v1/pdf', {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + getKey_() },
    payload: JSON.stringify({
      html: html,
      format: 'A4',
      margin: '15mm',
      print_background: true,
      filename: filename
    }),
    muteHttpExceptions: true
  });

  if (res.getResponseCode() !== 200) {
    const err = JSON.parse(res.getContentText()).error;
    throw new Error(err.code + ': ' + err.message);
  }
  return res.getBlob().setName(filename);
}
```

`muteHttpExceptions: true` lets you read the JSON error body (`{"error": {"code", "message", "hint"}}`) instead of getting a generic exception. A `402 insufficient_credits` means it's time to top up. The response headers `X-Pages` and `X-Credits-Remaining` are available via `res.getHeaders()`.

### 3. Save to Drive or email it

```js
function saveAndSend() {
  const pdf = htmlToPdf('<h1>Hello from Apps Script</h1>', 'hello.pdf');

  // Save to a Drive folder
  DriveApp.getFolderById('YOUR_FOLDER_ID').createFile(pdf);

  // Or email it as an attachment
  GmailApp.sendEmail('client@example.com', 'Your document', 'PDF attached.', {
    attachments: [pdf]
  });
}
```

To render a live page instead of a string, send `{"url": "https://..."}` instead of `html`. The page must be publicly reachable.

## Example: one invoice PDF per row in Google Sheets

This example turns a Sheet into a batch of invoice PDFs. Say your sheet has a header row and the columns `Number | Customer | Email | Amount | PDF`. The script builds an invoice for every row without a PDF link, saves it to Drive and writes the link back.

```js
function escape_(s) {
  return String(s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}

function invoicesFromSheet() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Invoices');
  const rows = sheet.getDataRange().getValues();
  const folder = DriveApp.getFolderById('YOUR_FOLDER_ID');

  for (let i = 1; i < rows.length; i++) {
    const [number, customer, email, amount, link] = rows[i];
    if (!number || link) continue;

    const html = `<!doctype html><html><head><meta charset="utf-8"><style>
        body { font-family: Arial, sans-serif; font-size: 12px; }
        header { display: flex; justify-content: space-between; }
        .total { font-size: 20px; margin-top: 24px; }
      </style></head><body>
        <header><h1>Invoice ${escape_(number)}</h1><strong>Your Company</strong></header>
        <p>Bill to: ${escape_(customer)}</p>
        <p class="total">Total: $${Number(amount).toFixed(2)}</p>
      </body></html>`;

    const pdf = htmlToPdf(html, `invoice-${number}.pdf`);
    const file = folder.createFile(pdf);
    sheet.getRange(i + 1, 5).setValue(file.getUrl());
    // GmailApp.sendEmail(email, `Invoice ${number}`, 'Attached.', { attachments: [pdf] });
  }
}
```

Run it from the editor, a custom menu, or a time-driven trigger. Apps Script has its own execution time and daily URL Fetch quotas, so process very large sheets in batches.

For a fuller invoice layout (line items, repeated table headers, clean page breaks) see https://html-pdf.com/generate-invoice-pdf and the ready-made https://html-pdf.com/templates.

## Useful options

These are the settings you will reach for most often.

- `format`: A3 to A6, Letter, Legal, Tabloid. `landscape: true` for wide tables.
- `header_html` / `footer_html` with `pageNumber` and `totalPages` for "Page 1 of 3".
- `prefer_css_page_size: true` to let your CSS `@page` rule decide.
- `wait_delay` or `wait_for_expression` when a URL draws charts with JavaScript.
- `response: "url"` returns JSON with a download link valid for 24 hours, handy if you only need to put a link in a cell or email.

Requests can be up to 20 MB and each render has 30 seconds. The full reference is at https://html-pdf.com/docs. If you prefer a no-code flow instead of a script, see https://html-pdf.com/guides/n8n.

## FAQ

### How do I convert HTML to PDF in Google Apps Script?

For simple pages, use `Utilities.newBlob(html, 'text/html').getAs('application/pdf')`. For output that matches Chrome, POST the HTML to `https://html-pdf.com/v1/pdf` with `UrlFetchApp.fetch` and save the returned blob.

### Why does getAs('application/pdf') ignore my CSS?

The built-in converter is not a browser. It skips flexbox, grid, web fonts, `@page` rules and JavaScript, so a headless Chromium renderer is the fix when layout matters.

### Is there a free way to try it?

Yes. You get 100 free credits once, with no card, and 1 credit = 1 PDF page. After that, packs start at $10 for 1,000 credits, and purchased credits never expire.

### Can I save the PDF to Drive and email it?

Yes. The API returns a blob, so pass it to `DriveApp.createFile` or attach it in `GmailApp.sendEmail`. The HTML is not stored, and `response: "url"` download links last 24 hours.
