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.
- Limited CSS. Flexbox, grid, web fonts,
@pagerules 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.
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 => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[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: truefor wide tables.header_html/footer_htmlwithpageNumberandtotalPagesfor "Page 1 of 3".prefer_css_page_size: trueto let your CSS@pagerule decide.wait_delayorwait_for_expressionwhen 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.
