Multiple FILE Maker

Making multiple copies in Google Drive is a slow, boring task. You select the file you want, make a copy, then have to rename it, then repeat this again and again. Here you’ll learn how to make multiple copies of a file and name them individually, really fast using Apps Script.

First, we have a Sheet where we will enter the details. There are three parts.


In cell A3 we type the fixed part of the file name want, i.e. The part that is the same in all the copies of the file.


In column B, we type the variable parts of the file name, i.e. The parts we want to be unique in each file.


Finally, in cell C3 we paste the full URL of the file we want to copy.


We then choose Make Multiple Files from the Creator menu.


Here are the files it made, all individually named:


Obviously, it works best when there are a lot of files to make.


Let’s go through the code step-by-step:


Line 1: First, we set up our function, here I’ve called it makeFiles().

Line 4: Then, we get the current active spreadsheet, using getActiveSpreadsheet().

Line 5: Then, we get the active sheet, using getActiveSheet().

Line 6: Now, let’s get the fixed name. It’s in row 3, column 1 (I.e. A3), so in the getRange() method we add (3,1), then get its value using getValue().

Line 7: Now, we need the variable names. First, we need to get all the cells in the second column (B), from row 3 to the bottom row. We need to find out where the bottom row is, so here I’ve used getMaxRows() which gets the last row on the sheet, whether it has any values or not. I take away 2, as we will be starting from row 3, so need to discount the first 2 rows.

Line 8: We use getRange() again, but this time we’re getting a Series of values, so we need to tell it where to start and where to end. In the brackets we state: starting row, starting column, number of rows, number of columns. So, here we’re starting in row 3, column 2, down to the bottom row using the bottomRow variable, and we only want 1 column. Then we get all the values with getValues(), which will store them in the variableNames array variable.

Lines 10 to 22, are ways to check the user has entered information into the relevant cells. The program could work without them, but often it’s important to include some kind of checks to deal with users doing things that they shouldn’t, as invariably, they will always do something, that’s not expected or what in your mind wasn’t logical! Here I’m going to use some simple if statements to check that they’ve filled in the sheet correctly before running the program.


Line 11: First, I want to check that they’ve entered something into the fixed name cell. So, I’ve used an if statement to check if the variable fixedName is blank, using the double speech marks.

Line 12: If it is blank, I want it to show an alert message, telling the user it’s blank. To do so, we use getUI() and the type of message is an alert. Then in the brackets we add the text we want to display. Line 13 closes the if statement.


Line 16: Here I get the URL from cell C3 (row 3, column 3) and store it in the variable fileUrl.

Line 17: Then, I check if the variable fileUrl is empty.

Line 18: if it is empty, as before, I want to display an alert.

Now, I want to check that both fixedName AND fileUrl are NOT empty. If they aren’t empty it will run the rest of the code and make the copies, if one is empty, it won’t run the code. This means that if one of the alerts is shown above, it won’t run any code afterwards,  a king sure no copies are made by mistake.


Line 22: Here we first check if the fixedName isn’t equal to a blank. Then, use the double ampersands, which mean AND, to also check if the fileUrl isn’t blank. If they both aren’t empty it runs the following code.


Line 25: Now, we need the file ID from the file URL, in order to make copies of the file. We use the match() method (more info here) and use some regular expressions to extract just the key part. Don’t worry about how this works exactly, but if you’re interested in the use of regular expressions, go to this page.

Line 27: Now, we use that ID stored in fileId, to get the file by its ID, using the DriveApp class and the getFileById() method, which basically, gets the file we want to copy.

Now, we finally get to the point where we make the multiple copies. We’ll use a simple for loop for this. We want it to loop down column B making copies as it goes down, but then stop when it reaches an empty cell in that column, i.e.  The end of the list.


Line 31: I’ve set up a counter variable called n, and we’ll keep going down until we hit the bottom row.

Line 32: But to prevent it making files with no variable names, I’ve included an if statement to check to see if the current variable name is blank, using variableNames and the current array number in the square bracket. Remember this is looping down the array variable variableNames, so it’s going from zero to potentially the bottom row. If it does find a blank it breaks out of the loop, i.e. It doesn’t continue going through the array. Also, note the use of a double equals sign, not a triple one, as we are comparing an array value with something empty and they are not the same type, so a triple equals sign will fail.

Line 34: This is the line that makes a copy of the file we want. We use the fileId stored in the variable fileToCopy we created in line 27. Then, use the makeCopy() method and in the brackets state the name we want. Here, we’ll going to join the fixed name with the variable name). Line 35 closes the loop.


Line 36: Finally, just to tell the user the process has finished, I’ve added a toast message, which will pop up in the bottom right-hand corner, when the program has finished. Here it frees to the active Spreadsheet ss, then in the brackets contains 3 parts: message, title, number of seconds it will be displayed.

Line 37 and 38: This closes the if statement from line 22, and the function.

Adding a menu

To run the program from a menu in the spreadsheet, I’ve also added a separate script file called onOpen, which will add a little menu to run the code.

Line 1: Call the function onOpen(), so that it opens automatically every time the spreadsheet is opened.

Line 2: Get the active spreadsheet, note I’ve left a comma at the end not a semi-colon, as the next 2 lines are connected to it.

Lines 3-4: Set up a variable called menuItems to add the menu details. The keyword var isn’t needed here as it using the one from line 2. Then we set up an array with the sub-menu name and the function it will run. Carefully note the syntax used here.

Line 5: Now, we add the menu to the spreadsheet using the addMenu() method to the active spreadsheet. In the brackets, we include 2 parts: menu name, and the variable menuItems.


Here’s the link to the Sheet and code:

This code can of course be modified to suit your needs, as for example, you may want to let users create files without a fixed part to the file name or maybe without any variable names so all the files are named the same.

Here’s the complete code:


The regular expression in line 25 is from here: 

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Follow my Google+ Learning Google Apps Script Collection

Baz Roberts (Flipboard / Twitter / Google+)

Pimp up your Sheet – Programmatically!

When working with spreadsheets we can spend quite a long time making them look good and if we have to make the same sheets over and over again, that’s a lot of wasted time. Here we’ll look at how you can format a Google Sheet via Apps Script. The focus of this post is to show you some of the common methods available to get Apps Script to format your sheets automatically. Quite often, these would be part of larger program which get the data from somewhere and create a new sheet with it, then format the way you want. However, it could also be used where you want consistency between different sheets.

Due to their simplicity, I also think it’s a great place to start if you are new to Apps Script. If you are new, please see my earlier posts, which explain how to open the Script Editor, etc.

There are 3 scripts. In the first, we’ll cover the following:

  • adding borders
  • changing the font and size
  • setting the cell wrap status
  • setting the horizontal and vertical alignment
  • setting the number format
  • changing to bold
  • changing the cell background colour
  • deleting unwanted rows and columns
  • adjusting the column to specific widths and to adjust to the text

In the second and third scripts, we’ll quickly look at hiding rows and columns, to set up the sheet with the information you want in it, then be able to reset it by showing all the rows and columns again.


As an example, we going to format a list of classes. So we are going to go from this:


To this (which will take the program about 3 seconds):


Here are all the steps one-by-one:


Let’s go through it step-by-step:

First we set up the function, here I’ve called it “formatSheet1” and we get the active Spreadsheet and the sheet we want, in this case, the sheet is “Sheet1”.


Then I want to set up some variables to store the rows and columns in the sheet.

Line 8: Here I’ve used getDataRange() to get the range that contains all the data in the sheet and stored it in the variable allCells, which we’ll be using throughout the script.

Lines 9 and 10: Here I also want to store the number of rows and columns for later use. To do so, I use the getLastColumn() and getLastRow() methods.


I also want to get a couple of specific ranges that we’ll use later.

Line 13: I get the header row by using getRange() and in the brackets I put (starting row, starting column, number of rows, number of columns). I’ve used the variable numColumns for the number of columns.

Line 14: Plus, I get the comments column, using getRange().


Now let’s start formatting the sheet.

Line 17: First, let’s add borders to all the cells which contain data. We use the setBorder() method and use it with the range stored in allCells, i.e. all the data. In the brackets, there are 6 parts: top, left, bottom, right, vertical, horizontal, which refer to the sides of the cells. We state “true” for each of these parts to add complete borders.


Now let’s change the font to Calibri and set its size to 11 to all the data.

Line 20: To set the font we use the setFontFamily method and add the name of the font in the brackets. Make sure you use a capital letter, otherwise it won’t work properly. To set this to all the data, we add it to the range stored in allCells.

Line 21: To set the font size, we use the setFontSize() and in the brackets state the size.


Line 24: In the comments column I want to set a smaller font size, so, I use setFontSize() with the commentsColumn variable, which contains the comments column range.


Line 27: Now let’s set the text so it wraps if it is long. We use the setWrap() method with the header row. To activate it, we put ‘true’ in the brackets.

Line 30: I also want to do the same, for all the comments column, so I use the same method with the commentsColumn variable.



Now, let’s set the alignment of the text, both horizontally and vertically.

Line 33: First, I want to set the horizontal alignment for all the data to “center”. We use the setHorizontalAlignment() method and state the type in the brackets.

Line 34: Then, I want to set just the horizontal alignment of the comments column to “left”.


Lines 39: We do the same for the vertical alignment, using the setVerticalAlignment() method. First, we set the vertical alignment of all the data to ‘middle’.

Line 41: Then we set the vertical alignment of the header row to ‘bottom’.


Now let’s set the number format for the class number and dates.

Line 45: First we get the class column via getRange().

Line 46: Then we use the setNumberFormat() methods to that range and state the format in the brackets. In this case, I want the class number to be two digits, so that ‘1’ will be ’01’. To do this, we use the format “00”.

Line 49: Then we get the columns which contain dates, which are from column 8 (column H) to column 18 (column R). Note, we start from column 8 and then including this one, include 11 columns to the right.

Line 50: This time I want the date format to be “dd/mm”, for example, “20/03”.


Line 53: Now let’s add bolding to the header row. We use the setFontWeight() method to do so, and in the brackets, add “bold”.


Now I want to colour header cells in different colours. First, I generally find it easier to state the hexidecimal codes beforehand and assign them to easy to recognise names. Plus, if different cells are using the same colour, it’s easy to change the colour by just updating the hexidecimal code for that colour.

In the file, I’ve added a sheet which contains all the hexidecimal codes for all the default colours in the colour palette.

Lines 56 to 63: I set up the variables as below. Remember to include the hash.


Then we are ready to set the different colours to the backgrounds of the cells.

Line 65: I want to set the first 6 cells in the header to be blue, so first I get the range.

Line 66: Then I set the background colour, by using setBackground() with that range and in the brackets refer to the colour in the variables we set up beforehand, here’s it the variable blue.

Lines 68 to 96: I continue in a similar way across the sheet.


One thing I like with Sheets, is the ability to delete unwanted rows and columns. We could hard code this, in other words, state the rows or columns we want to delete, but let’s get the code to work out which rows and columns don’t have data in them, and thus, delete those.

Line 99: First, we need to find out how many columns there are in the sheet. We use the getMaxColumns() to find this out. I’ve stored this in totalColumns.

Line 100: Then we do a bit of maths, we take the number of columns that contain data (stored in the variable numColumns) away from the total number of columns (totalColumns). I’ve stored this in numOfColumnsToDelete.

Line 101: Now we delete the empty columns. We use deleteColumns() and in the brackets there are two parts: the column position we’re going to start at, and how many columns we’re going to delete. The first is the variable numColumns + 1, as we don’t want to delete the last column with data but the one to the right of it. the second, we use the variable numOfColumnsToDelete which we worked out before.


Lines 104 to 106: We follow the same process for the rows, just we use the equivalent row methods and variables.


The final thing I want to do is change the column widths. The first 7 columns I want to adjust automatically according to the data in them.

Line 109-111: Here we can use a simple for loop to resize each of the columns in turn. I start the variable i at 1 (column 1) and it goes up by 1 each round of the loop until it reaches 7 (column 7). More info on loops here.

Line 110: Each round of the loop, it will automatically resize the column based on the widest piece of data in that column. The first time in the loop the i in brackets will be 1, which refers to column 1.


Line 114-116: I use a similar loop to adjust the widths of columns 8 to 18 to ’60’.

Line 115: This time I’m stating the exact width, so we use the setColumnWidth() method. In the brackets we need to state the column number and the width.

Line 119: Here I want to make the width of the comments column ‘120’, so I’ve used the variable numColumns to identify the comments column, as I know it’s the last column, then add the width ‘120’.


SCRIPT 2 – Hiding rows and columns

Sometimes we have a sheet which is full of data but we’re just interested in a part of it and only want the relevant parts showing. In this little script, I just want to show how easy it is, to set up a sheet showing just the rows and columns you want, by hiding the unwanted ones.

As in the first script we get the active spreadsheet and Sheet 1.


I want to only show term 1 information, so I hide columns L to R as they refer to term 2 and 3.

Line 6: First, I need to get the range of columns use getRange(). Note, the range refers to the first row, which is sufficient as it by default means it includes the columns we want.

Line 7: Then we use hideColumn() to hide those columns.


This time I want to hide the rows with the French classes, so I need to hide rows 4 and 10.

Lines 10 and 11: Similar to above, I get the ranges, and here including just column A is fine, it just needs to include the row number.

Lines 12 and 13: Then we use hideRow() to hide the respective rows.


So, potentially at a click of a button, we can hide the rows and columns we want, leaving just the relevant information.

SCRIPT 3 – Unhiding all rows and columns

This is great, but we will want to reset the sheet to display all the rows and columns.

As before we get the active spreadsheet and Sheet 1.


Line 6: First, I want to unhide all the columns, so I will need to know how many columns there are. So, here I’ve used getLastColumn().

Line 7: Then, we need to get the full range of columns, using getRange() and getting from column 1 to the number of columns in numColumns.

Line 8: Then, we use unhideColumn() to unhide all the columns in the range.


Then we do the same for the rows. The only difference is we’re referring to rows not columns, so we use the respective methods.


Here’s the link to make a copy of the sheet and code:

In the spreadsheet, you will find 4 sheets.

‘Sheet 1’: the sheet that the scripts work on

‘unformatted’: a copy of the unformatted data

‘formatted’: what the data looks like once formatted

‘ColourRefs’: As a bonus, I’ve added the hexadecimal references for all the default colours in the colour palette in sheets. In the script edittor, you’ll find a fourth script, which is what I used to get those reference numbers.

Here are the full scripts:






eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Follow my Google+ Learning Google Apps Script Collection

Baz Roberts (Flipboard / Twitter / Google+)

Book inventory

Here we’re going to make a simple book inventory, where we’ll be able to control the location of the books and also find out where a book is. This uses a mixture of GAS code and Sheets functions.

We’ve got 3 sheets:


Front page – This is what the user will see and use. In the yellow part, we’ll be able to find out the location of a book from its code. They enter the book number in cell B4 and the current location appears in B5. In the green part, we’ll be able to change the location of a book or in fact up to 10 books all in one go. The user adds the book numbers in cells B11 to B20 and enters the location in B10. Then from the menu they’ll run the program to update the list.


Coursebooks – On this page we have titles of the books, level, which part of the set it is (e.g. SB: student’s book), its individual reference number, and its current location. Columns D and E are what we’re going to use.


Ref – This page is used to create the drop down menus on the Coursebooks page using data validation.


In cell A2, I want to list the titles of the books we have on the Coursebooks page, but without duplicating any. So, I use the UNIQUE function and combine it with the SORT function to sort it into ascending order.


In column C I’ve listed the possible levels. In column E the possible type of book.

In column H I write the possible locations of the book, then to save me having to sort this list alphabetically every time I add a new person or location, I’ve added in cell I2 the SORT function below, to sort it alphabetically automatically and this is what the Coursebooks sheet will use.



There are two pieces of code here, one runs when the file is opened, and the second runs when the “Update locations” is selected from the “Books” menu.


It’s going to create a menu, clear any unwanted previous information on the Front sheet, highlight cell B10 to save having to click on it, and display a message when it’s finished.


Here’s the code step-by-step:

First, I start with a function and call it onOpen(). This tells the file to do something when the file is opened.


Line 3: Then I get the current spreadsheet and store it in the variable ss for later use in the code. Note, that I’ve added a comma at the end of lines 3 & 4, so that lines 4 and 5 are part of this.

Line 4-5: This states the name of the menu item and tells it which function to run, when someone clicks on it. This is stored in the variable menuItems and is stored as an array, so you need to make sure you enclose it all in square brackets and as these are menu properties, you’ll need to enclose them in curly brackets too.

If you use this code for your own projects, just copy lines 3 to 6 and change the text parts in inverted commas.

Line 6: This adds the menu to the spreadsheet. The first part “Books” is the name of the menu you will see on the menu bar, then the second part refers to the information in Lines 4-5, i.e. the menu item and associated function to run.

The rest of the code sets the sheet up ready to be used.


Line 9: When the user opens the file, I want them to start on the first page.  To select that specific sheet I want, I use the getSheets method and state that I want sheet 0. The getSheets method gets all the sheets in the spreadsheet and puts them in an array. This is why the first sheet is sheet 0, as arrays start with 0 (i.e. are zero-based). Now it’s got sheet 0 the setActiveSheet method makes the sheet I want the active one.

Next, I want to clear any book numbers or location that may have been previously entered in the range B10 to B20.


Line 12: First, I get the range I want using the getRange method, and here I enter the range between inverted commas.

Line 13: Then I get the range and use the clearContent method to clear it.

As the user more often than not, will want to fill out the new location in B10, I want the sheet to highlight that cell when they open the file, so they don’t have to click on it.


Line 16: First, I get the cell range using the getRange method and store it in the startCell variable.

Line 17: Then, I make that cell the active range, using the setActiveRange method and refer to the variable startCell where the range is stored.

Finally, just to tell the user the sheet’s ready to use, I want to display a little toast message, which is a pop-up message that appears in the bottom right-hand corner of the screen.


Line 20: This uses the toast method and has 3 parts to it in the brackets. The message you want to display, the title of the message, how many seconds you want the message to display.


Now let’s look at the second piece of code. We could add it at the bottom of the code we’ve just seen, but I often keep different functions separate just so it’s easier to read and to find the specific code you want.

First, let’s change the first one from “code” to “onOpen”. On the left of the screen you’ll see the list of script files you have for this particular project. To do so, just click on the little triangle next to the title and choose “Rename”, then type in a new title. You don’t need to add .gs at the end, it’s added automatically.


Now, let’s create a new Script file. Go to File>New>Script file.


Then, type in the title of the Script file and press OK.


You’ll see both script files on the left-hand side of the screen. Now, we’re going to work with the enterBookLocation one, so click on that file to open it up in the editor.


Here’s the complete code we’re going to use:


As before, let’s go through it step-by-step. First, start off with a new function called enterBookLocation and in line 3, let’s get the active spreadsheet.


Now, we want to get the details entered on the Front page, i.e. the location and the book numbers.


Line 6: We get the sheet called “Front page” using the getSheetName method. This is stored in the frontpage variable. Note, I could have used the getSheets ()[0] method as in the other code, but I just wanted to show an alternative method.

Line 7: Here we want to get the location entered, so we get the value in cell B10. Note, we’re using the getValue() method as it’s just one cell.

Line 8: Here we want to get the range of cells from B11 to B20, in case the user has entered up to 10 books. This time we use the getValues() method as there are 10 cells and this stores the values in an array variable, which we will call books. We will use this to find the books in the inventory.

Now, we need to get the list of books on the Coursebooks page.


Line 11: First, we get the sheet called “Coursebooks” with the getSheetByName method and store it in the coursebooks variable.

Line 12: Then we need to get the last row so we are able to get the list of books from the top to the last one on the list. To do this, I use the getLastRow() method.

Line 13: Now we can get the book numbers in column D. So, first we get the range using getRange and in the brackets we put the following information (starting row, starting column, number of rows, number of columns). So, here we start from row 2, column 4, down to the last row, and we only want this 1 column. Then we need to get the actual values in that range, using getValues(). So, now we have an array variable called bookNumbers which contains all the books.

Line 14: As we want to update the locations, we need to get the range of locations too. So, as before we use getRange and this time get column 5. We only need the ranges and not the values in those ranges, so we don’t need getValues(). We store this in the variable locationRows.

I want it to get a book number from the Front page then look it up on the inventory, and once found will get the corresponding location and change it to the one entered on the Front page. Then it will get the next book number and do the same until it comes across an empty cell on the Front page, when it will stop and go to the end of the code.

First, we need to set up a for loop which will go one by one down the list of book numbers on the Front page.


Line 19: We start with for and in the brackets we put 3 pieces of information:

(starting number of the variable; the condition which while it’s true will continue the loop; how much we’re going to increment the variable by each time it loops)

In this case, the variable bn starts at 0; it will continue while it is less than 10; every time it loops around the variable bn goes up by one (++ means +1). Then in curly brackets we put some kind of action. I’ve opened the brackets in line 19 and the closing bracket is on line 36. So, all the lines between 19 and 36 are included in this loop.

Now we create a second for loop to go down the book list and look for a matching number.


Line 22: The for loop is similar to the one above, but this time I use the variable RN (row number) and use the variable numRows as the condition, so that it will start at the top and go down the list one by one to the bottom. The open curly bracket is on line 22 and the closing one is on line 35.

Line 23: Every time it goes down the list we want it to check to see if the current book number on the list (stored in the bookNumbers variable) is the same as the book number on the Front page (stored in the books variable). To do this, we use an if statement. We put the condition we want to check in between brackets.

Here we get the value from the array variable bookNumbers and at position RN in our array. The zero is because our array is only one line of values, so the number in the second square brackets will always be 0. So, the first time it loops, it gets the value in the variable bookNumbers at [0][0], which is the first book on our list.

Then we use the triple equal signs to show we want the bookNumbers value to equal the books value. Now we need the books value at position bn, 0, which is the first book number, i.e. The value in cell B11.

Next I’ve add a second if statement to check to see if it has come to the end of the list of book numbers entered on the Front page, as this will speed up the program as it won’t continue to loop down the range B11 to B20 if it comes across an empty cell.


Line 26: Here it’s checking to see if the value in books is equal to “”, in other words, an empty cell.

Line 27: If it does, it “breaks” out of the loop, which means it doesn’t continue the loop and goes to the next part of the code after the loop (in this case line 37).

If there is a value and it the book number matches a number on the list, it performs the code in lines 31 to 33.


Line 31: Here we want to get the cell where the values match. This uses the getCell(row, column) method. For the row we use the variable RN and add 1 to convert it from its array position to its actual row on the sheet. The column is 1 as we are only using 1 column.

Line 32: Then we need to make that cell the active one and set its value with the new location. So, we get the sheet coursebooks, use the setActiveSelection method and use the location we just got in line 31. Then we set the value with the value from the location variable from the Front page.

Line 33: As it’s found a match, it doesn’t need to continue looking down the list, so we break out of the current loop by using {break}. Again this is to speed up the program.

Lines 34 to 36: These contain the closing curly brackets for the loops above.

The loop continues updating the location of each book until it comes across a blank cell in range B11 to B20.

