Automated invoicing from spreadsheets uses Google Apps Script to generate invoices from Google Sheets data. It reads client info, services, and amounts, then fills a Google Docs or PDF template. Invoices are saved, emailed, or printed automatically—saving time, reducing errors, and streamlining billing for freelancers, businesses, and agencies.
- Step 1: Click on the
Copy
button to copy the code snippet. - Step 2: Paste the copied code into your project’s script editor.
Apps Scripts Blog
Apps Scripts Code
function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu("Outright") .addItem("Create PDF Links", "createPdfLinks") .addItem("Send PDFs", "sendPdfs") .addToUi(); createHeadersIfNeeded(); } function createHeadersIfNeeded() { const properties = PropertiesService.getScriptProperties(); const headersSet = properties.getProperty("headersSet"); if (!headersSet) { createHeaders(); properties.setProperty("headersSet", "true"); } } function createHeaders() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); const headers = [ "INVOICE_DATE", "DUE_DATE", "INVOICE_NUMBER", "Contact person", "contact_company", "Country", "my_company_details", "ITEM_1", "cost_1", "ITEM_2", "cost_2", "ITEM_3", "cost_3", "Total_cost", "my_bank_details", "Email", "status", "Final invoice" ]; const existingHeaders = sheet.getRange("1:1").getValues()[0]; let headersToSet = []; headers.forEach((header, index) => { if (!existingHeaders.includes(header)) { headersToSet.push([header]); } else { headersToSet.push([existingHeaders[existingHeaders.indexOf(header)]]); } }); sheet.getRange(1, 1, 1, headersToSet.length).setValues([headersToSet.flat()]); const headerRange = sheet.getRange(1, 1, 1, headersToSet.length); headerRange.setFontWeight("bold"); headerRange.setFontSize(14); } function getTemplateId() { const templateSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("invoice templates"); if (!templateSheet) { throw new Error('Sheet "invoice templates" not found'); } const templateUrl = templateSheet.getRange("B2").getValue(); // Assuming the template URL is in cell B2 const templateId = templateUrl.match(/[-\w]{25,}/); // Extract the ID from the URL if (!templateId) { throw new Error('Template ID not found in the provided URL'); } return templateId[0]; } function createPdfLinks() { try { const copiedSheet = createMonthlySheetCopy(); // Create a copy of the master sheet before creating PDF links var ss = SpreadsheetApp.getActiveSpreadsheet(); var masterSheet = ss.getSheetByName("master-clients"); var sheet = copiedSheet; if (!sheet) { throw new Error('Copied sheet not found'); } var dataRange = sheet.getDataRange(); var dataValues = dataRange.getValues(); var headers = dataValues[0]; // Get the headers from the first row var templateFileId = getTemplateId(); // Get the template ID from the "invoice templates" sheet var templateDoc = DriveApp.getFileById(templateFileId); var templateName = templateDoc.getName(); var folder = DriveApp.createFolder("Generated Documents"); var currentDate = new Date(); var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate); var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2); var currentDay = ('0' + currentDate.getDate()).slice(-2); var invoiceName = currentMonth + ' ' + currentDay + ' Invoice from Outright Systems'; for (var i = 1; i < dataValues.length; i++) { var row = dataValues[i]; var clientNumber = row[headers.indexOf("INVOICE_NUMBER")]; var invoiceNumber = currentDate.getFullYear() + '.' + ('0' + (currentDate.getMonth() + 1)).slice(-2) + '.' + clientNumber; row[headers.indexOf("INVOICE_NUMBER")] = invoiceNumber; var invoiceDate = row[headers.indexOf("INVOICE_DATE")]; if (!invoiceDate) { invoiceDate = currentDate; row[headers.indexOf("INVOICE_DATE")] = invoiceDate; sheet.getRange(i + 1, headers.indexOf("INVOICE_DATE") + 1).setValue(Utilities.formatDate(invoiceDate, Session.getScriptTimeZone(), "dd MMM yyyy")); masterSheet.getRange(i + 1, headers.indexOf("INVOICE_DATE") + 1).setValue(Utilities.formatDate(invoiceDate, Session.getScriptTimeZone(), "dd MMM yyyy")); } var dueDate = row[headers.indexOf("DUE_DATE")]; if (!dueDate) { var nextMonthDate = new Date(invoiceDate); nextMonthDate.setMonth(nextMonthDate.getMonth() + 1); nextMonthDate.setDate(1); row[headers.indexOf("DUE_DATE")] = nextMonthDate; sheet.getRange(i + 1, headers.indexOf("DUE_DATE") + 1).setValue(Utilities.formatDate(nextMonthDate, Session.getScriptTimeZone(), "dd MMM yyyy")); masterSheet.getRange(i + 1, headers.indexOf("DUE_DATE") + 1).setValue(Utilities.formatDate(nextMonthDate, Session.getScriptTimeZone(), "dd MMM yyyy")); } var cost1 = parseFloat(row[headers.indexOf("cost_1")]) || 0; var cost2 = parseFloat(row[headers.indexOf("cost_2")]) || 0; var cost3 = parseFloat(row[headers.indexOf("cost_3")]) || 0; var totalCost = cost1 + cost2 + cost3; row[headers.indexOf("Total_cost")] = totalCost + " USD"; sheet.getRange(i + 1, headers.indexOf("Total_cost") + 1).setValue(totalCost + " USD"); masterSheet.getRange(i + 1, headers.indexOf("Total_cost") + 1).setValue(totalCost + " USD"); var templateCopy = templateDoc.makeCopy(); var doc = DocumentApp.openById(templateCopy.getId()); var body = doc.getBody(); headers.forEach((header, index) => { var placeholder = "{{" + header.toLowerCase() + "}}"; var value = row[index]; if (typeof value === 'object' && value instanceof Date) { value = Utilities.formatDate(value, Session.getScriptTimeZone(), "dd MMM yyyy"); } body.replaceText(placeholder, value); }); doc.saveAndClose(); var newFileName = templateName + " - " + invoiceNumber; var copiedFile = DriveApp.getFileById(templateCopy.getId()); copiedFile.setName(newFileName); var pdfFile = copiedFile.getAs(MimeType.PDF); var finalPdf = folder.createFile(pdfFile); finalPdf.setName(newFileName + ".pdf"); var pdfLink = finalPdf.getUrl(); sheet.getRange(i + 1, headers.indexOf("Final invoice") + 1).setValue(pdfLink); // Update status to "Created" in both sheets sheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Created"); masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Created"); Logger.log("PDF created: " + pdfLink); copiedFile.setTrashed(true); } SpreadsheetApp.getUi().alert("PDF Links successfully created."); } catch (error) { SpreadsheetApp.getUi().alert("Error: " + error.message); Logger.log("Error: " + error.message); } } function sendPdfs() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var masterSheet = ss.getSheetByName("master-clients"); if (!masterSheet) { throw new Error('Sheet "master-clients" not found'); } var currentDate = new Date(); var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate); var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2); var currentDay = ('0' + currentDate.getDate()).slice(-2); var newSheetName = currentMonth + " " + lastTwoDigitsOfYear + " Invoices"; var copiedSheet = ss.getSheetByName(newSheetName); if (!copiedSheet) { throw new Error('Copied sheet not found'); } var dataRange = copiedSheet.getDataRange(); var dataValues = dataRange.getValues(); var headers = dataValues[0]; // Get the headers from the first row var baseInvoiceName = currentMonth + ' ' + currentDay + ' Invoice from Outright Systems'; for (var i = 1; i < dataValues.length; i++) { var row = dataValues[i]; var email = row[headers.indexOf("Email")]; var pdfLink = row[headers.indexOf("Final invoice")]; Logger.log("Processing row: " + (i + 1)); Logger.log("Email: " + email); Logger.log("PDF Link: " + pdfLink); if (email && pdfLink) { try { var fileId = pdfLink.match(/[-\w]{25,}/); if (fileId) { var pdfFile = DriveApp.getFileById(fileId[0]).getAs(MimeType.PDF); MailApp.sendEmail({ to: email, subject: baseInvoiceName, body: "Please find attached your invoice.", attachments: [pdfFile] }); Logger.log("Email sent to: " + email); copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Sent"); masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Sent"); } else { Logger.log("Invalid PDF link: " + pdfLink); copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Invalid link"); masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Invalid link"); } } catch (e) { Logger.log("Failed to send email to: " + email + " with error: " + e.message); copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Failed to send"); masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Failed to send"); } } else { Logger.log("No email address or PDF link found for row: " + (i + 1)); copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("No email found"); masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("No email found"); } } SpreadsheetApp.getUi().alert("PDFs successfully sent."); } function createMonthlySheetCopy() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var masterSheet = ss.getSheetByName("master-clients"); if (!masterSheet) { throw new Error('Sheet "master-clients" not found'); } var currentDate = new Date(); var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate); var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2); var newSheetName = currentMonth + " " + lastTwoDigitsOfYear + " Invoices"; var existingSheet = ss.getSheetByName(newSheetName); if (existingSheet) { ss.deleteSheet(existingSheet); } var copiedSheet = masterSheet.copyTo(ss).setName(newSheetName); return copiedSheet; }