Skip to content

Instantly share code, notes, and snippets.

@brokensandals
Last active November 6, 2022 09:33
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save brokensandals/6b77f73666323d6e4b94ff1df12a532a to your computer and use it in GitHub Desktop.
Save brokensandals/6b77f73666323d6e4b94ff1df12a532a to your computer and use it in GitHub Desktop.
Google Docs/Sheets/Slides automated backups script

Update: I now use a slightly different version of this script, which creates a single zip file instead of one per document, and puts a timestamp in the filename rather than overwriting the previous backup file. That version can be found at https://github.com/brokensandals/export-google-docs.

Google Apps Script that exports all your Google Docs/Sheets/Slides into docx/xlsx/pptx files and PDFs into a folder in your Google Drive. For more info and step-by-step setup instructions, see here: http://brokensandals.net/google-docs-backup

Replace INSERT_FOLDER_ID_HERE with the ID of the folder you want backups to be placed in.

Create a trigger to run the backupAll function if you want to do this on a schedule (e.g. nightly).

Notes:

  • By default, only files that you own (as opposed to files others have shared with you) will be backed up. Remove the file.getOwner() check from the backupAll method if you want to change that.
  • For each file, both an Office file (docx/xlsx/pptx) and a PDF are generated, and combined into a zip file that's placed in the backup folder. Zipping the backup files ensures that they don't clutter up the recent activity list for Docs/Sheets/Slides.
  • The script depends on the lastUpdated dates being correct on both the input files and the files in the backup directory. If that seems problematic, you could change the createOrUpdateFileForBlob method to delete existing backup files rather than updating them.

As always, this code may have defects that prevent it from working properly. Use at your own risk and remember to periodically verify that your backups are actually working as expected.

var BACKUP_FOLDER_ID = 'INSERT_FOLDER_ID_HERE';
var NATIVE_MIME_TYPES = {};
NATIVE_MIME_TYPES[MimeType.GOOGLE_DOCS] = MimeType.MICROSOFT_WORD;
NATIVE_MIME_TYPES[MimeType.GOOGLE_SHEETS] = MimeType.MICROSOFT_EXCEL;
NATIVE_MIME_TYPES[MimeType.GOOGLE_SLIDES] = MimeType.MICROSOFT_POWERPOINT;
var NATIVE_EXTENSIONS = {};
NATIVE_EXTENSIONS[MimeType.GOOGLE_DOCS] = '.docx';
NATIVE_EXTENSIONS[MimeType.GOOGLE_SHEETS] = '.xlsx';
NATIVE_EXTENSIONS[MimeType.GOOGLE_SLIDES] = '.pptx';
var BACKUP_MIME_TYPES = Object.keys(NATIVE_MIME_TYPES);
function backupAll() {
const backupFolder = DriveApp.getFolderById(BACKUP_FOLDER_ID);
BACKUP_MIME_TYPES.forEach(function(mimeType) {
var files = DriveApp.getFilesByType(mimeType);
while (files.hasNext()) {
var file = files.next();
if (file.getOwner() && file.getOwner().getEmail() == Session.getActiveUser().getEmail()) {
backup(file, backupFolder);
}
}
});
}
function backup(file, folder) {
var targetName = file.getName() + ' ' + file.getId();
var lastUpdated = file.getLastUpdated();
var pdf = getPdfBlob(file);
var native = getNativeBlob(file);
var zip = Utilities.zip([pdf, native], targetName + '.zip');
createOrUpdateFileForBlob(zip, folder, lastUpdated);
}
function createOrUpdateFileForBlob(blob, folder, ifOlderThan) {
var existingFiles = folder.getFilesByName(blob.getName());
if (existingFiles.hasNext()) {
var file = existingFiles.next();
if (file.getLastUpdated() < ifOlderThan) {
updateFile(file, blob);
}
} else {
folder.createFile(blob);
}
}
function updateFile(file, blob) {
const url = 'https://www.googleapis.com/upload/drive/v2/files/' + file.getId() + '?uploadType=media';
const params = {
method: 'put',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
payload: blob
};
var response = UrlFetchApp.fetch(url, params);
if (response.getResponseCode() < 200 || response.getResponseCode() > 299) {
throw 'Failed to update file named ' + file.getName();
}
}
function getPdfBlob(file) {
var blob = file.getAs('application/pdf');
return blob;
}
function getNativeBlob(file) {
const nativeMimeType = NATIVE_MIME_TYPES[file.getMimeType()];
const extension = NATIVE_EXTENSIONS[file.getMimeType()];
const url = 'https://www.googleapis.com/drive/v2/files/' + file.getId() + '/export?mimeType=' + nativeMimeType;
const params = {
method: 'get',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() }
};
const blob = UrlFetchApp.fetch(url, params).getBlob();
blob.setName(file.getName() + extension);
return blob;
}
@freetimecoder
Copy link

@pantha007 Sure, I put my forked changes up here:
https://gist.github.com/freetimecoder/dc916ace62b0e67f89ed80e617b6f630
Beware though, it's probably not as stable, because I optimized for speed and skipped some checks along the way.

@pantha007
Copy link

@freetimecoder, thanks so much I will be keeping an eye on that :-) :-)

@tis-andias
Copy link

Thanks so much @brokensandals!

I'm looking for a way to back-up selected google docs only. Hoping you can help or point me in the right direction?

@brokensandals
Copy link
Author

