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
getActiveRowAsObjectand 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 <= lastRowcheck 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() <= headerRowbefore 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!
