// Sheets · Apps Script

Sort sheet tabs alphabetically in Google Sheets.

A step-by-step Apps Script that sorts all sheet tabs in a Google Sheets workbook alphabetically, working around the moveActiveSheet constraint with a 1-based position loop.

I have a workbook with dozens of tabs and I want to sort them alphabetically without dragging each one by hand.

The script

copy · paste · trigger
sortTabs.gs
Apps Script
// Sort all sheet tabs in the active workbook alphabetically.
// Run once from Extensions > Apps Script > Run.
function sortSheetTabs() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = ss.getSheets();

  var names = sheets.map(function(s) { return s.getName(); });
  names.sort(function(a, b) { return a.localeCompare(b); });

  for (var i = 0; i < names.length; i++) {
    var sheet = ss.getSheetByName(names[i]);
    ss.setActiveSheet(sheet);
    ss.moveActiveSheet(i + 1);
  }
}

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

Walkthrough

Why there is no direct moveSheet(sheet, position)

The Spreadsheet service exposes moveActiveSheet(pos), not a generic moveSheet(sheet, pos). That single word — Active — is the whole constraint. The method only moves whichever tab is currently active, so before every move you have to call setActiveSheet to tell Sheets which tab you mean. Skip that call and you will shuffle the same tab over and over.

Positions are 1-based: position 1 is the leftmost tab. The loop counter i runs from 0, so the call is moveActiveSheet(i + 1). Using i directly drops the first sheet into position 0, which the API silently clamps to 1 — and your sort comes out one step off.

Building the sorted name list first

The script grabs all sheets via getSheets(), maps to an array of name strings, and sorts that array with localeCompare. localeCompare handles mixed case correctly — it sorts 'Budget' before 'cash flow' the way a human would, rather than all uppercase names before all lowercase ones (which is what a plain < comparison gives you).

Sorting names separately before touching positions matters because moveActiveSheet shifts every subsequent tab's index in real time. If you read and move in the same pass without a pre-sorted target list, you are chasing a moving target. Compute the final order once, then write positions in a single forward pass.

Running it and what to expect

Open Extensions > Apps Script, paste the function, and hit Run. The first run triggers an authorization dialog — approve the 'See and edit your spreadsheets' scope. The function completes in under a second for workbooks up to around 50 tabs; above that it is still fast but you may see the tab bar visibly redraw.

The first time I used this on a client workbook with 30 monthly tabs named things like 'Jan 24', 'Feb 24', 'Dec 23', localeCompare sorted them lexicographically (Dec before Feb before Jan), not chronologically. If your tab names encode dates, you will want a custom comparator that parses the date out of the name before comparing. For purely alphabetic names like project codes or region abbreviations, the default sort is exactly right.

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
Will this work on a sheet that has hidden tabs?
Yes. getSheets() returns all sheets including hidden ones, and setActiveSheet plus moveActiveSheet both work on hidden tabs. Hidden tabs get sorted into the correct alphabetical position alongside visible ones.
Can I sort in reverse (Z to A)?
Change the sort comparator to names.sort(function(a, b) { return b.localeCompare(a); }). That flips the comparison and the loop writes positions in reverse alphabetical order.
I got a 'You do not have permission' error on moveActiveSheet — what is wrong?
The script needs the spreadsheets scope, which triggers on first run. If you copied the script into a project that previously only had a read-only scope grant, revoke and re-authorize: in Apps Script go to Project Settings > Manage Permissions, remove the existing grant, then run again.
Does this run automatically when I add a new tab, or only manually?
Manually, as written. To run it automatically on every structural change you would attach it to an onChange installable trigger (Edit > Current project's triggers > Add trigger, event type 'On change'). Keep in mind that onChange fires on cell edits too, not just tab additions, so on a busy sheet it will re-sort frequently.
// one good script a week

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