HTML to PDF in Google Apps Script

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.

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.

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.

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.

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

2. Render HTML to a PDF blob

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

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.

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.

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.

This page as markdown · text