Finally, once it’s updated the locations, I want it to return back to the Front page and highlight cell B10 again ready for future use, using similar code to what we saw in the onOpen function.


Line 38: This gets the first sheet.

Line 39: This gets the cell B10.

Line 40: This sets that cell as the active one.

Line 41: Closes the function.

Program in action

The user enters a new location (often a person) in cell B10. Then enters the books they want to link to that location. Here I’ve added two.


They click the Books menu, then Update Locations.



As we can see on the Coursebooks page, it’s updated book 0002 and 0005 to Paul. So, you can imagine with an inventory with hundreds or thousands of books, this will save the user a lot of time, as they can update up to 10 books in seconds, rather than scrolling up and down the list looking for the books to update.


Finding a location of a book

In the yellow part I’ve added a quick way to find out where a book is by entering the book number. This is particularly useful when a book is found and we want to find out whose it is.

The user types in the book number in cell B4 and the location appears in B5.


There’s no coding involved with this one, just the following VLOOKUP and IFERROR function. Using the VLOOKUP function, this looks up the number enters in B4 in column D on the Coursebooks page and gets the corresponding location from column E. If the book isn’t on the list, it comes back with the message “Book not found” by using the IFERROR function.


Here is a link to make a copy of the file, which contains the code above.

I did think of writing a program to enter new books but to be honest we found it was just easier to add the book to the bottom of the list using the drop down menus to speed up the process.

If you want to know more about how if statements and for loops work I recommend reading the information on the W3Schools site:

For loop

If statement

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Follow my Google+ Learning Google Apps Script Collection

Baz Roberts (Flipboard / Twitter / Google+)

Automatically emailing info from a form submission

Here we’ll look at how to set up automatic emails, which contain information submitted by the Google Form user. The beauty of this is that the information is sent to you (and others) without you having to do anything and without you having to check the spreadsheet to see if there has been a submission. This is building on the code from my previous post, so the areas which are the same I will only briefly describe here. If you want more details see my previous post.

This is the email that we’re going to send automatically:


Here’s the full code:


Let’s look at it step by step. First, we set up the function and then get the active spreadsheet.


Then we get the sheet which contains the information we want, which in this case is on ‘Form Responses 1’.


We want to get the values on the last row, which is the latest form submission. There are 3 steps to do this. We need to get the last row on the sheet, the last column and then get the range using that information.

Line 10: We get the last row of the form responses 1 sheet (using the getLastRow method) and store it in the variable called lastRow.

Line 11: Similar to the lastRow we get the last column (using the getLastColumn method) and store it in the variable lastColumn.

Line 12: Now we want to get the values in the last row, from the first column to the last column. We do that by getting the range (using the getRange method). This takes 4 pieces of information; starting row, starting column, number of rows, number of columns). So we start on the lastRow, we start at the first column, we only want 1 row, and we want to collect data until the lastColumn. Then we add getValues to get the values in that range.


Now we have an array variable called lastRowValues which contains all the data from that last row. Now let’s assign a variable for each piece of information on that row, so that we can use the variables later in the code and so we can clearly see what’s in those variables.

Lines 15-21: Here we’re extracting a specific bit of information from the lastRowValues array, and so need to state the position of the information. The array contains this at the moment:

[29/01/2017 15:43:01], [Ian Student], [], [123456789], [Mon/Wed 17:00-18:00], [06/02/2017], [31/03/2017], [To prepare for the CAE exam.]

There’s only one line in our array, so all the values we look up will start with position [0] . Then, for example, the name “Ian Student” is at position [1] in the array (remembering that the first item is [0] in an array). So, we write lastRowValues[0][1]; to get the name.

We then repeat the same format for each piece of information we want, just changing the position in the array each time.


In lines 19 and 20, we’re getting the dates from the form submission and we could use them directly from the sheet. The problem is that they won’t be in the format they are on the sheet. They will look like this:


Not the easiest date to read and normally we don’t want the full date, so let’s change the format of the date to the way we want it. For this email, I want the format to be DD-MM-YYYY, e.g. 06-02-17.

Line 24: First I check the user has in fact inputted a date. So, I write an IF statement to check if the variable startDate isn’t empty. The If statement syntax is:

if (variable + logic) {do something if it’s true}

!== means not equal; so I’m checking to see that the variable startDate isn’t equal to empty “”. If there is a date, lines 25 & 26 are run, i.e. the parts inside the curly brackets.

Line 25: First, let’s get the month from the date and add 1. This is because January is 0, so we need to add 1 to return a real month number. We use the getMonth() method to extract the month. Then we store it in the month variable.

Line 26: Now we need to create the date format we want. There are 3 main parts, date (day number), month, and year. To get the date, we use the getDate() method, then we add a hyphen, then we add the month we extracted in line 25. Then we add another hyphen, then we get the year with the getFullYear() method. This is then stored in the startDateEdit variable. So, it will create a date like this: 06-02-2017.

Line 28: What happens if the IF statement returns false, well I want it to put a few dashes instead of a date. Here we use the ELSE statement, which has the same syntax as the IF statement and will run if the above IF statement is false. So, here I just set the variable startDateEdit as three dashes “—“.


The finish date follows the same pattern as above.


I also want to include the spreadsheet URL, so I get that by using the getUrl() method and store it is the variable urlOfSS.


Now let’s create our email with the information we’ve extracted above.

Line 44: We set up the variable message, to store the information and in the first line type a short introduction. Note, at the end of every line from line 44 to 54, we have to add a “+” to show every line is connected together, and everything is within the message variable. Also, in the email, I want to list the details, one line after another, so we use “\n ” to add a line break.

Line 46: To add the name, next to the line break we type the text “Name: ” and close it with the inverted commas. Then we need to add the variable name, putting pluses (+) either side.

Lines 47 to 54 are written in a similar way. The final line of the email, line 55, ends with a semi-colon and doesn’t have a plus.


Now, we get the email addresses from our list on the sheet called “Emails”.

Line 58: Use the getSheetByName() method to get the sheet. Then we store it in the emails variable.

Line 59: Then we get the last row of the emails using the getLastRow() method and store it in the variable numRows.

Line 60: We get the values in the range using the methods getRange and getValues.


Here it’s getting the range from row 2, column 2, to the bottom of the list of emails, and only this 1 column.


Then we set the subject line of the email. Here I’ve added a fixed part in the title “Private Class Request – ” then added the variable name, so that the person’s name of that particular request will appear in the subject line. This is stored in the variable subject.


Then we send the email using the Gmail class and sendEmail() method. Then add the 3 variables, emailTo (the email addresses), subject (the subject line), and message (our email message).


Remember to activate the onFormSubmit trigger, as explained in my last post.

As this has touched on JavaScript areas such as, arrays, if, and else statements, if you’re not confident using these, I would suggest you read the following on the excellent W3schools website:

arrays / if else

Here’s the link that let’s you make a copy of the spreadsheet I’ve used, which will copy the spreadsheet, the linked form, and which contains the above code.

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Follow my Google+ Learning Google Apps Script Collection

Baz Roberts (Flipboard / Twitter / Google+)

Request form – Sending automatic emails

One of the most useful things I’ve learnt to do with Google Apps Script, is to email people automatically when a form is submitted. It has countless uses and in this example, we have a user requesting a private class via a Google Form. The relevant parties will receive an email which will contain a short message and a link to the sheet containing the details.

There are three parts to this:

  1. Setting up the Form (inc Email collecting and Data validation)
  2. Setting up the Sheet
  3. Writing the code

Part 1 – Setting up the Form to record details of class request

Open the Sheet and select Tools>Create a form, so that it is automatically linked to this spreadsheet.


To set up a question to collect the user’s email. Click on “Settings”.


Click “Collect email address. If you want the user to receive a copy of their request, then click “Response receipts”. You then have a choose of always sending them a copy or letting them choose this option in the Form. Here I always want them to receive it. Click “Save”.


Back in the Questions part, you can see it’s automatically created a question which will check for a valid email.


Then I add a telephone question with a short answer. Here I want it to check the user has only entered numbers. Click on the 3 dots on the bottom-right of the question, and then click “Data validation”.


I then change the data validation option to “Number”.


Then click on “Greater than” and change it to “Is number”.



Then I add start and finish date questions. With the new AI, when I type “Start date, it automatically changes the question type to “Date”.



Finally, I add a Details questions, which automatically changes it to a Paragraph style question.


Part 2 – Setting up the spreadsheet

Open the spreadsheet and on the ‘Form Responses 1’ tab you’ll see the questions in row 1. I like the fact that the sheets with a form linked to it, now have a GForm symbol on it.


Add a second tab and rename it ‘Emails’.


In that sheet, type in the email addresses you want the request to go to. In column A I’ve added their names and in column B their emails.


Part 3 – Writing the code

In the Sheet, click on Tools>Script Editor.


Click on “Untitled project” and give it a new name.


Then click “OK”.


We want the program to run when there’s a form submission, to get the spreadsheet URL and then email a group of people a short message telling us there’s a new request, including the URL, so we can easily click on it to open the sheet to see the request details. Here’s the code we’re going to use:


Let’s look at the code section by section. Note that you don’t need to add the comments (the parts after //) but it can help you follow the code and remember what each bit does.

First we set up a function called onFormSubmit() and open the function with the curly brackets.


Then we want to get the active spreadsheet. Here we set up a variable called ss, which we will refer to throughout the program. Then we use the SpreadsheetApp class with the getActiveSpreadsheet method.


Now we want the spreadsheet URL. Here we set up a variable urlOfSS, which is where we will store the URL. Then we use the ss variable we just created and use the getUrl() method to get the URL.


Now we need a message in our email.

Here let’s create a variable message to store the message. We put the text we want within inverted commas, as it is a piece of text. I also want to add the URL, so I add a + at the end to show it’s connected to the text.

Then I want to put the URL on a new line, so I add “\n “ which in HTML is a line break. Then I add another + and add the variable urlOfSS.


Now we want to get the email address of those we’re going to send the email to. Let’s look at this line by line.

Line 17: Using the getSheetByName method we get the Emails sheet. Put the name of the sheet in the brackets between inverted commas. We’ll store this in the variable emails.

Line 18: We want to get the last row on the sheet that has data in it. Assuming the sheet is set up as describing in part 2 above, then this will tell us that the last row is row 3. This uses the Sheet class followed by the getLastRow() method. We store this in the variable numRows. We’ll use this info to get the correct range in the next line.

Line 19: Now we need to get the email addresses in sheet. We use the Sheet class again and this time get a specific range by using the getRange method. This has 4 parts: starting row, starting column, number of rows, number of columns, So, in our example, we’re:

  • starting in row 2
  • starting column 2
  • getting the number of rows from the variable numRows (which in this case is 3)
  • there is only 1 column

So, in the brackets we write (2, 2, numRows, 1)

Important: This will get the ranges but not the values within those ranges. So, we need to add another method, getValues() which will stored the actual email addresses in the variable emailTo.


Now we need a subject line for our email. We store the text we want in the variable subject.


Finally, we of course want to send the email. Here we use the GmailApp class followed by the sendEmail method. In the brackets we add the three pieces of information we collected earlier:

  • emailTo (Email addresses we’re sending to)
  • subject (Email subject line)
  • message (Email message)

To close the function we always add a curly bracket at the end.


Note that all the lines except lines 2 and 26 have a semi-colon at the end.

This program uses the onFormSubmit trigger, which allows the program to run automatically when a Form is submitted. This needs setting up, otherwise it won’t run automatically, but it’s simple to do.

In the Script Editor, click on the clock icon.


This will open the Current project’s triggers menu. A new program won’t have any set up. Click on the blue “No triggers set up. Click here to add one now.”.


Here you need to tell it what to run and how to run it. All of these are simple drop down menus. By default the onFormSubmit program will be selected. Now let’s add the event details.


Click on “Time-driven” and change it to “From spreadsheet”. This will change the other menus.


Click on “On open” and change this to On form submit.


You should be left with this. This runs the function “onFormSubmit” when a form is submitted to the spreadsheet. Click “Save”.


Forgetting to set up the trigger is a common mistake to make.

Here’s the email that is sent out.


Here’s a link to make a copy of the above spreadsheet. The linked form will be copied automatically and the code will already be in the Script Editor.

The email in this post is quite basic and performs a similar function to the automatic email you can receive when setting up Notification rules, found in the Tools menu. However, this does allow you to email a group of people, plus it’s a springboard for my next post, which will focus on how you can extract data from the latest form submission and include it in the email.

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Follow my Google+ Learning Google Apps Script Collection

Baz Roberts (Flipboard / Twitter / Google+)

Google Sheets Functions – QUERY

The QUERY function is in a category all on its own. It’s an extremely powerful function that will let you filter, sort, group, pivot, basically extract data from a table and present it in numerous ways. At first it can look daunting, with its own language and syntax, but once you dip your toe into the QUERY pool you’ll realise that things are not so complicated and that with just one function, you can extract and analyse your data with ease.

As always the best way to learn how to use it is through examples, and in this post we’re going to use two main sources of data, some questionnaire feedback, and some data from a HR department, building the complexity up step by step.

Analysing questionnaire feedback

Here we’ve used a Google Form to collect feedback on the teachers, the classrooms, and admin information at the end of every course in an academy. Each row is a student’s piece of feedback. They grade the various criteria from 1 to 5, 5 being ‘excellent’. Below is a snippet of that table of data.


In the next few examples, we’ll see how easy it is to analyse this data, each time with just one QUERY function.

Example 1a – Selecting the relevant data from the master data

The head of studies wants to look at the feedback for her teachers, and she doesn’t need to know the classroom feedback or the admin feedback. So, the info she needs is from column A to H, as shown below:


In cell A1 on a different page, I’ve written the following QUERY function:


There are 2 main parts to a QUERY function, 1) the data range, 2) the query

So, in the first part (in orange) we look at the page called “Questionnaire” and range A1 to column N (note this is an open-ended range as we will receive more entries in the future).

In the second part we tell the function what to select. So, in this example, we want columns A to H. We add the column letters followed by commas. The query part always needs to be within speech marks, so we put it before select and at the end before the bracket.

“select” is one of the keywords within the QUERY language which tells the function what to do. Here are some of the other ones, most of which we will see in the following examples.


Example 2a – Filter by a teacher’s name

Now the head has decided that he wants to look at the feedback of a particular teacher. She wants the following information:


Here’s the QUERY formula I entered in cell A2:


It’s the same as before except that at the end I’ve stated a condition:


This looks at column C and returns anything that matches “Fred”. I.e. it only returns the feedback for Fred.

Example 2b – Filter by a teacher’s name using a cell reference

The head has decided that she doesn’t want to have to change the teacher’s name within the formula every time she wants to look at the feedback of a different teacher, she wants to enter the teacher’s name in cell B1 and wants the formula to update accordingly.

That’s no problem, although the syntax looks a little ugly. Here’s the formula:


At the end, instead of the name “Fred” I’ve put the reference to the cell. In QUERY function you have to use this syntax: ‘”&B1&”‘ basically so it knows it’s a cell reference.


As you can see it produces the same information as before and now if the head wants to see another teacher’s feedback she only needs to change the name in cell B1.


Example 2c – Filter by a teacher’s name and sort the date in descending order

By default, the data is sorted from the oldest date to the most recent, but when there is a lot of data this means that to see the most recent and probably most relevant data, the person has to scroll down. We can remedy that easily by sorted the data by date in descending order, as we can see below:


To achieve this, I’ve used the same formula as before except at the end I’ve added an ORDER BY part:



This orders (or sorts) column A (the dates) and the ‘desc’ tells it to do it in descending order. If you want to tell it to do it in ascending order, write asc.

Carefully note the syntax, as one tiny error will stop this from working.

Also note that, the QUERY results aren’t formatted and the column widths aren’t automatically adjusted. This needs to be done manually either beforehand, or afterwards.

Example 3a – Filter the data between 2 dates

The head also wants to be able to filter the data for a particular period of time, e.g. september 16. As you can see below, the data has been filtered between 1/9/2016 and 30/9/2016.


Here’s the formula I’ve added in cell A2:



The new part is at the end. First, we tell it to look in column A for a date bigger than or equal to the date in inverted commas. Make sure you add the word date, to tell the function that you’re looking for a date and not some text.


Then in the next part, we add ‘and’ to tell it to look for 2 criteria. Then tell it to look for a date less than or equal to the date in inverted commas.

Note that with dates you need to write the date in the following format:


Even if the date format in your sheet is different as it is in my one.

So to summarise, it gets columns A to H, and returns rows that meet the 2 criteria, i.e. 1/9/2016 to 30/9/2016.

Example 3b – Filter between 2 dates using cell references

As we saw earlier we can replace the actual dates in the formula with cell references.


The only thing is that to do this we need quite a complex looking formula. Here’s the formula I’ve added in cell A2:


Here’s the formula broken down into its component parts:




To replace the actual date we need to use the following formula after the word ‘date’:


This gets the date in cell D1, puts it into the correct format. The same goes for the second date:


If anyone reading this knows of simplier syntax to do the same thing, I’d love to hear it, as I couldn’t find on-line a better way to do this, but I’m open all ears!

Example 3c – Filter between 2 dates and by teacher

Finally, the head wants to filter the feedback between the two dates, by a specific teacher, and order the feedback by date in descending order, as you can see below:


As you can see the formula is getting pretty long, but you can also see that it’s made up of parts and you can extract what you want by adding extra parts.


Here’s the query part broken down:


It gets columns A to H.


Finds rows where the date is greater than or equal to the one in cell D1.


And that also is less than or equal to the one in cell F1.


AND where the name in column C is the same as the name in cell B1.


Then order the results by the dates in column A in descending order.

Example 4 – Filter against various criteria

Here the admin manager wants to use the questionnaire feedback to see how good the information is that is given to the students when they sign up and how well the service was in the office. He particularly wants to know if there was any low feedback in any of the areas under his control, so wants to know if the course info, payment info, or office service was rated less than 3 by anyone.


To create the table above I’ve added the following formula in cell A1:


Here I’ve selected 5 columns and notice that I’ve put column B at the end. This shows that when selecting the columns, you don’t have to have them in the same order as the original data. This is extremely useful at times.

In the second part, I set the criteria, i.e. he’s looking for values which are less than 3 in each of the columns, L, M, and N. To include 3 different criteria, I’ve used the ‘or’ keyword, so that it will return rows if any one of them have a value of less than 3 in it.

As we can see in the table above, it’s found 3 results where the course information was rated poorly, and we can see from the level that it was related to level B1, so clearly some work is need there. Plus, there is one incident where the service in the office was rated poorly, which may need investigating.

Example 5a – Returning the averages of data

The director of the school wants to know if there are any classrooms that are rated more poorly than others. He wants to see if the average rating is different for any of the classrooms. In the table below, we can see that clearly, there is a problem with class A1, as it is rated poorly and much lower than the others.


To do this, I’ve added the following formula in cell A1:


This time I’m interested in the classrooms in column I and the average of the scores given in column J. First, I select column I, then select the average of column J, then I group them by classroom, in other words by column I.

We can return the average, count the number of entries, return a maximum or minimum in that column, or sum up the entries, using the following syntax:


Quite often these work with the ‘group by’ keyword, to be able to return the results.

Note, there is an empty row in row 2, as the data range is looking below the original data and into empty rows, and it will return one. This can be eliminated by stating the exact range of your data as we will see next, but the downside is that if more data is added the range will have to be updated.

Example 5b – Returning the averages of data and ordering them

Following on from the example above, we can adjust our returned information by sorting the feedback by the lowest to the highest, i.e. in ascending order. Here we can see class A1 is the lowest rated.


Here’s the formula I wrote in cell A1:


The first part is as before, then it’s followed by:


This orders the results by the average of column J (in column B), in ascending order.

Example 6 – Pivot information using QUERY not pivot tables

To finish off this first part, let’s look how we can pivot the information to see the averages of 2 criteria for each teacher. We want to look at the “is clear” and “is organised” categories. If you’re familiar with pivot tables, this works in a similar way, but with the bonus of doing everything right within the QUERY function.

Here the data has extracted the information below. It looks like Fred’s class organisation may need improving a little.


To get this output, I’ve written the following formula in cell A1:


This selects the average of column D (“is clear”) and the average of column E (“is organised”) as the criteria, then pivots it by teacher (column C), so that we see an average of each criteria for each teacher.

We could look at every criteria per teacher, just by adding the average for each criteria column, e.g. avg(F), avg(G), etc.

Analysing a HR database

OK let’s look at a different set of data now. Here we have employee database with some information about them.


Example 7 – Returning average salaries per department

The HR director wants to know what the average salary is per department from the data above. Here’s the end result:


In cell A1, I’ve written the following formula:


This selects the departments (column B), and the average of the salaries (column C) grouped by department.

Example 8a – Listing salaries per employee in descending order

Here he wants to see the salaries per person in descending order, without any of the other information.


In cell A1, I’ve written the following formula:


This selects columns A and C, and sorts column C in descending order.

Example 8b – Limiting the number of results

The HR director actually only wants to see the 5 highest salaries. We can use the formula and add a limit to it, to show the following:


Here’s the formula:


In the last part I’ve added ‘limit 5’. This returns the first 5 rows.

Example 9 – Ordering by more than 1 criteria

Let’s now look at how we can order our results by 2 or more criteria. Here the HR guy wants to see the employee names, their departments and salaries. He wants the data organised by department then by salary, with the salaries going from highest to lowest.


To do this, I’ve written the following formula:


This selects columns A, B, and C (employee, department, and salary), orders first by department (B), then by salary (C). Note the syntax, after ‘order by’ you just add the first column letter, then the second one after a comma.

Example 10 – Relabelling column headers

Finally, let’s look at how we can rename the column headers to something different from the original data. This can be useful, if the original data is from a computer output and the column headers aren’t in everyday English, or you may simply want to change them.

Here I’ve changed the column “Employee” to “Name” using the QUERY function.


To do this, yes you’ve guessed it, I’ve added the following formula in cell A1:


The new part is at the end, (label A ‘Name’). This tells it to rename column A with the word ‘Name’. To add more labels, just add a comma and the column letter and new name.

A couple of final comments about QUERY. Be careful where you place your QUERY function, as you need to make sure that there is nothing in the cells particularly below it, as otherwise it’ll throw an error.

The syntax is very exact, so make sure you notice in the examples, how the punctuation is used.

If you want to play around with the data in this post, here’s a link to the sheet, which will prompt you to make a copy of it:

Despite this being a long post, I’ve only scratched the surface as to what QUERY is capable of. To learn more go to Google’s page on the query language:

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Baz Roberts (Flipboard / Twitter / Google+)

Google Sheets Functions – IMAGE

In this post let’s look how we can insert images into our sheets. There are two main ways, either inserting the image via the Insert menu or by using the IMAGE function.

Example 1 – Inserting an image from Drive

Here let’s add an image from my Drive. Open the “Insert” menu then click “Image”.


Choose where your image is, in this case, let’s choose “Google Drive”.


Search for your image, and click on the one you want, then click Select.


This will place the image on top of your sheet and won’t affect the cells in any way. You can change the size of it by moving the blue squares on the border of your image.


Example 2 – Inserting an image within a cell using the IMAGE function