@tis-andias You'll want to change the backupAll function so that it retrieves a File object for each file you're interested in, and then calls the backup function with it. (You can get File objects using the DriveApp API which is documented at https://developers.google.com/apps-script/reference/drive/drive-app . Below I'll give a couple examples of using it.)

If you just want to back up a handful of specific docs, you can find the ID of each one by opening it in Google Docs (or sheets or whatever) and looking at your web browser's address bar; the ID is the long series of letters and numbers between /d/ and /edit. Then you can change the backupAll function to look like this:

function backupAll() {
  const backupFolder = DriveApp.getFolderById(BACKUP_FOLDER_ID);
  // Add as many files as you want on the following line.
  const ids = ['INSERT_FIRST_ID_HERE', 'INSERT_SECOND_ID_HERE', 'INSERT_THIRD_ID_HERE'];
  for (var index = 0; index < ids.length; index++) {
    backup(DriveApp.getFileById(ids[index]), backupFolder);
  }
}

Alternatively, if you wanted to back up all the files in a specific folder, you could look up its ID in the address bar on Google Drive and do something like:

function backupAll() {
  const backupFolder = DriveApp.getFolderById(BACKUP_FOLDER_ID);
  const folderToBeBackedUp = DriveApp.getFolderById('PASTE_FOLDER_ID_HERE');
  var files = folderToBeBackedUp.getFiles();
  while (files.hasNext()) {
    backup(files.next(), backupFolder);
  }
}

I haven't tested this code so it might not be quite right, but should be close.

@jake-babcock
Copy link

Now is there a way to instead of backing it up to my google drive to have it download the files locally to an external hdd?

@brokensandals
Copy link
Author

Now is there a way to instead of backing it up to my google drive to have it download the files locally to an external hdd?

You can install Google Backup & Sync so that the backup file on google drive gets automatically downloaded to your hard drive. (You could either configure Backup & Sync to use a folder on your external hard drive, or use a different backup program to do backups from your main hard drive to your external hard drive.)

Or you could write a script to download just the backup file from google drive using the google drive API. Or write a different backup script similar to this one, but running on your computer and using the API to export each file and write the backup directly to your external hard drive. I don’t really use google drive any more so I can’t help with any of that.

@jake-babcock
Copy link

Yeah I've got a python script thatll download all the zips. Now im trying to automate this as much as possible, so is there a way within this Apps Script to create that backup folder at the beginning of the script, and then pull that File ID for the BACKUP_FOLDER_ID? I know there would have to be some checks in place to keep it from breaking should the folder already exist, but Im running into an issue of not being able to pull the file id properly.

@brokensandals
Copy link
Author

is there a way within this Apps Script to create that backup folder at the beginning of the script, and then pull that File ID for the BACKUP_FOLDER_ID?

One approach could be:

  • instead of putting the zips directly inside the backup folder, have the Apps Script create a new subfolder inside that folder each time it runs, and put the current date in the folder name
  • have the python script list all the subfolders in the backup folder and download from the newest one

See the createFolder method in the Folder documentation.

You could also look at my newer version of the script which puts the entire backup into a single zip file with a date in the name - then your python script would just need to download the newest file.

@dmorgans0803
Copy link

I'm a complete newbie and am struggling to implement this code.
I've installed it in Google Drive but not sure it's in the correct spot.
I get an error message when I try to run it ...
7:53:26 PM Notice Execution started
7:53:26 PM Error
Exception: No item with the given ID could be found. Possibly because you have not edited this item or you do not have permission to access it.
backupAll @ Code.gs:16

Tried researching the error message but only went further down the rabbit hole.
Any easy fixes?
Thanks
Douglas

@brokensandals
Copy link
Author

@dmorgans0803 Based on the error, I'm guessing maybe you didn't replace the text INSERT_FOLDER_ID_HERE in the script. You need to replace it with the ID of some folder that you've created (this is where the backup files will go), as mentioned in the "Instructions" section here: https://brokensandals.net/technical/backup-tooling/google-docs/

To get the ID, you open the folder in Google Drive in your web browser, then look at the address bar - you should see something similar to "https://drive.google.com/drive/folders/asdflkjjlk23409cdsklmklmnnl290". Notice the long sequence of random-looking letters and numbers after "folders/" - that's what you need to copy. Don't include the slash or anything before it; and if you see a question mark, don't include the question mark or anything after it.

Hope this helps - if the issue is something else, I'm afraid I don't have any immediate ideas.

@dmorgans0803
Copy link

dmorgans0803 commented Jan 7, 2022 via email

@Martianno
Copy link

Hello! Thanks for your fantastic utility. It works nicely!

May I have just one request? Would it be please possible to export my Documents as plain text - as a .txt file?

Thank you!

@brokensandals
Copy link
Author

@Martianno Glad you found it useful!

Would it be please possible to export my Documents as plain text - as a .txt file?

I won't have time to test or troubleshoot this, but, one thing you could try would be to replace MimeType.MICROSOFT_WORD on line 4 with MimeType.PLAIN_TEXT, and replace .docx on line 9 with .txt

@Martianno
Copy link

Thanks for a super fast reaction! It Works, God bless you!

@cygonzales
Copy link

Hi.. Thank you for the script. If i am not mistaken, the script is to backup all kinds of file and zip it. I am new in this environment and im trying to do the basics for each type of file. Can you help me with the script that can backup google docs file? I was able to make an auto backup for the spreadsheet however, im trying to replicate the code and replaced the var name but i dont know the specific function for google docs. I hope you could help me.. Thank you.. :)

function archiveCopy() {
var file= DriveApp.getFileById("1liz6Kfh4eiXJOR95Ry03m-lVO_6fv6-sylZ3N3RAWCg");
var destination= DriveApp.getFolderById("1y-aYx3SbnlZSde_J57FEj_Jyfo4bj6ED");

var timeZone=Session.getScriptTimeZone();
var formattedDate= Utilities.formatDate(new Date(),timeZone,"yyyy-MM-dd' 'HH:mm:ss");
var name= SpreadsheetApp.getActiveSpreadsheet().getName()+"Copy"+formattedDate;

file.makeCopy(name,destination);

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment