Get Selected Row Objects

getActiveRowAsObject is perfect for one row. Ops rarely stop at one. getSelectedRowObjects(sheet, headerRow) returns a header-keyed object for every data row in the current selection — multiple ranges, partial column selections, and all — with _row attached so you can write results back to the right place.

When to use

  • “Run this on the rows I highlighted” menu items
  • Re-sending, re-syncing, or re-processing a handful of rows without touching the rest
  • Anywhere you’d otherwise loop getActiveRowAsObject and hope the user selected one row

How to use this snippet

/** * Header-keyed objects for every data row in the active selection. * - Header row(s) and rows below the data are skipped * - Partial column selections still return the whole row * - Multiple selected ranges (Ctrl/Cmd-click) are merged, de-duplicated, sorted * - Each object gets _row (1-based sheet row) for write-backs * * @param {GoogleAppsScript.Spreadsheet.Sheet=} sheet default: active sheet * @param {number=} headerRow default: 1 * @return {Object[]} */ function getSelectedRowObjects(sheet, headerRow) { sheet = sheet || SpreadsheetApp.getActiveSheet(); headerRow = headerRow || 1; var lastCol = sheet.getLastColumn(); var lastRow = sheet.getLastRow(); if (lastCol < 1 || lastRow <= headerRow) return []; var headers = sheet.getRange(headerRow, 1, 1, lastCol).getValues()[0]; // 1. Every row touched by any selected range, minus header and empty tail rows. var rowSet = {}; var list = sheet.getActiveRangeList(); var ranges = list ? list.getRanges() : [sheet.getActiveRange()]; ranges.forEach(function (r) { for (var row = r.getRow(); row <= r.getLastRow(); row++) { if (row > headerRow && row <= lastRow) rowSet[row] = true; } }); var rows = Object.keys(rowSet).map(Number).sort(function (a, b) { return a - b; }); // 2. Read the full width of each row so a one-column selection still works. return rows.map(function (row) { var values = sheet.getRange(row, 1, 1, lastCol).getValues()[0]; var obj = { _row: row }; headers.forEach(function (h, i) { var key = String(h == null ? '' : h).trim(); if (key) obj[key] = values[i]; // blank headers are ignored }); return obj; }); }

Example

function resendSelected() { var sheet = SpreadsheetApp.getActiveSheet(); var picked = getSelectedRowObjects(sheet); if (!picked.length) { SpreadsheetApp.getUi().alert('Select one or more data rows first.'); return; } var statusCol = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0].indexOf('Status') + 1; picked.forEach(function (row) { sendReminder_(row); // your worker sheet.getRange(row._row, statusCol).setValue('Resent'); }); SpreadsheetApp.getActive().toast(picked.length + ' row(s) resent', 'Done', 5); }

Tips:

  • Selecting a whole column (click the column letter) selects to the bottom of the grid — the row <= lastRow check keeps you from processing thousands of empty rows.
  • It reads one row at a time, which is fine for “a handful the user picked.” For hundreds of rows, read the block once with getRange(minRow, 1, maxRow - minRow + 1, lastCol) and slice.
  • If your menu must refuse header selections outright, check sheet.getActiveRange().getRow() <= headerRow before calling this.
  • For one row, getActiveRowAsObject is still the simpler twin.

Tip: NitroGAS Co-Pilot can wire getSelectedRowObjects into an existing single-row menu action so it works on everything the user highlighted.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension