Auto-sort a Google Sheets table by multiple columns, every time the data changes
Sorting by two columns (say Status A to Z, then Due date newest first) is easy once: Data > Sort range > Advanced range sorting options > Add another sort column. Keeping it that way as rows are added or changed needs a script or an add-on. This page gives a free Apps Script for both: on every edit, and on a timer for rows that arrive from other tools. Checked against Google's documentation on 1 November 2026.
1. The free script: several sort columns
- Open the sheet, choose Extensions > Apps Script, replace
Code.gswith the script below. - Set the tab name, the header rows, and the table's first and last column. Columns outside the table (notes on the right, say) are never moved.
- List the sort columns in
SORT_KEYS, most important first. Column numbers count from column A of the sheet (A = 1, B = 2), even if the table starts further right. - Click Save. It now runs on every edit people make on that tab.
// Keeps a table sorted by several columns whenever someone edits it.
const SHEET_NAME = 'Sheet1';
const HEADER_ROWS = 1; // rows at the top that never move
const FIRST_COLUMN = 1; // table's first column (A = 1)
const LAST_COLUMN = 0; // table's last column; 0 = last column with data
const SORT_KEYS = [ // first key wins, later keys break ties
{column: 3, ascending: true}, // C: Status, A to Z
{column: 2, ascending: false}, // then B: Due date, newest first
];
function onEdit(e) {
const sheet = e.range.getSheet();
if (sheet.getName() !== SHEET_NAME) return;
sortTable_(sheet);
}
function sortTable_(sheet) {
const rows = sheet.getLastRow() - HEADER_ROWS;
if (rows < 2) return;
const last = LAST_COLUMN || sheet.getLastColumn();
sheet.getRange(HEADER_ROWS + 1, FIRST_COLUMN, rows, last - FIRST_COLUMN + 1)
.sort(SORT_KEYS);
}
2. Sort when data changes from other tools too
onEdit only fires for people typing or pasting. Rows inserted or deleted, and rows written by Zapier, Make, another script or the Sheets API do not fire it. Google's rule is that script and API changes fire no trigger at all, so the dependable fix is a timer. Paste this under the script, choose installTriggers, click Run once and allow access:
function sortNow() {
sortTable_(SpreadsheetApp.getActive().getSheetByName(SHEET_NAME));
}
function installTriggers() {
const ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('sortNow').forSpreadsheet(ss).onChange().create(); // rows inserted/removed
ScriptApp.newTrigger('sortNow').timeBased().everyMinutes(10).create(); // rows from other tools
}
The timer sorts every 10 minutes (Apps Script allows 1, 5, 10, 15 or 30). Shorter intervals use more of your daily trigger runtime quota and move rows under people more often. Run installTriggers once; each run adds another pair.
3. The catches
- One table per script. Two tables side by side work if you give each its own
FIRST_COLUMN/LAST_COLUMN(copy the function); tables stacked on top of each other in the same columns get mixed into one. - No custom order. Each key sorts A to Z or Z to A. "High, Medium, Low" or "To do, Doing, Done" needs a helper column with numbers such as
=MATCH(C2, {"High","Medium","Low"}, 0)and a sort on that column. - Text that looks like numbers or dates sorts as text. Check with
=ISNUMBER(B2). - Merged cells in the table make the sort fail (the error shows under Executions in the Apps Script editor).
- The timer runs as you, even when the sheet is closed. Remove it from the Triggers page when you no longer need it.
4. Options compared
| Option | Several sort columns | Keeps sorting as data changes |
|---|---|---|
| Data > Sort range (advanced) | Yes | No, once |
=SORT(A2:F, 3, TRUE, 2, FALSE) on another tab | Yes | Yes, but a read-only copy |
| Filter or table views | Yes | Only your own view |
| Script, sections 1-2 | Yes | Edits at once; other tools within 10 minutes |
| KeepSorted add-on | Free: 2, Pro: 5 | Yes, set up from a sidebar |
5. No code: KeepSorted
KeepSorted is an add-on: select the table, add sort columns in a sidebar, and it keeps the real rows in that order, header on top, blanks last, formatting and formulas moving with their rows. Free: one area per tab, up to 2 sort columns, sorted when people with the add-on edit, no trial clock. Pro ($5/month or $39/year): up to 5 sort columns, custom orders like High > Medium > Low without a helper column, up to 10 tables per tab (stacked tables sorted separately), and sorting after form responses, other editors' changes and an hourly check.
KeepSorted is built and in testing. It is not in the Google Workspace Marketplace yet. Leave your email and we send one message when you can install it.
The scripts on this page stay free either way.
More KeepSorted guides
- Auto sort script generator
- Auto-sort alphabetically (A to Z)
- Auto-sort when a new row is added
- Auto-sort by date, newest first (and Google Forms)
- StaySorted alternative: an honest comparison
Sources
- Apps Script: simple triggers · installable triggers (onChange, time-driven; script and API changes fire no triggers)
- everyMinutes: 1, 5, 10, 15 or 30 · Apps Script quotas
- Range.sort reference · SORT function