An alternative way to inserting images, is to use the IMAGE function, which will insert the image within the cell where that function is. To do this we need the URL of the image.

In the cell I type =IMAGE() then in between the brackets I add speech marks and the URL inside them, i.e. =IMAGE(“”)

Here I’ll add an image from my blog:


This adds the image within the cell. A bit small isn’t it?


This is because it has adjusted the size of the image to the size of cell. To make it bigger we just make the row and/or column sizes bigger.


By default, the image is inserted in “sizing mode 1”. So, what does that mean? Well, there are 4 modes and they treat the images in different ways.

Mode 1 – Resizes the image to fit inside the cell, maintaining aspect ratio.

Mode 2 – Stretches or compresses the image to fit inside the cell, ignoring aspect ratio.

Mode 3 – Leaves the image at original size, which may cause cropping.

Mode 4 – Allows the specification of a custom size.

So, let’s look at the modes 2, 3, and 4 in turn.

Mode 2

Taking the same image and formula, but this time just adding a comma and 2 at the end, will squash the image into the cell, and ignore the original aspect ratio, so it now looks too wide.



Mode 3

This time replacing the 2 with a 3, will insert the image as its original size, but if it is bigger than the cell, it will be cropped, as we can see below.



Mode 4

Finally, we can control the height and width we want, but to do this we must use mode 4.

As you can see in the formula, after the 4, we add the height (150) and then the width (120).



Which mode you use is of course entirely dependent on what you want to achieve.

It’s important to note that as these images are within the cells, they are affected by any cell changes, rows or columns added, etc.

 eBooks available on Drive, Forms, Sheets, Docs, Slides, and Google Sheet Functions:

Baz Roberts (Flipboard / Twitter / Google+)

Google Sheets Functions – OFFSET

Sometimes we spend time setting up beautiful spreadsheets only for us to have to add rows or columns afterwards, which then messes up our formulas and we have to change them. In this post, we’re going to look at a couple of examples of the OFFSET function, which will help us create more dynamic formulas. What we mean by this is that the formula will adapt to changes made to the spreadsheets, quite often where rows and columns have been added.

Example 1 – Creating dynamic ranges to maintain an average formula

Here we have some marks for some students. (To those who have been following my posts, honestly, I’m not obsessed with exam marks, they just make good examples!)

In cell B6 I’ve added an AVERAGE function to work out the average of the marks.



But I now have another student to add who’s done the test. I add a row and insert the student’s details, but as you can see this hasn’t changed the average figure.


If we look at the formula, it hasn’t changed despite there being an extra row.


We can solve this by using the OFFSET function in the AVERAGE one.


In cell E6, I’ve added the following formula:


OK, so what’s happening? Well let’s look at the syntax of the function to understand it better.


The OFFSET function has 3 main parts:

cell reference: this is the cell you start from

offset rows: this is the number of rows you move to; positive numbers move down and negative numbers move up. In other words, a positive number increases the row number and a negative one decreases it.

offset columns: this is the number of columns you move to

There are 2 other optional parts, height and width, but here we’re not going to use them.

So going back to our formula:


The OFFSET function starts at E6 which is where our total is.

Then moves up one row to E5 as there’s a -1 in the second part.

It doesn’t move from the column, as there’s a 0 in the third part.

So, this returns the cell E5.

Now we just add the AVERAGE part. It takes the range from E2 to the result of the OFFSET function, which is E5 (E2:E5).

As we can see it returns the correct average like we saw earlier.


Now let’s add the extra student and see what happens. Ah-ha, the average has changed from 8.5 to 8.8, which is what we want.


Looking at the formula, we can see that it has changed subtly, the start cell reference is now E7 (the total) and it’s still moving one cell up, so returns the cell E6. This means the range is now E2:E6, which is what we want.


We can add or delete rows and the average will always be correct, without having to manually change it.

Example 2 – Dynamically calculating the sales for the last X months

Here we have a company’s sales from January to June. The sales manager wants to be able to find out the sales for the last X months. Here he adds the number of months he wants to look back from the last month, e.g. in cell D1 he writes 3. Then in cell D2 it tells him the total number of sales in that period, which in this case is 2,100 (700+600+800).


So, how did we do that? In cell D2 is the following formula:


Let’s break it down and start with the OFFSET function.

B2: This starts from cell B2 (the first month’s sales).

COUNT(B2:B)-D1: Then it counts how many rows (months) there are from B2 to the end of column B. Then it takes away the number of months we want to report back, in this case 3. So it offsets by 3 rows (6-3), so starts from cell B5.

0: It doesn’t move columns.

D1: The height is the figure in D1, i.e. 3 rows. So it takes figures that are from B5 to B8 (i.e. 3 rows).

1: It returns just that one column.

So, when we change the number of months in cell D1, it returns the new number of sales. In this case, the last 2 months total 1,400.


This has the added benefit, like we saw in example 1, that when more rows are added it still works. Now, the sales manager has added the sales for the month of July. As we can see, the last 2 months now add up to 1,800.


eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Baz Roberts (Flipboard / Twitter / Google+)


Following on from my post on the basic date functions, let’s look at some really useful functions that work with dates, namely: WEEKDAY, WORKDAY, NETWORKDAYS, EDATE and EOMONTH, plus we’ll see an example with the CHOOSE function. With these we’ll:

  • Find out the day of the week of a particular date
  • Work out a deadline date
  • Work out how many working days there are between two dates
  • Easily set up start and end of the month dates
  • Work out how many days there are in a month
  • Work out how many working days there are in a month

Example 1 – What day of the week was a particular date?

What day of the week was 1st January 2000 on? No, I couldn’t remember either. Let’s use the WEEKDAY function to quickly find out.

In cell A1 I’ve written the date, then in cell B1 I write the following:


This returns the day number for that date, where Sunday=1 and Saturday=7. So, we can see that 1st January 2000 was in fact a Saturday.


Personally, I find using Sunday as the first day of the week a bit confusing, but you can change which day is the first one by adapting the formula. Let’s make Monday the first day of the week. This time, after the date, add a comma then “2”. This makes Monday the first day.


As we can see in cell B2, it now returns “6”, as Saturday is now the sixth day of the week.


You can make any day the first day, just by changing the number in the formula.

Example 2 – Returning the day of the week as text not as a number

The above example’s great, but it requires you to think of what the number represents. Wouldn’t it be better to return the actual name of the day? Well, that’s easily done by adding the CHOOSE function to our WEEKDAY one.

I write the following formula:


This carries out the WEEKDAY function, finds the day number, then looks down the list of days. So, for the 1st Jan 2000, it will move along 6 spaces down the list and then choose the entry there, which of course is Saturday. Note, each entry needs to be in quotation marks.


The entries can be anything you want. For example, the other day I used this to return the days in Spanish despite using a sheet that was English-based, as we have both English and Spanish speakers using it.

A fun one to do in class with kids, is to find out what day of the week they were born.

Example 3 – Find out the date a number of days from a given date

In this example, a team have 90 working days to finish the project. I want to find out want date that is. To do so, I use the WORKDAY function:


This takes the start date in cell B1 and then adds 90 working days, and returns the end date. So, I can quickly see that they need to finish by 22nd March.


What about the Christmas holidays I hear you cry?! Well, WORKDAY can exclude a list of dates, such as holidays. In range D2 to D4, I’ve listed the Christmas and New Year holidays.


Back in our formula, I need to state where those holidays are, so I just add them after the “90”.


This time I see that the deadline’s moved out a little. Note, it’s only moved by one day, as out of 3 holidays, only one falls on a work day. In the UK, the weekend ones would in fact move to the Monday, but I just wanted to keep the example simple, and in other countries weekend public holidays don’t always move.


Example 4 – How many working days are there between two dates?

I’m looking forward to my Christmas break already and I want to know how more working days there are until I finish for Christmas. In cell B1 I put today’s date, in cell B2 the end of term date.


