Combine Tabs With Source

Twelve rep tabs, one per person, and a manager who wants one list. combineTabsWithSource(ss, pattern, opts) reads every sheet whose name matches a regex, maps each row by its header names (so column order can differ between tabs), and returns one 2D array with a Source column first. The result goes straight into setValues.

When to use

  • Rolling per-rep, per-region or per-month tabs into a Summary tab
  • Tabs that were copied by hand and drifted: an extra column here, a reordered one there
  • Any rollup where you need to know which tab a row came from

How to use this snippet

/** * Combine every tab whose name matches `pattern` into one table. * Rows are mapped by header name; the first column is the source tab. * * @param {Spreadsheet} ss * @param {RegExp|string} pattern e.g. /^Rep - / * @param {{sourceHeader?: string, headerRows?: number}} [opts] * @return {Array[]} header row + data rows, ready for setValues */ function combineTabsWithSource(ss, pattern, opts) { opts = opts || {}; var sourceHeader = opts.sourceHeader || 'Source'; var headerRows = opts.headerRows == null ? 1 : opts.headerRows; var re = pattern instanceof RegExp ? pattern : new RegExp(pattern); var headers = []; var seen = {}; var records = []; ss.getSheets().forEach(function (sh) { var name = sh.getName(); if (!re.test(name) || sh.getLastRow() <= headerRows) return; var values = sh.getDataRange().getValues(); var head = values[headerRows - 1].map(function (h) { return String(h).trim(); }); head.forEach(function (h) { if (h && !seen[h]) { seen[h] = true; headers.push(h); } }); for (var r = headerRows; r < values.length; r++) { var row = values[r]; if (row.join('') === '') continue; var rec = {}; head.forEach(function (h, c) { if (h) rec[h] = row[c]; }); records.push({ source: name, rec: rec }); } }); var out = [[sourceHeader].concat(headers)]; records.forEach(function (x) { out.push([x.source].concat(headers.map(function (h) { return x.rec[h] === undefined ? '' : x.rec[h]; }))); }); return out; }

Example

function buildRollup() { var ss = SpreadsheetApp.getActive(); var table = combineTabsWithSource(ss, /^Rep - /); var out = ss.getSheetByName('Summary') || ss.insertSheet('Summary'); out.clearContents(); out.getRange(1, 1, table.length, table[0].length).setValues(table); } // Source | Deal | Amount | Stage // Rep - Ana | A | 5 | // Rep - Ben | B | 7 | Won

Tips:

  • Headers are the union of every matched tab, in the order they're first seen. A tab without a column gets blanks there.
  • Fully blank rows are skipped. Tabs with only a header row are ignored.
  • Make sure the pattern can't match the Summary tab itself, or each rerun will read in its own output.
  • headerRows: 2 uses the second row as headers when the first row is a title.

Tip: NitroGAS Co-Pilot can wire this to a nightly trigger and a menu item. Paste your tab names and it'll write the regex.

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