├── CONTRIBUTE.md ├── LICENSE ├── README.md └── scripts ├── Doc Images to Folder ├── README.md └── docImgToFolder.js ├── Get Contacts For Label ├── README.md └── getContacts.js ├── List Header Names ├── README.md └── allHeaders.js ├── OrgChart └── README.md ├── Post File to Folder ├── README.md └── postFileToFolder.js ├── Save Email Attachments ├── README.md └── saveEmailAttachments.js └── Sheet Backup Scheduler ├── README.md └── sheetBackup.js /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing? 2 | 3 | This repo is a consilidation of scripts that I have posted on my blog, and on various forums over the last decade. I'm happy to share the code and let others copy it without limitaion (The Unlicense License), but it doesn't really make sense to have others contribute completely new scripts, given the purpose of the repo. I just want to share my work and consolidate it. 4 | 5 | However! I'm totally open to community-submitted issues or PRs that improve my existing scripts, or requests for new scripts! Feel free to create an issue for a new script idea, and if your use case seems like it will be helpful for the wider Apps Script community, I'll be glad to give it a shot. 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # google-apps-script-utils 2 | 3 | Collection of Google Apps Script Utility Functions & Web Apps 4 | 5 | **Author**: _Joseph Petty - [GreenFlux, LLC](https://www.greenflux.us/)_ 6 | 7 | ## Description 8 | This repo is a collection of Google Apps Scripts that I had originally posted on my blog and several other forums over the last decade while working as a freelancer. I'm consolidating all of the scripts here, and linking to the original blog posts. 9 | 10 | ## Scripts 11 | |name|description| 12 | |----|-----------| 13 | |[Doc Images to Folder](scripts/Doc%20Images%20to%20Folder) | Extracts all images from Doc with `sourceId` and saves them to `folderId` (or new folder if not specified)| 14 | |[POST File to Folder](scripts/Post%20File%20to%20Folder) | Saves a file to `folderId` (or new folder) when the URL is sent as a `POST` request to a webapp.| 15 | |[Get Contacts For Label](scripts/Get%20Contacts%20For%20Label)|Creates an endpoint to `GET` contacts from your account with a certain label. Useful for adding a dropdown list in other platforms.| 16 | |[List Header Names](scripts/List%20Header%20Names)|The `allHeaders()` function retrieves all table and column headers from all sheets within a Google Sheets document, except the currently active sheet. Each header is represented as a pair [table name, column name].| 17 | |[Post File To Folder](scripts/Post%20File%20to%20Folder)|Creates a `POST` endpoint to save files to Drive. It downloads the file from the provided URL and saves it to a specified folder.| 18 | |[Save Email Attachments](scripts/Save%20Email%20Attachments)|Downloads new Gmails with a matching label to a Google Sheet. Each email's details, including sender, subject, and body, are saved to the sheet. Additionally, if an email contains attachments, their links are saved in a new column.| 19 | |[Sheet Backup Scheduler](scripts/Sheet%20Backup%20Scheduler)|Backs up a list of sheets to the specified folder on a schedule| 20 | |[Data-Driven Organizational Chart](scripts/OrgChart)|Data-Driven Organizational Chart using OrgChart JS| 21 | 22 | ## More Resources 23 | Most of these scripts were first posted within a longer tutorial or blog post. The originals can be found at: 24 | | | | | | 25 | |---|---|---|---| 26 | |[GreenFlux Blog](https://blog.greenflux.us/) | [AppSheet Forums](https://www.googlecloudcommunity.com/gc/forums/searchpage/tab/message?filter=location,authorId&q=script&noSynonym=false&location=category:appsheet&author_id=312288&collapse_discussion=true) | [Appsmith Community](https://community.appsmith.com/tag/google-apps-script) | [u/HomeBrewDude on Reddit](https://www.reddit.com/user/HomeBrewDude/) | 27 | 28 | ## Contributing 29 | Given that the purpose of this repo is to consolidate my own scripts, it doesn't really make sense to allow others to contribute new scripts. However, I'm happy to accept contributions that improve on my existing scripts (without changing scope or intent). Or you can submit ideas for new scripts, and I'll be happy to consider writing it, if it seems useful for the wider Apps Script community (not too specific to one use case). 30 | 31 | ![image](https://github.com/GreenFluxLLC/google-apps-script-utils/assets/24459976/c14013a0-cb7a-4843-8913-f82e86e9e167) 32 | -------------------------------------------------------------------------------- /scripts/Doc Images to Folder/README.md: -------------------------------------------------------------------------------- 1 | # Google Doc Image Extractor 2 | 3 | This utility script retrieves images from a Google Doc and saves them to a specified Google Drive folder. 4 | 5 | > Original Blog Post: https://community.appsmith.com/content/guide/five-ways-extract-all-images-google-doc 6 | 7 | ## Usage 8 | 9 | ### Function Signature 10 | ```javascript 11 | /** 12 | * Retrieves images from a Google Doc and saves them to a specified Drive folder. 13 | * @param {string} sourceId - The ID of the source Google Doc. 14 | * @param {string} destinationId - (Optional) The ID of the destination Drive folder. 15 | */ 16 | function getDocImages(sourceId, destinationId) { 17 | // Function logic 18 | } 19 | -------------------------------------------------------------------------------- /scripts/Doc Images to Folder/docImgToFolder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Retrieves images from a Google Doc and saves them to a specified Drive folder. 3 | * @param {string} sourceId - The ID of the source Google Doc. 4 | * @param {string} destinationId - (Optional) The ID of the destination Drive folder. 5 | */ 6 | function getDocImages(sourceId, destinationId) { 7 | // Validate sourceId 8 | if (!sourceId || typeof sourceId !== 'string') { 9 | throw new Error('Invalid sourceId. Please provide a valid Google Doc ID.'); 10 | } 11 | 12 | // Retrieve source name and images 13 | const sourceName = DriveApp.getFileById(sourceId).getName(); 14 | const allImages = DocumentApp.openById(sourceId).getBody().getImages(); 15 | 16 | // Determine destination folder 17 | if (!destinationId) { 18 | const parentId = DriveApp.getFileById(sourceId).getParents().next().getId(); 19 | destinationId = DriveApp.getFolderById(parentId).createFolder('images').getId(); 20 | } 21 | 22 | const saveTo = DriveApp.getFolderById(destinationId); 23 | 24 | // Save images to destination folder 25 | allImages.forEach((image, idx) => { 26 | const imageName = `${sourceName}_${idx + 1}`; 27 | const imageBlob = image.getAs('image/png'); // Change file type if needed 28 | saveTo.createFile(imageBlob.setName(imageName)); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /scripts/Get Contacts For Label/README.md: -------------------------------------------------------------------------------- 1 | # Google Contacts API Integration 2 | 3 | ## Overview 4 | 5 | This Google Apps Script integrates with the Google Contacts API to retrieve contacts and contact groups. The script exposes a web endpoint that accepts GET requests with parameters to specify the desired operation, such as retrieving contacts belonging to a specific label or fetching all contact groups. 6 | 7 | This can be used in other platforms and tools like Appsmith, AppSheet, Bubble, etc. to provide a dropdown list of contacts from your Google account. 8 | 9 | > Original Blog Post: https://blog.greenflux.us/adding-gmail-contacts-to-appsmith-using-apps-script 10 | 11 | ## Usage 12 | 13 | 1. **API Key Setup**: 14 | - Set the `API_KEY` variable in the script to a custom string that will be used to authenticate requests. 15 | 16 | 2. **Deploy Script as Web App**: 17 | - Save the script and deploy it as a web app in your Google Apps Script project. 18 | - Go to "Publish" > "Deploy as web app" in the script editor. 19 | - Choose a version name and click "Deploy." 20 | - Set access permissions to "Anyone, even anonymous" or "Anyone within organization." 21 | - Click "Deploy" and authorize the script when prompted. 22 | 23 | 3. **Accessing Contacts and Groups**: 24 | - Make GET requests to the web app URL with the following parameters: 25 | - `key`: API key for authentication. 26 | - `label`: (Optional) Label of the contact group to retrieve contacts from. 27 | - `labels`: (Optional) Flag to indicate retrieval of all contact groups. 28 | 29 | ## Behavior 30 | 31 | - When a valid API key is provided, the script retrieves contacts or contact groups based on the specified parameters. 32 | - Contacts are returned with name, phone, and email properties. 33 | - Contact groups are returned with label and value properties. 34 | - Errors are handled gracefully, and appropriate error responses are returned for unauthorized requests or failed API calls. 35 | -------------------------------------------------------------------------------- /scripts/Get Contacts For Label/getContacts.js: -------------------------------------------------------------------------------- 1 | const API_KEY = 'APIKEY'; // Custom string to check before returning contacts 2 | 3 | /** 4 | * Handles GET requests to retrieve contacts or contact groups based on provided parameters. 5 | * @param {Object} e The event object representing the HTTP GET request. 6 | * @returns {ContentOutput} The response containing contacts or contact groups in JSON format. 7 | */ 8 | function doGet(e) { 9 | let responseBody = {'requestEvent': e}; 10 | 11 | // Check if the API key is provided and valid 12 | if ('key' in e.parameter && e.parameter.key == API_KEY) { 13 | // Retrieve contacts if 'label' parameter is provided 14 | if ('label' in e.parameter) { 15 | const label = e.parameter.label; 16 | const foundContacts = getContacts(label); 17 | responseBody['contacts'] = foundContacts; 18 | } 19 | // Retrieve contact groups if 'labels' parameter is provided 20 | else if ('labels' in e.parameter) { 21 | const foundGroups = getLabels(); 22 | responseBody['groups'] = foundGroups; 23 | } 24 | } 25 | // Return error if API key is missing or invalid 26 | else { 27 | responseBody['error'] = 'Unauthorized'; 28 | } 29 | 30 | // Return response as JSON 31 | return ContentService.createTextOutput(JSON.stringify(responseBody)) 32 | .setMimeType(ContentService.MimeType.JSON); 33 | } 34 | 35 | /** 36 | * Retrieves contacts belonging to the specified label. 37 | * @param {string} label The label of the contact group to retrieve contacts from. 38 | * @returns {Array} An array of contacts with name, phone, and email properties. 39 | */ 40 | function getContacts(label) { 41 | try { 42 | // Get contact group and contacts belonging to the group 43 | const contactGroup = ContactsApp.getContactGroup(label); 44 | const contactsArr = ContactsApp.getContactsByGroup(contactGroup); 45 | 46 | // Map contacts to desired format 47 | const contacts = contactsArr.map(c => ({ 48 | 'name': c.getFullName(), 49 | 'phone': c.getMobilePhone(), 50 | 'email': c.getEmailAddresses().join(', ') // Join multiple email addresses 51 | })); 52 | 53 | // Log contacts for debugging 54 | Logger.log(JSON.stringify(contacts)); 55 | return contacts; 56 | } catch (error) { 57 | // Handle errors and log them 58 | Logger.log('Error retrieving contacts:', error); 59 | return []; 60 | } 61 | } 62 | 63 | /** 64 | * Retrieves contact groups available in the user's Google Contacts. 65 | * @returns {Array} An array of contact groups with label and value properties. 66 | */ 67 | function getLabels() { 68 | try { 69 | // Get all contact groups 70 | const groups = ContactsApp.getContactGroups(); 71 | // Map contact groups to desired format 72 | const groupsArr = groups.map(group => ({ 73 | 'label': group.getName(), 74 | 'value': group.getName() 75 | })); 76 | 77 | // Log contact groups for debugging 78 | Logger.log(groupsArr); 79 | return groupsArr; 80 | } catch (error) { 81 | // Handle errors and log them 82 | Logger.log('Error retrieving labels:', error); 83 | return []; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /scripts/List Header Names/README.md: -------------------------------------------------------------------------------- 1 | # allHeaders Google Apps Script 2 | 3 | ## Overview 4 | 5 | The `allHeaders` function retrieves all table and column headers from all sheets within a Google Sheets document, except the currently active sheet. Each header is represented as a pair `[table name, column name]`. 6 | 7 | ![image](https://github.com/GreenFluxLLC/google-apps-script-utils/assets/24459976/2bcfbaf4-44a1-4624-b887-1da10e0dadb5) 8 | 9 | > Orignal Blog Post: https://www.googlecloudcommunity.com/gc/Tips-Tricks/Auto-Fill-a-Sheet-with-All-Table-Column-Names-from-Your-App/m-p/262203 10 | > 11 | ## Usage 12 | 13 | To use the function: 14 | 15 | 1. **Copy the Script**: Copy the `allHeaders` function into the script editor of your Google Sheets document. 16 | 17 | 2. **Cell Formula**: Enter the cell formula `=allHeaders()` in any cell of your Google Sheets document. 18 | 19 | 3. **Result**: The function will return a 2D array containing all table and column headers, excluding the headers from the currently active sheet. 20 | 21 | ## Return Value Format 22 | 23 | The function returns a 2D array where each row represents a table and column header pair. The format of each row is `[table name, column name]`. 24 | 25 | ## Example 26 | 27 | 28 | |TABLE | COLUMN| 29 | |-|-| 30 | |Table1 | Column1| 31 | |Table1 | Column2| 32 | |Table1 | Column3| 33 | |Table2 | Column1| 34 | |Table2 | Column2| 35 | |Table2 | Column3| 36 | -------------------------------------------------------------------------------- /scripts/List Header Names/allHeaders.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Retrieves all table and column headers from all sheets except the current one. 3 | * Each header is represented as a pair [table name, column name]. 4 | * 5 | * @returns {string[][]} 2D array of table and column headers. 6 | */ 7 | function allHeaders() { 8 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 9 | var sheets = spreadsheet.getSheets(); 10 | var currentSheetName = SpreadsheetApp.getActiveSheet().getName(); 11 | var allHeaders = []; 12 | 13 | for (var i = 0; i < sheets.length; i++) { 14 | var sheetName = sheets[i].getName(); 15 | 16 | if (sheetName != currentSheetName) { 17 | var sheet = spreadsheet.getSheetByName(sheetName); 18 | var lastColumn = sheet.getLastColumn(); 19 | var headerNames = sheet.getRange(1, 1, 1, lastColumn).getValues()[0]; 20 | 21 | for (var j = 0; j < lastColumn; j++) { 22 | var tableAndColumn = [sheetName, headerNames[j]]; 23 | allHeaders.push(tableAndColumn); 24 | } 25 | } 26 | } 27 | 28 | return allHeaders; 29 | } 30 | -------------------------------------------------------------------------------- /scripts/OrgChart/README.md: -------------------------------------------------------------------------------- 1 | # Building A Data-Driven Organizational Chart In Apps Script 2 | Using OrgChart JavaScript Library In A Web App 3 | 4 | ![orgchart](https://github.com/user-attachments/assets/e7c74ffa-f40e-4d7b-bfd7-e663c8a577f4) 5 | 6 | Tutorial: [Building A Data-Driven Organizational Chart In Apps Script](https://blog.greenflux.us/building-a-data-driven-organizational-chart-in-apps-script) 7 | 8 | ## Script 9 | ```javascript 10 | function doGet(e) { 11 | return HtmlService.createHtmlOutputFromFile('index') 12 | .setTitle('Org Chart Tutorial') 13 | .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); 14 | } 15 | 16 | function getEmployeeData() { 17 | const ss = SpreadsheetApp.getActiveSpreadsheet(); 18 | const sheet = ss.getSheetByName("Employees"); 19 | const data = sheet.getDataRange().getValues(); 20 | 21 | const employees = {}; 22 | let root = null; 23 | 24 | // Skip header row 25 | for (let i = 1; i < data.length; i++) { 26 | const row = data[i]; 27 | const id = String(row[0]); 28 | const name = row[1]; 29 | const title = row[2]; 30 | const supervisorId = String(row[3]) || ''; 31 | 32 | employees[id] = { 33 | id: id, 34 | name: name, 35 | title: title, 36 | supervisorId: supervisorId, 37 | children: [] 38 | }; 39 | 40 | // Find the root (no supervisor_id) 41 | if (supervisorId === "") { 42 | root = employees[id]; 43 | } 44 | } 45 | 46 | // Build the tree structure 47 | for (const id in employees) { 48 | const employee = employees[id]; 49 | if (employee.supervisorId !== "" && employees[employee.supervisorId]) { 50 | employees[employee.supervisorId].children.push(employee); 51 | } 52 | } 53 | 54 | Logger.log(JSON.stringify(root, undefined, 2)); 55 | return root; 56 | } 57 | ``` 58 | ## HTML 59 | ```html 60 | 61 | 62 | 63 | 64 | Organization Chart Plugin 65 | 66 | 67 | 74 | 75 | 76 |
77 | 78 | 79 | 80 | 91 | 92 | 93 | 94 | ``` 95 | 96 | `` 97 | -------------------------------------------------------------------------------- /scripts/Post File to Folder/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive File Downloader Web App 2 | 3 | This Google Apps Script processes POST requests containing file URLs and saves the files to Google Drive. 4 | > Original Blog Post: https://blog.greenflux.us/save-files-to-google-drive-by-post-ing-the-url-to-a-web-app 5 | 6 | ## Description 7 | 8 | This script is designed to be deployed as a web app and handle POST requests containing file URLs. It downloads the file from the provided URL and saves it to a specified folder in Google Drive. 9 | 10 | ## Deployment Steps 11 | 12 | 1. **Copy Script**: Copy this script into the script editor and save it. 13 | 2. **Trigger Permission Dialog**: Run the `doPost` function once to trigger the permission dialog, and then click allow. 14 | 3. **Publish as Web App**: 15 | - Go to "Publish" > "Deploy as web app" in the script editor. 16 | - Choose a version name and click "Deploy." 17 | - Set access permissions to "Anyone, even anonymous" or "Anyone within organization." 18 | - Click "Deploy" and authorize the script when prompted. 19 | 4. **Note Web App URL**: Note the web app URL generated after deployment, which will be used to send POST requests. 20 | 21 | ## Post Body Format 22 | 23 | The script expects POST requests with a JSON body containing the following fields: 24 | - `key`: A custom string to authenticate the request. Change the value of `key` to match the one used in the request. 25 | - `fileUrl`: The URL of the file to be downloaded and saved to Google Drive. 26 | - `folderId` (optional): The ID of the folder in Google Drive where the file should be saved. If not provided, the file will be saved to the default folder specified in the script. 27 | 28 | ## Function Signature 29 | 30 | ```javascript 31 | /** 32 | * @param {Object} e The event object representing the HTTP POST request. 33 | * @return {TextOutput} Text output containing the URL of the new file Drive. 34 | */ 35 | function doPost(e) { 36 | // Function logic 37 | } 38 | -------------------------------------------------------------------------------- /scripts/Post File to Folder/postFileToFolder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Google Apps Script to process POST requests and save files to Google Drive. 3 | * 4 | * This script is designed to be deployed as a web app and handle POST requests containing file URLs. 5 | * It downloads the file from the provided URL and saves it to a specified folder in Google Drive. 6 | * 7 | * Deployment: 8 | * 1. Copy this script into the script editor and save. 9 | * 2. Run the doPost function once to trigger the permission dialog, and then click allow. 10 | * 3. Publish the script as a web app: 11 | * - Go to "Publish" > "Deploy as web app" in the script editor. 12 | * - Choose a version name and click "Deploy." 13 | * - Set access permissions to "Anyone, even anonymous" or "Anyone within organization." 14 | * - Click "Deploy" and authorize the script when prompted. 15 | * 3. Note the web app URL generated after deployment, which will be used to send POST requests. 16 | * 17 | * 18 | * Post Body Format: 19 | * - The script expects POST requests with a JSON body containing the following fields: 20 | * - key: A custom string to authenticate the request. Change the value of `key` to match the one used in the request. 21 | * - fileUrl: The URL of the file to be downloaded and saved to Google Drive. 22 | * - folderId (optional): The ID of the folder in Google Drive where the file should be saved. 23 | * If not provided, the file will be saved to the default folder specified in the script. 24 | * 25 | * @param {Object} e The event object representing the HTTP POST request. 26 | * @return {TextOutput} Text output containing the URL of the new file Drive. 27 | */ 28 | 29 | const DEFAULT_FOLDER_ID = 'FOLDER_ID_FROM_URL'; // Folder ID to use if no ID is given 30 | const API_KEY = 'TEST_KEY'; // Change key, and optionally, move it to a script property 31 | 32 | function doPost(e) { 33 | try { 34 | const requestData = JSON.parse(e.postData.contents); 35 | const { key, fileUrl, folderId } = requestData; 36 | 37 | if (!key || key !== API_KEY || !fileUrl) { 38 | throw new Error('Invalid request. Missing key or fileUrl.'); 39 | } 40 | 41 | const returnedUrl = getFileByUrl(fileUrl, folderId || DEFAULT_FOLDER_ID); 42 | return ContentService.createTextOutput(returnedUrl); 43 | } catch (error) { 44 | console.error('Error processing request:', error.message); 45 | return ContentService.createTextOutput('Error processing request.'); 46 | } 47 | } 48 | 49 | function getFileByUrl(url = DEFAULT_URL, folderId = DEFAULT_FOLDER_ID) { 50 | // Download file from URL and save it to the specified folder in Google Drive 51 | const fileData = UrlFetchApp.fetch(url); 52 | const folder = DriveApp.getFolderById(folderId); 53 | const fileName = url.substring(url.lastIndexOf('/') + 1); // Extract file name from URL 54 | const newFile = folder.createFile(fileData).setName(fileName); 55 | const newFileUrl = newFile.getUrl(); 56 | Logger.log(`File created: ${newFileUrl}`); 57 | return newFileUrl; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/Save Email Attachments/README.md: -------------------------------------------------------------------------------- 1 | # Gmail to Google Sheet Downloader 2 | 3 | ## Overview 4 | 5 | The `emailToSheet` function downloads new Gmails with a matching label to a Google Sheet. Each email's details, including sender, subject, and body, are saved to the sheet. Additionally, if an email contains attachments, their links are saved in a new column. 6 | 7 | > Based on script from this forum post: https://www.googlecloudcommunity.com/gc/Tips-Tricks/APPS-SCRIPT-Download-Gmails-to-GSheet-using-Gmail-Filters-Labels/m-p/362574 8 | 9 | ## Usage 10 | 11 | To use the function: 12 | 13 | 1. **Create Filter Rule in Gmail**: Set up a filter rule in Gmail to apply a custom named label to emails you want to download. 14 | 15 | 2. **Modify Script Parameters**: 16 | - Replace `'YOUR_CUSTOM_LABEL'` with the name of the custom label applied to matching emails. 17 | - Replace `'GSHEET_ID'` with the ID from the Google Sheet URL where you want to save the emails. 18 | 19 | 3. **Run Script and Authorize**: Save the script and click run. Follow the authorization prompts to grant necessary permissions. 20 | 21 | 4. **Install Timed Trigger (Optional)**: Optionally, install a timed trigger for the script to automate the download process at regular intervals. 22 | 23 | ## Behavior 24 | 25 | - Matching emails are downloaded to the Google Sheet. 26 | - The custom label is removed from the downloaded emails. 27 | - Emails are moved to the archive and marked as read. 28 | - Optionally, emails can be moved to a new label after download by uncommenting and configuring the `moveToLabel` variable. 29 | 30 | ## Attachments 31 | 32 | Attachments are saved to the same folder as the Google Sheet in a subfolder named with the timestamp and subject of the email. 33 | -------------------------------------------------------------------------------- /scripts/Save Email Attachments/saveEmailAttachments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Downloads new Gmails with a matching label to a Google Sheet. 3 | * 4 | * 1. Create a filter rule in Gmail to apply a custom named 'label'. 5 | * 2. Replace 'YOUR_CUSTOM_LABEL' below with the new label name. 6 | * 3. Replace 'GSHEET_ID' with the ID from the sheet URL. 7 | * 4. Save the script and click run, then authorize. 8 | * 5. Optionally, install a timed trigger for the script. 9 | * 10 | * Matching emails are downloaded, and then the label is removed. 11 | * 12 | * @returns {void} 13 | * 14 | */ 15 | function emailToSheet() { 16 | var label = GmailApp.getUserLabelByName('YOUR_CUSTOM_LABEL'); // <-- RENAME TO YOUR CUSTOM FILTER LABEL 17 | var ss = SpreadsheetApp.openById('GSHEET_ID'); // <-- INSERT GSHEET_ID 18 | var sh = ss.getSheetByName("Email"); // <-- RENAME TO SAVE TO DIFFERENT SHEET 19 | // var moveToLabel = GmailApp.getUserLabelByName('MOVE_TO_LABEL'); // <-- Uncomment to move to new label after download 20 | var threads = label.getThreads(); 21 | 22 | for (var i = 0; i < threads.length; i++) { 23 | var messages = threads[i].getMessages(); 24 | 25 | for (var j = 0; j < messages.length; j++) { 26 | var sent = messages[j].getDate(); 27 | var sender = messages[j].getFrom(); 28 | var subject = messages[j].getSubject(); 29 | var body = messages[j].getPlainBody(); 30 | 31 | // Get attachments 32 | var attachments = messages[j].getAttachments(); 33 | var attachmentLinks = []; 34 | 35 | // Save attachments and get links 36 | if (attachments.length > 0) { 37 | var folder = DriveApp.getRootFolder().createFolder(getFolderName(subject)); 38 | for (var k = 0; k < attachments.length; k++) { 39 | var attachment = attachments[k]; 40 | var attachmentName = attachment.getName(); 41 | var attachmentFile = folder.createFile(attachment); 42 | var attachmentUrl = attachmentFile.getUrl(); 43 | attachmentLinks.push(attachmentUrl); 44 | } 45 | } 46 | 47 | // Append row with email details and attachment links 48 | ss.appendRow([sent, sender, subject, body].concat(attachmentLinks)); 49 | } 50 | 51 | // Remove label, move to archive, mark as read, and optionally move to a new label 52 | threads[i].removeLabel(label); 53 | threads[i].moveToArchive(); 54 | threads[i].markRead(); 55 | // if (typeof moveToLabel !== 'undefined') {threads[i].addLabel(moveToLabel)} 56 | } 57 | } 58 | 59 | /** 60 | * Generates a folder name using the timestamp and subject. 61 | * 62 | * @param {string} subject The subject of the email. 63 | * @returns {string} The folder name. 64 | */ 65 | function getFolderName(subject) { 66 | var timestamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd"); 67 | return timestamp + "-" + subject; 68 | } 69 | -------------------------------------------------------------------------------- /scripts/Sheet Backup Scheduler/README.md: -------------------------------------------------------------------------------- 1 | # Google Sheets Backup Script 2 | 3 | This Google Apps Script automates the process of backing up sheets from a source spreadsheet to destination folders in Google Drive. 4 | > Original Blog Post: https://blog.greenflux.us/save-files-to-google-drive-by-post-ing-the-url-to-a-web-app 5 | 6 | ## Overview 7 | 8 | The `backupSheets` function takes input parameters specifying the source spreadsheet ID, the name of the sheet containing the list of source sheet IDs, the column indices for source and destination IDs, and the column index to update with the URL of the new backup files. It copies each source sheet to its respective destination folder in Google Drive and updates the specified column with the URL of the new backup files. 9 | 10 | ## Usage 11 | 12 | To use the script: 13 | 14 | 1. **Configuration**: 15 | - Specify the source spreadsheet ID, sheet name, and column indices for source and destination IDs in the script. 16 | - Ensure that the source sheet contains a list of sheet IDs to be backed up and the corresponding destination folder IDs. 17 | 18 | 2. **Deployment**: 19 | - Copy the script into the script editor of a Google Sheets document. 20 | - Run the `backupSheets` function once to trigger the permission dialog and authorize the script. 21 | 22 | 3. **Execution**: 23 | - Call the `backupSheets` function with the required parameters to initiate the backup process. 24 | - The script will copy each source sheet to its designated destination folder in Google Drive and update the specified column with the URL of the new backup files. 25 | 26 | ## Parameters 27 | 28 | - `spreadsheetId`: The ID of the source spreadsheet containing the sheets to be backed up. 29 | - `sheetName`: The name of the sheet within the source spreadsheet containing the list of source sheet IDs. 30 | - `sourceIdCol`: The column index of the source sheet IDs in the specified sheet. 31 | - `destinationIdCol`: The column index of the destination folder IDs in the specified sheet. 32 | - `newFileURLCol`: The column index to update with the URL of the new backup files. 33 | 34 | |Source Sheet ID|Destination Folder ID|New File URL| 35 | |---------------|---------------------|------------| 36 | abc123|xyz456|[https://docs.google.com/spreadsheets/d/backup_id_1]| 37 | def456|uvw789|[https://docs.google.com/spreadsheets/d/backup_id_2]| 38 | ghi789|lmn012|[https://docs.google.com/spreadsheets/d/backup_id_3]| 39 | -------------------------------------------------------------------------------- /scripts/Sheet Backup Scheduler/sheetBackup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Backup sheets listed in a source spreadsheet to destination folders in Google Drive. 3 | * 4 | * @param {string} spreadsheetId The ID of the source spreadsheet. 5 | * @param {string} sheetName The name of the sheet containing the list of source sheet IDs. 6 | * @param {number} sourceIdCol The column index of the source sheet IDs. 7 | * @param {number} destinationIdCol The column index of the destination folder IDs. 8 | * @param {number} newFileURLCol The column index to update with the URL of the new backup files. 9 | */ 10 | function backupSheets(spreadsheetId, sheetName, sourceIdCol, destinationIdCol, newFileURLCol) { 11 | try { 12 | // Load source sheet IDs for backup 13 | const ss = SpreadsheetApp.openById(spreadsheetId); 14 | const sh = ss.getSheetByName(sheetName); 15 | const lastRow = sh.getLastRow(); 16 | const sourceIds = sh.getRange(2, sourceIdCol, lastRow - 1, 1).getValues(); // Array of IDs for source sheets 17 | 18 | // Copy each source sheet to destination folder 19 | sourceIds.forEach((sourceId, index) => { 20 | const source = SpreadsheetApp.openById(sourceId); 21 | const sourceName = source.getName(); 22 | const dateTimeStr = Utilities.formatDate(new Date(), 'GMT-4', 'yyyyMMdd_HHmmss'); 23 | const backupName = sourceName + '_BAK_' + dateTimeStr; 24 | const backupId = source.copy(backupName).getId(); // File created in My Drive by default 25 | const destinationId = sh.getRange(index + 2, destinationIdCol).getValue(); // Folder ID for destination sheet 26 | const destination = DriveApp.getFolderById(destinationId); 27 | DriveApp.getFileById(backupId).moveTo(destination); 28 | 29 | // Save new file link to sheet 30 | const backupURL = 'https://docs.google.com/spreadsheets/d/' + backupId + '/edit#gid=0'; 31 | const hyperlink = SpreadsheetApp.newRichTextValue().setText(backupName).setLinkUrl(backupURL).build(); 32 | sh.getRange(index + 2, newFileURLCol).setRichTextValue(hyperlink); // Link to last backup file 33 | }); 34 | } catch (error) { 35 | console.error('Error backing up sheets:', error); 36 | } 37 | } 38 | --------------------------------------------------------------------------------