In cell B3, I write the following:


This takes the two dates and works out how many working days (Mon to Fri) there are. As we can see there are 26 days.

Ah, but in Spain we also have two public holidays on 6th and 8th December, I’m not working then.


We can exclude those from the total by modifying the formula:


This now works out the number of days and subtracts the number of days in range D2:D4. So, it turns out there are only 24 working days. Excellent!


Example 5 – Easily adding start of the month and end of the month dates

In the table below, I want to record how many students have had classes in each month. I need to include the start of the month and end of the month dates as I’m going to use them to filter a master list. Now, I could type in the dates, but with a long list this would be laborious. Instead I can use the EDATE and EOMONTH functions to do it for me.


In cell A2 I write the first date I want, in this case 1/10/2016. then in cell B2, I want to add the end of that month. I do this by writing the following:


This takes the date in cell A2 and then gets the date of the end of the month and as it’s the same month, I write a “0”. If I wanted the next month (i.e. 30/11), I would write “1” and so on.

On the next line I want to add the start of the next month (1/11). So, I write the following:


This takes the date in cell A2 and adds a month to it, keeping the same day of the month, i.e. 1st. In cell B2, I copy the same EOMONTH function as before, i.e. from cell B2.

Now for all future rows I can just copy this row and paste it below. So, for example, cell A6 is =EDATE(A5,1) and cell B6 is =EOMONTH(B5,0).

You can use EOMONTH to return the end of the month of future months, just by changing the 0 to a higher number. For example, in cell B2 =EOMONTH(A2, 3) would return 31/1/2017.

Example 6 – Working out the number of days in a month

In a salary sheet I need to know how many days there were in the month. Every month I type in the month in cell B1 and in cell B2 it tells me how many days are in that month, which I can then use in other formulas to calculate my teachers’ salaries.


In cell B2, I write the following:


This gets the date in cell B1 and gets the end of month date, then subtracts the start of the month (B1) then adds one so it starts with one and not zero.

As you can see it rightly, worked out that in 2016 February had 29 days.

Example 7 – Working out the number of working days in a month

In the same salary sheet, I also need to know how many working days there were in that month. This time I need to combine the NETWORKDAYS function with the EOMONTH function. I write the following in cell B3:


This gets the start date from cell B1, gets the end of the month date from the EOMONTH function and works out the number of working days in between.

So, it correctly worked out that there were 21 working days in February 2016.


There are two additional functions similar to WORKDAY and NETWORKDAYS, these are WORKDAY.INTL and NETWORKDAYS.INTL. These add the extra option of stating how many days of the week are non-working.

eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Baz Roberts (Flipboard / Twitter / Google+)


Google Sheets Functions – ISEMAIL, ISNUMBER, ISURL, NOT

In this post we’ll look at how we can check that emails addresses are in the correct format, numbers are indeed numbers, and that website addresses (URLs) are in the correct format. We’ll start with the basic checking of these and then move on to how we can highlight these using conditional formatting.

Throughout this post, we’re going to use the information in the table below. Here we have information relating to some parents of children at the school. We have their email address, phone numbers and the website where they can access their child’s reports.

We wish to check that their email, phone number and website URL are in formats that can be used.


Example 1 – Checking email addresses

In column B, we have their email addresses (obviously fictitious ones).


To check to see if the format of these email addresses is ok, in column E we write the following function:


This checks that the contents of cell B2 is in fact an email address. If it is, it returns the word “TRUE”. If not, it returns the word “FALSE”. We then copy that formula down the rows B3 to B6.

As we can see, it correctly identified that there is a problem with the last two addresses. We can see that the fourth one is missing the @ symbol and the fifth one is missing something like .com, or at the end.


Example 2 – Checking for numbers

This time we want to check that the phone numbers in column C, are numbers and are not text or contain characters that maybe the computer system can’t handle.


To do this we write the following function:


This time it looks at cell C2 and checks to see it’s a number. If it is a number it returns “TRUE” and if not, it returns “FALSE”.

As we can see it’s found lots of problems. In cell C3, the number has a dash. In cell C4 there are spaces between the numbers. In cell C6, it’s obviously got some letters in there.


In cell C5, this number was entered as text and not a number, as it has a single apostrophe before the number to allow the number to start with a zero.


All of these situations mean that the contents of those cells aren’t numbers, at least in the way Sheets sees them.

Example 3 – Checking website addresses (URLs)

This time we want to check that the URLs in column D are in the correct format.


We write this function in cell G2:


This checks to see that the URL in cell D2 is ok.

As we can see, the first two are ok, but the last three have problems. In cell D4, the URL is missing the .com (or similar). In cell D5, the .com has 2 ‘m’s. In cell D6, the backslash is a forward slash.


Note, that the URLs don’t need the http: part nor the www. part to be recognised as genuine URLs. Sheets also automatically highlights correct URLs as hyperlinks.

Example 4 – Displaying different text depending on whether it’s TRUE or FALSE

In the examples above, our functions were displaying either TRUE or FALSE, which fine for a quick check, but don’t really look that good on your sheet. In the next few example we’ll look at how we can improve that feedback.

Firstly, we can change the wording from TRUE and FALSE to anything we would like. For example, let’s report ‘OK’ if it comes back TRUE if it’s an email, and ‘Not OK’ if it’s not.


In column E, we write the following formula:


This wraps the ISEMAIL function in an IF function. It looks at the content of cell B2 and if it’s true, it displays ‘OK’ and if it’s false it displays ‘Not OK’. See my post on the IF function if this is new to you.

Then we do the same for the other functions:



Now in columns E to G, we can see which ones are OK and which ones aren’t. This makes it a little clearer to anyone looking at your sheet.


Example 5 – Adding conditional formatting

Now let’s add some colour to show which are OK and not OK more clearly. Let’s put the OKs in green and the Not OKs in red.


Select the cells you want to colour.


Right click and select Conditional formatting.


This opens the Conditional format rules sidebar. Click on where it says “Cell is not empty”.


Then select “Is equal to” from the options.


In the box type “OK”.


Then select the colour you want by clicking on the fill icon.


As we can see, the first rule is now set up and it’ll fill any cell in with green that is equal to OK.


Click on “Add new rule”. Then repeat the process above, this time selecting red and in the box typing in Not OK.



This is far more visual and effective, especially if you have a long list.


If you want more information on conditional formatting, see this post.

Example 6 – Using custom formula to add colour to cells with the data in it

So far we’ve reported what’s OK and not OK in different cells, but quite often the better way is to highlight the cells themselves that are OK or not OK. For example, first let’s highlight the emails that are OK in green.


Select the emails and right-click and select Conditional formatting as before.


This time, at the bottom of the options, select “Custom formula is”.


In the box, type the ISEMAIL function referring to the first cell in your selection. In this case, cell B2.


Note that this automatically, applies your formula to the whole range you selected.


As we can see it highlights those emails that are indeed emails, i.e. which from the formula return as true.


Example 7 – Using NOT in a custom formula to highlight what isn’t true

Highlighting which emails are correct is fine, but I usually find that you normally want to know where there are any problem ones, as one assumes that the majority will be ok. As you can see in the picture below, we can clearly see which cells need attention.


The process is exactly the same as the previous example, except we’re going to change the formula slightly. Start off with the emails.


Having selected “Custom formula is”, type the following formula in the box:


This wraps the ISEMAIL function in a NOT function. Effectively, what it’s doing is saying if the cell content ISN’T an email then apply the formatting. We then add a red fill.


As we can see it’s highlighted the problematic cells in red.


We can then do the same for the numbers and URLs, selecting each range at a time and entering the following formulas.



This is much clearer and without the need of extra columns.


eBooks available on Drive, Forms, Sheets, Docs, Slides, and Sheet Functions:

Baz Roberts (Flipboard / Twitter / Google+)