fx Gmail, Drive, Calendar & Docs

Generate PDF invoices from a Docs template

⏱ 22 min

What you'll learn

  • Designing a Google Docs template with placeholders
  • Copying the template, replacing placeholders, exporting to PDF
  • Saving the PDF link back to the sheet and emailing it

Concept

Step 1: the template

Create a Google Doc called Invoice Template and design it as you like (logo, address, table). Wherever data should go, type a placeholder in double curly braces:

INVOICE {{InvoiceNo}}                     Date: {{Date}}

Bill to: {{Customer}}
Phone:   {{Phone}}

Item: {{Item}}
Amount: ₹{{Amount}}
GST:    ₹{{Gst}}
Total:  ₹{{Total}}

Copy the Doc's ID from its URL: docs.google.com/document/d/<TEMPLATE_ID>/edit.

Step 2: the sheet

Tab Invoices:

A InvoiceNo B Date C Customer D Phone E Email F Item G Amount H PDF Link

Step 3: the script

const TEMPLATE_ID = 'PASTE_TEMPLATE_DOC_ID';
const OUTPUT_FOLDER_ID = 'PASTE_FOLDER_ID';
const GST_RATE = 0.03;

function createInvoices() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Invoices');
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;

  const rows = sheet.getRange(2, 1, lastRow - 1, 8).getValues();
  const folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID);
  const tz = Session.getScriptTimeZone();

  rows.forEach((r, i) => {
    const [invoiceNo, date, customer, phone, email, item, amount, link] = r;
    if (!invoiceNo || link) return; // skip blank rows and rows already done

    const gst = Math.round(amount * GST_RATE * 100) / 100;
    const total = amount + gst;
    const fmt = n => Number(n).toLocaleString('en-IN', { minimumFractionDigits: 2 });

    // 1. Copy the template
    const copy = DriveApp.getFileById(TEMPLATE_ID).makeCopy(`Invoice ${invoiceNo}`, folder);

    // 2. Replace placeholders
    const doc = DocumentApp.openById(copy.getId());
    const body = doc.getBody();
    const values = {
      InvoiceNo: invoiceNo,
      Date: Utilities.formatDate(new Date(date), tz, 'dd-MMM-yyyy'),
      Customer: customer,
      Phone: phone,
      Item: item,
      Amount: fmt(amount),
      Gst: fmt(gst),
      Total: fmt(total)
    };
    Object.entries(values).forEach(([key, val]) => {
      body.replaceText(`\\{\\{${key}\\}\\}`, String(val));
    });
    doc.saveAndClose();

    // 3. Export as PDF and remove the temporary Doc
    const pdf = folder.createFile(copy.getAs(MimeType.PDF)).setName(`Invoice ${invoiceNo}.pdf`);
    copy.setTrashed(true);

    // 4. Save the link back to the sheet
    sheet.getRange(i + 2, 8).setValue(pdf.getUrl());

    // 5. Email it (optional)
    if (email) {
      GmailApp.sendEmail(email, `Invoice ${invoiceNo}`,
        `Dear ${customer},\n\nPlease find your invoice attached.\n\nThank you.`,
        { attachments: [pdf.getBlob()], name: 'Accounts Team' });
    }
  });
}

Why each step matters

  • makeCopy — never edit the template itself; always work on a copy.
  • replaceText uses a regular expression. Curly braces have special meaning there, so we escape them: \\{\\{Date\\}\\}. Without escaping, some placeholders won't be found.
  • saveAndClose() — must be called before export, or the PDF may show the old placeholders.
  • getAs(MimeType.PDF) — converts the Doc to a PDF blob; createFile saves it.
  • setTrashed(true) — moves the temporary Doc to the bin so your folder only has PDFs.
  • The link column doubles as a "done" marker, so re-running only creates new invoices.

Variations

  • Use a Google Slides template for designs with images (SlidesApp, replaceAllText).
  • Combine with the year/month folder helper from the Drive lesson.
  • Run it from a menu item, or from a form-submit trigger to invoice each new order automatically.

Key points

  • Template + placeholders + copy + replace + export = automated documents.
  • Escape { and } in replaceText patterns.
  • Store the PDF link to avoid creating duplicates.

Exercises

mediumPractice 1

Add a {{AmountInWords}} placeholder. Write a helper that converts a number to Indian-style words (e.g. "One Lakh Twenty Thousand Rupees Only").

Quiz

Why must you call doc.saveAndClose() before getAs(MimeType.PDF)? a) To rename the file b) So the PDF includes the replaced text c) To share it
So the PDF includes the replaced text
Why do we escape curly braces in replaceText? a) For Hindi text b) The pattern is a regular expression c) Docs doesn't allow braces
The pattern is a regular expression
Generate PDF invoices from a Docs template · Google Apps Script | ExcelWalaa