Checks a source sheet hourly and appends any rows it has not copied yet into a second sheet. One-way only - nothing is overwritten.

UsesGoogle Sheets

Copy new rows to a second spreadsheet

Every hour, the key column of a source tab is compared with the key column of a destination tab in another spreadsheet, and every row whose key does not yet appear in the destination is appended there. One team keeps the list, another gets its own copy, and nobody pastes anything.

The copy is one way and append only. Nothing already in the destination is ever written over, and edits or deletions made in the source are not mirrored. A row counts as copied once its key appears in the destination, so running it twice copies nothing the second time. Rows with an empty key cell are left alone and counted in the run summary, a row that fails to copy is reported and tried again next run, and a large first copy is spread over several runs.

Both tabs need the same layout, with a column that identifies each row uniquely, such as an order number or an email address, in the same position in each. If the destination tab is empty, the source's header row is copied across first.

ScheduleRun every hour.Run every hour.
ValuesSet sourceSpreadsheetLink to blank.Set sourceSpreadsheetLink to blank.
ValuesSet sourceTabName to blank.Set sourceTabName to blank.
ValuesSet destinationSpreadsheetLink to blank.Set destinationSpreadsheetLink to blank.
ValuesSet destinationTabName to blank.Set destinationTabName to blank.
ValuesSet keyColumnNumber to 1.Set keyColumnNumber to 1.
ValuesSet maxNewRowsPerRun to 200.Set maxNewRowsPerRun to 200.
ListsGet the first item of the last item of sourceSpreadsheetLink split by /spreadsheets/d/ split by / and save it as sourceFileId.Get the first item of the last item of sourceSpreadsheetLink split by /spreadsheets/d/ split by / and save it as sourceFileId.
ListsGet the first item of the last item of destinationSpreadsheetLink split by /spreadsheets/d/ split by / and save it as destinationFileId.Get the first item of the last item of destinationSpreadsheetLink split by /spreadsheets/d/ split by / and save it as destinationFileId.
Decisions & LoopsIf sourceFileId is destinationFileId and sourceTabName is destinationTabNameIf sourceFileId is destinationFileId and sourceTabName is destinationTabName
Yes
Decisions & LoopsStop and report a problem: The source and the destination are the same tab, so every run would copy the sheet into itself. Point 'destinationTabName' at a different tab.Stop and report a problem: The source and the destination are the same tab, so every run would copy the sheet into itself. Point 'destinationTabName' at a different tab.
No
No steps
Google SheetsGoogle SheetsRead column keyColumnNumber of sourceTabName in sourceSpreadsheetLink. Save it as sourceKeyColumn.Read column keyColumnNumber of sourceTabName in sourceSpreadsheetLink. Save it as sourceKeyColumn.
ValuesSet sourceKeys to sourceKeyColumn.values.Set sourceKeys to sourceKeyColumn.values.
Google SheetsGoogle SheetsRead column keyColumnNumber of destinationTabName in destinationSpreadsheetLink. Save it as destinationKeyColumn.Read column keyColumnNumber of destinationTabName in destinationSpreadsheetLink. Save it as destinationKeyColumn.
ValuesSet copiedKeys to destinationKeyColumn.values.Set copiedKeys to destinationKeyColumn.values.
Decisions & LoopsIf sourceKeys is emptyIf sourceKeys is empty
Yes
Decisions & LoopsFinish the run: The source tab has nothing in its key column, so there was nothing to copy.Finish the run: The source tab has nothing in its key column, so there was nothing to copy.
No
No steps
Decisions & LoopsIf copiedKeys is emptyIf copiedKeys is empty
Yes
Google SheetsGoogle SheetsRead row 1 of sourceTabName in sourceSpreadsheetLink. Save it as sourceHeaderRow.Read row 1 of sourceTabName in sourceSpreadsheetLink. Save it as sourceHeaderRow.
ValuesSet headerValues to sourceHeaderRow.values.Set headerValues to sourceHeaderRow.values.
Decisions & LoopsIf headerValues isn't emptyIf headerValues isn't empty
Yes
Google SheetsGoogle SheetsAdd a row to destinationTabName in destinationSpreadsheetLink.Add a row to destinationTabName in destinationSpreadsheetLink.
No
No steps
No
No steps
ValuesSet copiedCount to 0.Set copiedCount to 0.
ValuesSet blankKeyCount to 0.Set blankKeyCount to 0.
ValuesSet failedCount to 0.Set failedCount to 0.
ValuesSet rowNumber to 0.Set rowNumber to 0.
Decisions & LoopsFor each sourceKey in sourceKeys:For each sourceKey in sourceKeys:
ValuesAdd 1 to rowNumber.Add 1 to rowNumber.
Decisions & LoopsIf rowNumber is 1If rowNumber is 1
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf copiedCount is greater than or equal to maxNewRowsPerRunIf copiedCount is greater than or equal to maxNewRowsPerRun
Yes
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
Decisions & LoopsIf a step below fails, save the error as rowErrorIf a step below fails, save the error as rowError
Try
TextTrim sourceKey and save it as rowKey.Trim sourceKey and save it as rowKey.
Decisions & LoopsIf rowKey is emptyIf rowKey is empty
Yes
ValuesAdd 1 to blankKeyCount.Add 1 to blankKeyCount.
Else if @copiedKeys doesn't contain @rowKey
Google SheetsGoogle SheetsRead row rowNumber of sourceTabName in sourceSpreadsheetLink. Save it as sourceRow.Read row rowNumber of sourceTabName in sourceSpreadsheetLink. Save it as sourceRow.
ValuesSet rowValues to sourceRow.values.Set rowValues to sourceRow.values.
Decisions & LoopsIf rowValues isn't emptyIf rowValues isn't empty
Yes
Google SheetsGoogle SheetsAdd a row to destinationTabName in destinationSpreadsheetLink.Add a row to destinationTabName in destinationSpreadsheetLink.
ListsAdd rowKey to the end of copiedKeys and save it as copiedKeys.Add rowKey to the end of copiedKeys and save it as copiedKeys.
ValuesAdd 1 to copiedCount.Add 1 to copiedCount.
No
No steps
Otherwise
No steps
On error
ValuesAdd 1 to failedCount.Add 1 to failedCount.
ValuesShow in the output: Could not copy row rowNumber of the source tab: rowError.message.Show in the output: Could not copy row rowNumber of the source tab: rowError.message.
ValuesSet runSummary to Copied copiedCount new row(s) into '@destinationTabName'.Set runSummary to Copied copiedCount new row(s) into '@destinationTabName'.
Decisions & LoopsIf blankKeyCount is greater than 0If blankKeyCount is greater than 0
Yes
ValuesChange runSummary to runSummary blankKeyCount row(s) had nothing in the key column and were left alone.Change runSummary to runSummary blankKeyCount row(s) had nothing in the key column and were left alone.
No
No steps
Decisions & LoopsIf failedCount is greater than 0If failedCount is greater than 0
Yes
ValuesChange runSummary to runSummary failedCount row(s) could not be copied and will be tried again next run.Change runSummary to runSummary failedCount row(s) could not be copied and will be tried again next run.
No
No steps
Decisions & LoopsIf copiedCount is greater than or equal to maxNewRowsPerRunIf copiedCount is greater than or equal to maxNewRowsPerRun
Yes
ValuesChange runSummary to runSummary This run stopped at its limit of maxNewRowsPerRun rows; the next run carries on from there.Change runSummary to runSummary This run stopped at its limit of maxNewRowsPerRun rows; the next run carries on from there.
No
No steps
ValuesShow in the output: runSummary.Show in the output: runSummary.