Code-first automation / Google Apps Script
Generate a PDF from Google Apps Script
JavaScript running inside a Google account, with direct access to Drive, Gmail, Sheets and Docs and no infrastructure to run.
Making the request
UrlFetchApp.fetch, which takes a URL and an options object with method, contentType, headers and payload. It is synchronous, which is unusual and makes the code read as a straight sequence.
What it does with the response
The cleanest on this list. The response object has getBlob, which returns a Blob: a first-class object in this environment that carries bytes, a content type and a name. Every Google service that accepts a file accepts a Blob, so there is no conversion step anywhere between the render and the destination. Set the name on the blob and the filename follows it into Drive and into a Gmail attachment.
Where the document can go afterwards
This is the part that differs most between platforms, and it is usually the reason one platform was chosen over another in the first place.
- Drive, with DriveApp.createFile, into a folder you can pick by id
- a Gmail message as an attachment, by passing the blob in the attachments array
- a Sheets-driven mail merge, one render per row, which is the most common shape here
- a shared drive, where the file inherits the drive's permissions rather than the script owner's
The limit that bites in production
Three real limits. A script execution is capped at six minutes on a consumer account and longer on a Workspace one, so a loop rendering hundreds of documents will time out and needs to be driven by a trigger that processes a batch per run. UrlFetchApp has a daily call quota that varies by account type. And the response size that UrlFetchApp will accept is capped, so a very large document has to be stored and fetched rather than returned inline.
The shape that works
Render, name the blob, write it where it is going, and record that you did. For a mail merge over a sheet, write a status back to the row inside the loop, so a timeout leaves a resumable state rather than an unknown one.
function renderInvoice(html, filename) {
const res = UrlFetchApp.fetch("https://api.pdfpipe.xyz/v1/pdf", {
method: "post",
contentType: "application/json",
headers: {
Authorization: "Bearer " + PropertiesService
.getScriptProperties().getProperty("PDFPIPE_KEY"),
},
payload: JSON.stringify({
html: html,
options: { format: "A4", margin: "16mm" },
}),
// Without this a non-200 throws before you can read the body,
// so the error message is lost.
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error("Render failed: " + res.getContentText());
}
// A Blob is the native currency here. Name it, and the name follows
// it into Drive and into an email attachment.
const blob = res.getBlob().setName(filename);
const folder = DriveApp.getFolderById("FOLDER_ID");
const file = folder.createFile(blob);
GmailApp.sendEmail("customer@example.com", "Your invoice", "Attached.", {
attachments: [blob],
});
return file.getUrl();
}The mistake people make
Looping over a whole sheet in one execution. The six minute limit arrives somewhere in the middle, the script stops, and there is no record of which rows were done. Process a batch, write a status back to each row as you go, and let a time-driven trigger pick up where it stopped.
Frequently asked
Can Google Apps Script handle the PDF bytes directly?
The cleanest on this list. The response object has getBlob, which returns a Blob: a first-class object in this environment that carries bytes, a content type and a name. Every Google service that accepts a file accepts a Blob, so there is no conversion step anywhere between the render and the destination. Set the name on the blob and the filename follows it into Drive and into a Gmail attachment.
Should I store the document or pass the bytes through?
It depends on what the platform does with a large value. On a platform that serialises everything passing between steps, pushing several megabytes through the workflow is slow and sometimes capped, so storing the document and passing a URL is better. On a platform that hands you a file object natively, passing it straight to its destination is simpler and avoids a second fetch. The page above says which of those this platform is.
Where does the API key live?
In the platform's own secret or environment storage, never in a step body where it is visible in an execution log. Every platform on this list has somewhere to put it, and the execution logs of an automation platform are seen by more people than a codebase is.
Other platforms
Platforms of the same kind, and one from each of the other kinds.
Generate a PDF from Pipedream
A workflow platform where the steps are real Node.js or Python, so anything the language can do the workflow can do.
Generate a PDF from Power Automate
Microsoft's cloud flow automation, which sits closest to SharePoint, OneDrive, Outlook and Dataverse and is usually chosen because the documents have to land in one of them.
Generate a PDF from Retool
An internal tool builder where the UI is assembled from components and the data comes from queries, which makes a file an awkward shape to pass around.
Generate a PDF and attach it in Airtable
A database with a spreadsheet interface, where attachments are a first-class field type and are set in a way that surprises people.
Every automation platform covered
Grouped by what kind of platform it is, with the three that have their own guide linked out.
Storing generated PDFs and fetching them later
The capability several of these integrations are built on, with the retention that applies.
100 free documents a month, and a playground that runs a real render without a key, which is the fastest way to get an HTML sample worth wiring up.