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 snippets — plus optional Co-Pilot when you want a boost (Day Pass — $3, or 1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension