appscript.dev
Automation Advanced Drive

Bundle a folder of images into one PDF

Combine Northwind scans into a single deliverable PDF using a generation service.

Published Nov 17, 2025

Northwind’s site team comes back from a job with a folder of photos — twenty JPEGs of a finished install. The client wants one tidy PDF, not a shared folder they have to scroll through. Doing it by hand means dragging every image into a document and exporting, which is fine once and tedious the tenth time.

Apps Script cannot assemble a PDF from images on its own, so this script takes the same approach as the other PDF automations: it collects the images from a Drive folder, base64-encodes them, and posts the lot to a small PDF-building service. The service stitches them into a single document and the script files the finished PDF straight back into the folder.

What you’ll need

  • A PDF-building endpoint that accepts a JSON array of base64-encoded images and returns a base64 PDF. This can be a hosted service or a small function you deploy; the script only depends on the request and response shape.
  • If the endpoint needs an API key, keep it in Script Properties rather than in the code — see Store API keys and secrets securely.
  • A Drive folder of images to bundle. The script handles any image/* file and skips everything else.

The script

// PDF-building endpoint. It takes { "images": ["<base64>", ...] }
// and returns { "pdf": "<base64>" }.
const PDF_BUILDER = 'https://your-pdf-service.example/images-to-pdf';

/**
 * Bundles every image in a Drive folder into a single PDF and saves
 * the result back into that folder.
 *
 * @param {string} folderId The folder of images to bundle.
 * @param {string} outputName The file name for the finished PDF.
 */
function bundleImagesToPdf(folderId, outputName) {
  const folder = DriveApp.getFolderById(folderId);

  // 1. Collect every image in the folder, base64-encoded for the request.
  const images = [];
  const files = folder.getFiles();
  while (files.hasNext()) {
    const f = files.next();
    if (!f.getMimeType().startsWith('image/')) continue;
    images.push(Utilities.base64Encode(f.getBlob().getBytes()));
  }

  // 2. Bail out if the folder has no images — nothing to bundle.
  if (images.length === 0) {
    Logger.log('No images in the folder — nothing to bundle.');
    return;
  }

  // 3. Post the images to the PDF-building service.
  const res = UrlFetchApp.fetch(PDF_BUILDER, {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({ images }),
    muteHttpExceptions: true,
  });

  // 4. Guard against a failed call before parsing the body.
  if (res.getResponseCode() !== 200) {
    throw new Error('PDF service returned ' + res.getResponseCode());
  }

  // 5. Decode the returned PDF and save it back into the folder.
  const pdfBase64 = JSON.parse(res.getContentText()).pdf;
  const pdf = Utilities.newBlob(
    Utilities.base64Decode(pdfBase64),
    'application/pdf',
    outputName
  );
  folder.createFile(pdf);

  Logger.log('Bundled ' + images.length + ' image(s) into ' + outputName + '.');
}

How it works

  1. bundleImagesToPdf opens the folder and walks its files, encoding each image/* file as base64 and skipping anything that is not an image.
  2. If the folder turned up no images, it logs a message and stops before making a pointless API call.
  3. It POSTs the collected images as a JSON array to PDF_BUILDER. muteHttpExceptions lets the script inspect a bad response instead of throwing on it.
  4. It checks the response code first, so a service that is down or rejecting the request fails clearly rather than producing a broken file.
  5. It pulls the base64 pdf field from the response, decodes it back into bytes, wraps them in a PDF blob with the chosen name, and saves it into the same folder.

Example run

Say a folder holds the photos from one job, plus a stray notes file:

File in folderMIME typeIncluded?
install-01.jpgimage/jpegYes
install-02.jpgimage/jpegYes
install-03.pngimage/pngYes
site-notes.txttext/plainNo — skipped

Calling bundleImagesToPdf(folderId, 'Acme install report.pdf') produces a single Acme install report.pdf in that folder, with the three images as its pages in folder order. The log reads Bundled 3 image(s) into Acme install report.pdf.

Run it

This runs on demand, once a job’s photos are in place:

  1. Set PDF_BUILDER to your PDF-building endpoint.
  2. In the Apps Script editor, call bundleImagesToPdf from the Run panel with the folder ID and a name for the output PDF.
  3. Approve the authorisation prompt the first time.
  4. Open the folder and check the new PDF.

To bundle jobs automatically, wrap bundleImagesToPdf in a function that loops over a parent folder of job folders and run it on a time-driven trigger.

Watch out for

  • Page order follows whatever order Drive returns files in, which is not guaranteed to be alphabetical. Name images with zero-padded numbers (01, 02, …) and sort the list before sending if order matters.
  • Every image is base64-encoded and held in memory at once. A folder of large, high-resolution photos can hit Apps Script’s memory or runtime limits — bundle in smaller batches if it fails.
  • The images leave your account. Whatever service PDF_BUILDER points at receives every photo — only use an endpoint you trust, and prefer one you control for client work.
  • Re-running on the same folder adds another PDF, and since the PDF lands in the source folder, a later run would also try to encode it (it is not an image, so it is skipped — but the folder fills with old PDFs). Clear or rename previous outputs between runs.
  • The script does not resize or compress images. If the service expects a size limit, downscale large photos before encoding them.

Related