I recently had a legend (a.k.a reader) ask about how to copy selected columns from one sheet and append them to the bottom of different selected columns in another sheet.
To me, this seemed like a pretty common task that one might face, so in response, I decided to create a template function to easily do this and reuse on the same project or in other projects.
But first, let’s take a look a the problem a bit more.
We want:
- the freedom to select any number of columns from one sheet.
- to select the same number of different columns from the sheet we want to paste to.
- to paste to the bottom (append) the receiving sheet.
- to take into account varying header row heights.
- to have a “Completed” or “Copied” column in our origin sheet so that we don’t continue to paste all the data instead of simply what has been updated.
- have the data with formulas that we only want to paste the values for.
Table of Contents
The Example
Let’s say we have a sheet that is updated regularly and we want to be able to paste data from columns A, B and C of this source sheet and append it to columns C, E and A of the destination Sheet.
- Copy Sheet 1 Col A -> Append Sheet 2 Col C
- Copy Sheet 1 Col B -> Append Sheet 2 Col E
- Copy Sheet 1 Col C -> Append Sheet 2 Col A
Take a look at our two Google Sheets
You can see in the Source Sheet (Sheet1) that we have 3 data columns and one column market To Copy. The To Copy column will be updated with “Copied” once the code is run successfully.
In the Destination Sheet (Sheet2) you can see that we already have some data there. We don’t want to overwrite it. We simply want to append our data to the bottom of it.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
/*Copy data from multipe sources in Sheet1 and paste them in multiple locations in Sheet 2 * *Copy Sheet 1 Col A -> Append Sheet 2 Col C *Copy Sheet 1 Col B -> Append Sheet 2 Col E *Copy Sheet 1 Col C -> Append Sheet 2 Col A */ function pasteMultiCol(sourceSheet, destinationSheet,sourceColumns,destinationColumns, doneColumn){ var sourceDataRange = sourceSheet.getDataRange(); var sourceDataValues = sourceDataRange.getValues(); var sourcesheetFirstRow = 0; var sourceSheetLastRow = sourceDataRange.getLastRow(); var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow(); var pendingCount = 0; //Find the row start for copying for(i = 0; i < sourceDataValues.length; i++){ if(sourceDataValues[i][doneColumn-1] === "Copied"){ sourcesheetFirstRow++; }; if(sourceDataValues[i][doneColumn-1] === ""){ pendingCount++; }; }; //Update Source sheet first row to take into account the header var header = sourceSheetLastRow-(sourcesheetFirstRow + pendingCount); sourcesheetFirstRow = sourcesheetFirstRow+header; // if the first row equals the last row then there is no data to paste. if(sourcesheetFirstRow === sourceSheetLastRow){return}; var sourceSheetRowLength = sourceSheetLastRow - sourcesheetFirstRow; //Iterate through each column for(i = 0; i < destinationColumns.length; i++){ var destinationRange = destinationSheet.getRange(destinationSheetLastRow+1, destinationColumns[i], sourceSheetRowLength, 1); var sourceValues = sourceDataValues.slice(sourcesheetFirstRow-1,sourceSheetLastRow); var columnValues =[] for(j = header; j < sourceValues.length; j++){ columnValues.push([sourceValues[j][sourceColumns[i]-1]]); }; destinationRange.setValues(columnValues); }; //Change Sheet 1 to Copied. var copiedArray =[]; for(i=0; i<sourceSheetRowLength; i++){copiedArray.push(["Copied"])}; var copiedRange = sourceSheet.getRange(sourcesheetFirstRow+1,doneColumn,sourceSheetRowLength,1) copiedRange.setValues(copiedArray); }; function runsies(){ var ss = SpreadsheetApp.openById("1DLYmQ4kOWq0XQorSqyha9xEb84Qf0F_2iD4LZxGx_Nc"); var source = ss.getSheetByName("Sheet1"); var destination = ss.getSheetByName("Sheet2"); var sourceCols = [1,2,3]; var destinationCols = [3,5,1]; var doneCol = 4 //Run our copy and append function pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol); }; |
NOTE! On the code snippet, there is an option to open in a new window. It might help you so you can follow the code as I explain it.
The main code here is inside the pasteMultiCol
function. You can see that the function takes 5 arguments. We run this function in the runsies
function which is just a placeholder for whatever you want. The file arguments are:
- sourceSheet – the Source sheet in our case Sheet1
- destinationSheet – the Destination sheet in our case Sheet2
- sourceColumns – selection of source columns in our case col 1,2,3
- destinationColumns – selection of destination columns in our case 3,5,1
- doneColum – the column that records if the data has already been added to the destination sheet.
Need help with Google Workspace development?
Go something to solve bigger than Chat GPT?
I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
Schedule a free consultation today to discuss your needs and get started or learn more about our services here.
runsies()
In the runsies
function (Ln 62) we set all the variables we need to run pasteMultiCol
.
First, we get the spreadsheet and then the two sheets we will be working in (Ln 63-64).
Next, we create an array of columns for the source sheet and the destination sheet respectively. Careful to ensure that the length of both arrays is the same. (Ln 67-68)
It is easier to use columns by numbers in these arrays than the letter notation.
Our last variable indicates where the column in the source file is that marks the row as already been added to the destination sheet.
Finally, we run the pasteMultiCol
function with all our variable added.
pasteMultiCol
Setting up the Main Variables
Lines 10 through to 31 sets up our main variables that we will use for the rest of the function.
First, we get the range of all the data from the Source Sheet. We then grab all their values in the next line (Ln 11). This will be a nested array of row values that would look a little like this:
1 2 |
[["To Col 3","To Col 5","To Col 1",""To Copy"] ["This","4","This Is",""]... etc] |
Line 12 Sets the sourcesheetFirstRow
(more on this in a moment) to initially zero and sourceSheetLastRow
gets the number of the last row.
We then find the destinationSheetLastRow
so we know what row to paste the updated Source Sheet data on. (Ln 15)
Then our pending pendingCount
is set to zero (Ln17). The pending count is, in our case, any blank cell in column 4 (D) that has data in the rows in the other columns. This is currently set to zero, but we will update this in the following code.
On lines 20-27 we use a for
loop to loop through the source data by row looking for values in the doneColumn
. We have to subtract the doneColumn
by 1 because unlike the spreadsheet numbering system where numbering starts at 1 we are now referencing an array that starts its count at 0.
While we are iterating through each row of the source data we are looking for the word “Copied” in the done column and counting each time it appears. We are also looking for any blank cells in the done column and counting those too. You can change the “Copied” and blank values to True or False or some other value if you wish.
Next, we want to determine the header. Header height in rows can change from project to project so to combat this we grab the sum of the first row of the Source Sheet and subtract it from the Source Sheet Last Row.
1 |
var header = sourceSheetLastRow-(sourcesheetFirstRow + pendingCount); |
We then use the header to update the sourcesheetFirstRow variable (Ln 31)
1 |
sourcesheetFirstRow = sourcesheetFirstRow+header; |
Checking if There is Anything That Needs to Be Appended to the Destination Sheet
On line 34 we check to see if there is anything that needs to be added to the destination sheet. We do this by comparing the length of the sourcesheetFirstRow
and see if it matches sourceSheetLastRow
. If it does, that means we don’t need to do anything so we should return
the function to stop the script.
Pasting the Data into the Destination Sheet
On line 36 we first need to get the sourceSheetRowLength
by subtracting the sourceSheetLastRow
from the sourcesheetFirstRow
.
Then it’s time to run through each of the destinationColumns
(Ln 39-50). Initially, we get the range we will be pasting our data into. The row will be the first empty space after the last of the data. That’s why we add one (1) to the destinationSheetLastRow
(Ln 40). The destinationColumns[i]
(Ln 41) is the number that we find in our destinationCols
back in line 68. The row length is determined back on line 36. Finally, the column width is only 1.
Lines 44-48 allow us to get the correct column data from the Source Sheet Range Values and put it into an array. Line 44. Slices off all the Copied data and Header information in the array.
1 |
var sourceValues = sourceDataValues.slice(sourcesheetFirstRow-1,sourceSheetLastRow); |
We then create a columnValues
variable array (Ln 45). We’ll then loop through our sourceValue
adding each value base on our sourceColumn
to our ColumnValues
array.
1 2 3 4 5 6 7 |
... var sourceValues = sourceDataValues.slice(sourcesheetFirstRow- 1,sourceSheetLastRow); var columnValues =[] for(j = header; j < sourceValues.length; j++){ columnValues.push([sourceValues[j][sourceColumns[i]-1]]); }; ... |
Finally, the moment we have all been waiting for! We add the values to our destination sheet.
1 |
destinationRange.setValues(columnValues); |
Update the Source Sheet to say “Copied”
Lines 53-57 updates the source sheet column 4 (D) and adds a “Copied” cell to all the cells that were…um… copied over.
To start we need to create an array the length of the number of rows we just copied across. Then we need to populate that array with the word “Copied”. (If you changed “Copied” to something else on line 21 then you need to change it here. (or you could be awesome and create a variable for this and only change it once ;))
Line 56 then gets the range we need to copy in our data.
1 |
var copiedRange = sourceSheet.getRange(sourcesheetFirstRow+1,doneColumn,sourceSheetRowLength,1) |
Then, we just set the values with our array of “Copied”.
Now, you might be wondering why we don’t just iterate through each cell adding “Copied” as we go. Well, we want to reduce our interaction between the server and Google sheets, because these are quite costly in time. This article explains the method in my madness a little better:
Iterating Through Google Sheets the Right Way and the Wrong Way
Conclusion
Before we finish up, let’s look at our script in action:
Bonza! How about we add some more data.
Yay! it worked!!!
This code is by no means perfect. Ideally, I would like to add some error handling to catch a few things that might occur, such as a mismatch between the number of columns to copy from and copy too.
Have a go. See if you can improve on it or make some changes for your own project.
Good Luck!
If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.
Further Reading
- Copy and Paste Range Values from one Google Sheet into another with Google Apps Script
- Google Apps Script – When I add a value to a cell in a selected column, I want it to move to another Google Sheet [update Jan 2022]
- Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes [updated Dec 2021]
Changelog
- 23 Feb 2022 – Add further reading section and table of contents.
~Yagi.
Great, Yagi! Looks great!
There are two scenarios that I need to figure out:
1) After you have copied all of the inputs, if you want to copy again any of the middle ones, when you remove “Copied” your script will copy one line but it won’t be the one without “Copied”, it will be the bottom one.
Example: We have 6 rows input, and the 4th row is missing “Copied”, if you run your script, it will copy the bottom one (6th row) and not the one where “Copied” is missing. Any way to fix this?
2) Is there any way we can add another IF option? For example, if column T is not empty and column U has no “Copied”, then copy those lines? (“if ((cellT$ !==””)&&(cellU$ ==””)) {“)
Please find attached my file where you will understand better what I need. Thanks very much for your time again, this is so much helpful!!!
In the link below, between 1 and 4 there are two hyphens(- -), just when I paste it here, it shows as –:
(“https://docs.google.com/spreadsheets/d/1–4UL1SjdEv-YT-_gTpvKqy9H4SDEdBm8aubFpUZYuw/edit?ts=5be08e3d#gid=0”)
Thanks again!
1) You would have to rewrite your code to deal with this. You will need to iterate through the spreadsheet identifying each line that event you want to copy occurs and push the row and the row number into a new 2d array. Then paste that array, go back and change the fields in the original data.
2. Yep you are on the right track with your AND statement.
Hi,
I am getting following error with the script
var sourceDataRange = sourceSheet.getDataRange();
TypeError: Cannot call method “getDataRange” of undefined. (line 9, file “Code”)
Hi pankaj,
Did you call the source data in the
runsies
function? I’d have a look there first. Let me know how you wen.~Yagi.
Can you share a sample sheet with the script. Today I am getting following error
The number of rows in the data does not match the number of rows in the range. The data has 89 but the range has 96. (line 49, file “Code”)
Hi Pankaj,
It would be more beneficial if you shared your work or your replicated sample with us so we can see where the error lies and guide you.
Hi,
Let me know how I can send you my spreadsheet with example
How I can share my sample sheet with you
You can share the link in the comments or via the Feedback form.
facing the same issue how we can resolve this ”TypeError: Cannot call method “getDataRange” of undefined. (line 3, file “Code”)”
Di
Hi Sashank,
Did you run the ‘runsies’ function?
Now it’s working fine also want to know there is based on the value can we capture selective column value in different spreadsheet.
Thanks Now it’s working fine, Pls suggest there is why where based on the selected value we can copy selected column value in different sheets
Hi Shashank,
No, not with the current configuration of the function. You would have to create something custom for that purpose.
~Yagi
Hi,
I am new to google script and I am trying to write a script that is able to copy a specified (fixed column) of data from one google sheet to another google sheet’s next available empty row, do you have a script that is able to execute this:
// function to save data
function saveData() {
var sheetFrom= SpreadsheetApp.openById(‘source data sheet ‘).getSheetByName(“XXX “);
var sheetTo = SpreadsheetApp.openById(‘Destination data sheet’).getSheetByName(“XYZ”);
sheetFrom.getRange(84, 7, sheetFrom.getLastRow(), 1).copyTo(sheetTo.getRange(1,
sheetTo.getLastColumn()+1, sheetFrom.getLastRow()-84, 1), {
contentsOnly: true
});
Hi Vicks,
You could use the code provided in this tutorial for your purpose. You will just need to update the variables in the runsies() function.
Cheers,
Hi Yagi,
Thank you for this example. It works well and I learned a lot.
I do have one question, the last line of “runsies” you put the function in a var but you don’t use the var. Line 73.
” //Run our copy and append function
var copyColumns = pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol);
};
The var copyColumns is not used anywhere (I can’t find it). Also, the var is greyed out on my editor, I assume because it is not used.
Can you explain why you did it this way?
Thank you.
Hi pnnyg,
Great find. It looks like I had an error in the code from a legacy function there. I’ve updated the tutorial.
The like should read:
pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol);
};
In order to execute the the function.
Note the runsies() function is a sample function you could execute pasteMultiCol in any of your functions.
Thanks again for the heads up.
Cheers,
Yagi 🐐
Great!
Thank you.
Hello, I’m getting the following error – would you be able to help fix?
TypeError: Cannot read property ‘getDataRange’ of undefined
pasteMultiCol @ Code.gs:10
Hi Krystal,
Try setting up the Globals in your runsies() function first and then run that function instead of the pasteMultiCol() function.
Cheers,
Yagi
Hi, Thanks, that’s exactly what I was looking for, except i want to merge two of your examples together.
This one & when I add a value to a cell in a selected column, I want it to move to another Google Sheet.(https://yagisanatode.com/2019/02/13/google-apps-script-when-i-add-a-value-to-a-cell-in-a-selected-column-i-want-it-to-move-to-another-google-sheet/)
So if cell = yes, then copy certain columns from row to specific columns in a new sheet
Thank you!
Hi Yagi,
I tried to use your script but I would like to paste it into specific rows at the destination sheet.
How to modify the getDataRange().getLastRow() to address the specific rows?
I put the details on here, if you might have some time to look for it:
https://stackoverflow.com/questions/66717847/copy-selected-columns-in-one-sheet-and-add-them-to-selected-columns-in-another-s
Thank you!
Hi Arief,
My guess is that it might be an issue with your other columns containing data like formulas in rows past the row you want to copy your data in the Destination.
Give this a try:
You might also find this post useful:
Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes
~Yagi
hey,
I’m getting the same error as much around here, but I don’t seem capable to sort it.
Error
TypeError: Cannot read property ‘getDataRange’ of undefined
pasteMultiCol @ Untitled.gs:10
I know you mention something about calling the runsies, but please could you be more specific in what to do? I would appreciate a lot! Thank you in advance for that
Hi Rafael,
There are two functions in this code the
pasteMultiCol
(line 8) and therunsies
(Line 62). Therunsies
function is the sample function that grabs all the variables needed and then calls thepasteMultiCol
function.In the Google Apps Script editor (IDE), to run a different function you can go to the menu bar and between the ‘Debug’ and ‘Execution log’ buttons, there is a drop-down of all the functions in your project. Select
runsies
, and then hit run and see how if this fixes your error.Cheers,
Yagi
Yagi,
I really like your tutorials. The issues I am observing is similar to one described above. I am also a novice at best, so the more specificity, the better for this padawan. I am getting the error of:
TypeError: Cannot read property ‘getDataRange’ of undefined
pasteMultiCol @ Code.gs:10
The runsies function works great assuming that in the Sheet 2 (destination sheet) there is nothing in any of the cells past the first header row. When I use the pasteMultiCol, I receive the error above. In other words,
– runsies = works assuming nothing in row 2 and beyond
– pasteMultiCol = throws the error listed above and does not work.
My objective is to copy columns from one sheet and append (adding to the end) another sheet once I figure out this code. I am not sure what exactly is wrong with getDataRange that would solve this. Any help would be appreaciated.
Steve