appscript.dev
Automation Advanced Drive

Auto-watermark PDFs in a folder

Stamp Northwind documents with a confidentiality watermark before they're shared.

Published Sep 6, 2025

Northwind shares contracts, proposals and briefs as PDFs, and every one of them should carry a “Confidential” watermark before it leaves the studio. Doing that by hand — open each file, stamp it, re-export — is exactly the dull step that gets skipped under deadline, and an unmarked document going out is a real risk.

Apps Script cannot edit PDF binaries itself, but it can route them through an external watermarking service (PDFMonkey, ILovePDF, or your own endpoint). This script watches a Drive folder, sends every unstamped PDF to that service, saves the watermarked copy back, and trashes the original — so the folder ends up holding only stamped, shareable documents.

What you’ll need

  • A Drive folder that collects the PDFs to be stamped, and its folder ID.
  • A watermarking HTTP endpoint that accepts a PDF and returns a stamped PDF. Set its URL in the PDF_API constant.
  • If that service needs an API key, store it in Script Properties rather than in the code — see Store API keys and secrets securely.
  • Edit access to the folder from the account running the script.

The script

// The watermarking endpoint: accepts a PDF, returns a stamped PDF.
const PDF_API = 'https://your-watermark-service.example/stamp';

// Marker added to stamped filenames, so a file is never processed twice.
const STAMPED_TAG = '[stamped]';

// The default watermark text.
const DEFAULT_WATERMARK = 'CONFIDENTIAL — Northwind';

/**
 * Sends every unstamped PDF in a folder through the watermarking
 * service, saves the stamped copy back, and trashes the original.
 *
 * @param {string} folderId The Drive folder of PDFs to watermark.
 * @param {string} watermarkText The text to stamp onto each PDF.
 */
function watermarkFolder(folderId, watermarkText = DEFAULT_WATERMARK) {
  const folder = DriveApp.getFolderById(folderId);
  const files = folder.getFiles();

  let stampedCount = 0;

  // 1. Walk every file in the folder.
  while (files.hasNext()) {
    const file = files.next();

    // 2. Skip anything that is not a PDF.
    if (file.getMimeType() !== 'application/pdf') continue;

    // 3. Skip PDFs that have already been stamped.
    if (file.getName().includes(STAMPED_TAG)) continue;

    // 4. Send the PDF's bytes to the watermarking service.
    const response = UrlFetchApp.fetch(PDF_API, {
      method: 'post',
      contentType: 'application/pdf',
      payload: file.getBlob().getBytes(),
      headers: { 'X-Watermark': watermarkText },
      muteHttpExceptions: true,
    });

    // 5. Bail on this file if the service did not return success.
    if (response.getResponseCode() !== 200) {
      Logger.log(
        'Watermark failed for "' + file.getName() +
        '" (HTTP ' + response.getResponseCode() + ') — skipping.'
      );
      continue;
    }

    // 6. Name the stamped copy and save it into the same folder.
    const baseName = file.getName().replace(/\.pdf$/i, '');
    const stamped = response
      .getBlob()
      .setName(baseName + ' ' + STAMPED_TAG + '.pdf');
    folder.createFile(stamped);

    // 7. Trash the original so only stamped PDFs remain.
    file.setTrashed(true);
    stampedCount++;
  }

  Logger.log('Watermarked ' + stampedCount + ' PDF(s).');
}

/**
 * Convenience wrapper: watermarks a fixed folder. Edit the folder ID,
 * or call watermarkFolder directly.
 */
function watermarkNorthwindFolder() {
  watermarkFolder('1abcFolderId');
}

How it works

  1. watermarkFolder opens the folder by ID and gets an iterator over its files.
  2. It walks each file and skips anything whose MIME type is not application/pdf — images, Docs and other formats are left alone.
  3. It skips any PDF whose name already contains [stamped], so the function is safe to re-run and never double-stamps a document.
  4. It reads the PDF’s raw bytes and POSTs them to the PDF_API endpoint, passing the watermark text in an X-Watermark header.
  5. It checks the HTTP response code. Anything other than 200 is logged and the file is skipped, so a service hiccup does not trash an original that was never successfully stamped.
  6. It takes the stamped PDF the service returned, names it with the original base name plus the [stamped] tag, and saves it into the same folder.
  7. Only once the stamped copy is safely saved does it trash the original — so the folder ends up holding stamped versions only.

Example run

The watch folder before a run:

File
Acme proposal.pdforiginal, unstamped
Beta contract.pdforiginal, unstamped
logo.pngnot a PDF — ignored

After a run:

File
Acme proposal [stamped].pdfwatermarked copy
Beta contract [stamped].pdfwatermarked copy
logo.pnguntouched

The two originals are in the Drive trash; the folder now contains only “Confidential”-stamped, shareable PDFs.

Run it

This is a job you run on demand, before a batch of documents goes out:

  1. Set PDF_API to your watermarking endpoint and put the folder ID into watermarkNorthwindFolder (or call watermarkFolder directly).
  2. In the Apps Script editor, select the function and click Run.
  3. Approve the authorisation prompt the first time.
  4. Check the folder — every PDF should now carry the [stamped] tag.

To stamp documents automatically as they are added, attach a time-driven trigger to watermarkNorthwindFolder running every few hours.

Watch out for

  • The script trashes the original after stamping. The file is recoverable from the Drive trash for 30 days, but if you need to keep originals, save the stamped copy to a different folder and skip the setTrashed call.
  • It depends entirely on the external service. If PDF_API is down, slow, or changes its response format, nothing gets stamped — the HTTP check in step 5 stops a failure from destroying originals, but watch the logs.
  • PDF bytes go to a third-party endpoint. For confidential documents, use a service you trust (or self-host one), and check its data-retention policy before sending real contracts through it.
  • UrlFetchApp caps a single request payload at around 50 MB and the whole job must finish inside Apps Script’s 6-minute runtime. A folder of large or numerous PDFs may need processing in smaller batches.
  • The [stamped] filename tag is the only thing preventing re-stamping. If someone renames a stamped file, the next run will stamp it again.
  • The watermark is whatever the external service produces. A service that lays text over the page is not the same as redaction — it does not remove or protect the underlying content.

Related