Guide · Updated October 2026
Make PDF Invoices Automatically from Google Sheets
Stop typing client details into every invoice by hand. With one Google Doc design and a short Apps Script, one click turns each row in your Sheet into a PDF with your logo — saved in Drive and emailed to the client.
How it works
Everything happens in your own Google account — no billing software and no monthly fee:
- Google Sheet keeps the invoice details
- Google Doc is the invoice design
- Apps Script puts each row into the design
- Drive saves the PDF, Gmail sends it
Step by step
Put your invoice data in a Sheet
One row for each invoice, with column names in row 1. Add any other columns you need — address, GSTIN, due date, items — the script finds them by name.
Design the invoice once in Google Docs
Make a normal Google Doc with your logo, bank details and terms. Where a value should change, type the column name inside double curly brackets, like {{Client}} or {{Amount}}.
Make a Drive folder for the PDFs
Every PDF is saved here. Copy the folder's ID from its web address (the long code after /folders/), and the Doc's ID from its address (the code after /d/).
Add the script and run it
In the Sheet, open Extensions → Apps Script. Paste the code below, add your two IDs and press Run once to give permission. After that you get an “Invoices” menu in the Sheet.
The Sheet columns
The script needs these column names in row 1 (spelled exactly the same):
| Column | What goes in it |
|---|---|
| Invoice No | INV-001 — a different number for every invoice |
| Date | Invoice date |
| Client | Customer or company name |
| Where the PDF should be sent | |
| Amount | The total, written the way you want it on the bill |
| PDF Link | Leave empty — the script fills it |
| Status | Leave empty — the script writes “Sent” |
The Apps Script
Copy this into Extensions → Apps Script. It skips rows that already say “Sent”, so you can run it again any time you add new rows.
// Row 1 = headers, e.g. Invoice No | Date | Client | Email | Amount | PDF Link | Status
// The Google Doc template uses the same names: {{Invoice No}}, {{Client}}, {{Amount}} ...
const TEMPLATE_ID = 'PASTE-YOUR-GOOGLE-DOC-ID';
const FOLDER_ID = 'PASTE-YOUR-DRIVE-FOLDER-ID';
function createInvoices() {
const sheet = SpreadsheetApp.getActiveSheet();
const rows = sheet.getDataRange().getDisplayValues(); // keeps ₹ and date formats
const head = rows[0];
const col = (name) => head.indexOf(name);
const folder = DriveApp.getFolderById(FOLDER_ID);
for (let i = 1; i < rows.length; i++) {
const r = rows[i];
if (!r[col('Client')] || r[col('Status')]) continue; // empty or already done
// 1. Copy the template and fill in this row
const copy = DriveApp.getFileById(TEMPLATE_ID)
.makeCopy('Invoice ' + r[col('Invoice No')], folder);
const doc = DocumentApp.openById(copy.getId());
head.forEach((h, c) => {
const tag = ('{{' + h + '}}').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
doc.getBody().replaceText(tag, r[c]);
});
doc.saveAndClose();
// 2. Save it as a PDF and delete the Doc copy
const pdf = folder.createFile(copy.getAs(MimeType.PDF));
copy.setTrashed(true);
// 3. Email the PDF to the client
GmailApp.sendEmail(
r[col('Email')],
'Invoice ' + r[col('Invoice No')],
'Hi ' + r[col('Client')] + ',\n\nPlease find your invoice attached.\n\nThank you!',
{ attachments: [pdf.getAs(MimeType.PDF)] }
);
// 4. Write the link and status back to the Sheet
sheet.getRange(i + 1, col('PDF Link') + 1).setValue(pdf.getUrl());
sheet.getRange(i + 1, col('Status') + 1).setValue('Sent');
}
}
// Adds an "Invoices" menu to the Sheet so anyone can run it with one click
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Invoices')
.addItem('Create & send invoices', 'createInvoices')
.addToUi();
}
- Want it to run by itself? Add a trigger (Triggers → Add trigger) to run it every hour, or when a Google Form adds a new row.
- For invoices with many items, the items go on a second tab and the script fills a table in the Doc. I can set this version up for you.
Gmail limits: a script can email about 100 people a day from a free Gmail account and about 1,500 from Google Workspace. That is enough for most small businesses — but good to know before sending all month-end bills at once.
Not only invoices
The same Sheet → Doc → PDF idea works for any document you fill again and again:
- Bills and tax invoices
- Quotations
- Payment receipts
- Salary slips
- Offer and appointment letters
- Reports for clients
Waiting for these invoices to be paid? The payment follow-up tracker guide shows how to track pending money and send polite reminders.
Tips before you start
- The script uses getDisplayValues, so dates and ₹ amounts look the same in the PDF as in the Sheet.
- Make invoice numbers with a formula so they never repeat, for example ="INV-"&TEXT(ROW()-1,"000").
- Send the first few invoices to your own email before sending to real clients.
- Keep one Drive folder per month or per client so PDFs stay easy to find.
- Never delete the Status column — it stops a client from getting the same invoice twice.
Frequently asked questions
Is it free to make PDF invoices from Google Sheets?
Yes. Google Sheets, Docs, Drive and Gmail all come free with a Google account, and Apps Script costs nothing to run. You don't need any paid add-on or billing software.
How many invoices can it email in a day?
Gmail lets a script email about 100 people a day from a free account and about 1,500 from Google Workspace. Making the PDFs in Drive has no real limit for a small business.
Can the invoice have my logo and my own design?
Yes. The design is a normal Google Doc, so you add your logo, colours, bank details and terms once. Every invoice the script makes looks exactly like it.
Can one invoice have several items?
Yes. Keep the items on another tab with the invoice number on each line. The script fills a table in the Doc with those items and the totals.
Will the PDF be a valid GST invoice?
The PDF can show everything a GST invoice needs — GSTIN, HSN/SAC codes, CGST/SGST/IGST and totals. If your business must make e-invoices (IRN) on the government website, that is a separate step this script does not do.