Skip to content
// Sheets · Apps Script

Copy a sheet to another spreadsheet in Google Sheets.

How to copy a sheet tab to a different Google Sheets file using Apps Script, including renaming it immediately so it doesn't land as 'Copy of X'.

I need to programmatically copy a sheet tab from one spreadsheet to another and control what the tab is named after the copy lands.

The script

copy · paste · trigger
copySheetToFile.gs
Apps Script
// Copy a named sheet to another spreadsheet and rename it on arrival
function copySheetToAnotherFile() {
  var SOURCE_ID = 'YOUR_SOURCE_SPREADSHEET_ID';
  var TARGET_ID = 'YOUR_TARGET_SPREADSHEET_ID';
  var SHEET_NAME = 'Monthly Report';
  var DESTINATION_NAME = 'June Report';

  var sourceSheet = SpreadsheetApp.openById(SOURCE_ID)
    .getSheetByName(SHEET_NAME);

  if (!sourceSheet) {
    throw new Error('Sheet not found: ' + SHEET_NAME);
  }

  var targetFile = SpreadsheetApp.openById(TARGET_ID);
  var copiedSheet = sourceSheet.copyTo(targetFile);

  // copyTo always names the tab 'Copy of <original>' — fix it immediately
  copiedSheet.setName(DESTINATION_NAME);
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What copyTo actually returns

The Sheet.copyTo(spreadsheet) method appends the sheet as a new tab in the target file and returns the new Sheet object. That return value is the whole game. Most scripts ignore it, which is why they end up with a tab called 'Copy of Monthly Report' that nobody asked for.

Capturing the return value and calling setName on it in the same execution is the correct pattern. You're not doing a second lookup — the object is already in memory, no extra API call needed. The first time I missed this, I wrote a separate getSheetByName('Copy of ...') call that broke the moment someone ran the script twice.

Finding your spreadsheet IDs

Both SOURCE_ID and TARGET_ID are the long alphanumeric string in the spreadsheet URL, between /d/ and /edit. For example, in https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/edit the ID is 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms.

The script that does the copying must be bound to one of the two spreadsheets, or run as a standalone script. In either case, the account running it needs Editor access to both files. If the target file is owned by a service account or a different Google Workspace domain, openById will throw a permissions error at runtime, not at authorization — something to test before wiring this into a trigger.

Attaching this to a trigger or a menu

For a one-off migration, running from the Apps Script editor is fine. For a recurring export (say, copying a summary sheet to a shared reporting file every Monday), wire it to a time-based trigger via Triggers > Add Trigger in the Apps Script UI, or programmatically with ScriptApp.newTrigger('copySheetToAnotherFile').timeBased().everyWeeks(1).onWeekDay(ScriptApp.WeekDay.MONDAY).create().

If the same sheet is copied repeatedly, the tab name will collide on the second run. Add a date suffix (DESTINATION_NAME + ' ' + new Date().toLocaleDateString()) or check for an existing tab with getSheetByName before copying. Sheets does not overwrite — it creates a duplicate, which will silently succeed and leave you with two tabs named 'June Report' if you're not looking.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why does the copied sheet always say 'Copy of' at the start of the name?
That is Apps Script's hardcoded behavior for copyTo — there is no parameter to override it. The fix is to call setName on the Sheet object that copyTo returns, immediately after the copy, in the same function.
Can I copy a sheet to a file in a shared drive?
Yes, as long as the account running the script has at least Contributor access to the shared drive and Editor access to the target file. Use the file ID from the URL the same way. Cross-domain shared drives (different Google Workspace organizations) sometimes block this at the drive policy level regardless of file-level permissions.
Does copyTo bring along named ranges, conditional formatting, and data validation?
Named ranges scoped to the sheet copy over. Conditional formatting and data validation rules copy over. Named ranges scoped to the entire spreadsheet (workbook-level) do not — they stay in the source file and are not recreated in the target.
How do I copy the sheet to a specific position in the tab bar, not just the last position?
copyTo always appends to the end. After the copy, call copiedSheet.activate() then targetFile.moveActiveSheet(position) where position is a 1-based index. Alternatively, use copiedSheet.getIndex() to confirm where it landed before moving it.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.