// Sheets · Apps Script

Convert text to uppercase in a range in Google Sheets.

Use a short Apps Script function to uppercase every text cell in a selected range with one getValues / setValues round-trip, guarding numbers and dates so they pass through untouched.

I want to uppercase all the text in a column or selection without retyping or adding a helper column full of UPPER() formulas.

The script

copy · paste · trigger
uppercaseRange.gs
Apps Script
// Uppercase every text cell in the active selection.
// Non-string values (numbers, dates, booleans) pass through unchanged.
function uppercaseRange() {
  var range = SpreadsheetApp.getActiveRange();
  var values = range.getValues();

  for (var r = 0; r < values.length; r++) {
    for (var c = 0; c < values[r].length; c++) {
      var cell = values[r][c];
      if (typeof cell === 'string') {
        values[r][c] = cell.toUpperCase();
      }
    }
  }

  range.setValues(values);
}

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

Walkthrough

Why getValues and setValues — not a cell loop

getValues() pulls the entire range into a single JavaScript 2D array in one API call. setValues() writes it back in one call. The alternative — reading and writing each cell inside the loop with getValue() / setValue() — sends one HTTP round-trip per cell to Google's backend, which means a 200-row column triggers 400 calls instead of 2. Apps Script's execution quota is 6 minutes per run; the batched version of this function finishes a 1,000-row column in under a second. The cell-by-cell version will hit quota on anything larger than a few hundred rows.

The 2D array structure mirrors the range: values[0] is the first row, values[0][0] is the top-left cell. You mutate the array in place, then hand the whole thing back to setValues(). The range object never needs to be touched again after that first getValues() call.

The typeof guard is not optional

Apps Script returns cell values as their native JavaScript types, not as strings. A cell containing 42 comes back as the number 42; a date cell comes back as a JavaScript Date object; a checkbox comes back as a boolean. Calling .toUpperCase() on any of those throws a TypeError and aborts the function — which means cells after the bad one never get processed.

The typeof cell === 'string' check is the correct guard here. An empty cell returns an empty string, which passes the check and toUpperCase() returns '' — no harm done. I keep this pattern in a utils file I paste into every project because I have watched people omit it and spend twenty minutes wondering why the script errors on column C but not column B.

Attaching it to a menu so anyone on the sheet can run it

The function works as-is when you run it from the Apps Script editor with a range selected. To make it available to teammates without editor access, add an onOpen trigger that installs a menu item.

Paste this alongside the uppercaseRange function: function onOpen() { SpreadsheetApp.getUi().createMenu('Utilities').addItem('Uppercase selection', 'uppercaseRange').addToUi(); }. The next time anyone opens the spreadsheet, a Utilities menu appears. They select a range, click the menu item, done. No editor access required, no sharing the script URL, no explaining what a script even is.

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
Can I run this on a named range instead of the active selection?
Yes. Replace SpreadsheetApp.getActiveRange() with SpreadsheetApp.getActiveSpreadsheet().getRangeByName('MyNamedRange'). Everything else stays the same.
Will this overwrite formulas in the range?
Yes. setValues() replaces whatever is in the cell — including formulas — with a static value. If a cell contains =LOWER(A1), after the script runs it will contain the uppercase string that formula previously evaluated to, not the formula itself. Check for formulas before running, or use getFormulas() to identify and skip them.
How do I make it run on a fixed range like A2:A500 instead of whatever I selected?
Change the first line inside the function to: var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('A2:A500'); The rest of the function is unchanged.
Does UPPER() in a cell formula do the same thing?
UPPER() wraps the original value and recalculates live, so the source cell stays unchanged and you end up with two columns. The script approach converts in place and removes the formula dependency, which is what you want when importing or cleaning data before passing it to another system.