PDFPipe

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.

javascript
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.

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.