This is my attempt to collate all of the best attributes of all of the other answers. I've torture-tested it in every way I can think of, and it seems to hold up pretty well. That said, error reports are welcomed.
Usage: call it with the cell reference in quotes: =MERGED_CELL_VALUE("H19")
, not =MERGED_CELL_VALUE(H19)
. I couldn't make the function always raise an informative error if you call it without quotes, since the contents of H19 could easily be A2
or something. But it'll usually complain in a useful way.
If you want to be able to use click-and-pull autofill with this, there's a fairly easy workaround. Use CELL()
as so: =MERGED_CELL_VALUE(CELL("address", H19))
. Now, H19 is bare, so it'll be updated if you move it around. This trick also works with H$19
and the like.
/**
* Returns the value at the top of a merged cell.
*
* Inspired by the code at:
* https://stackguides.com/questions/41967357/google-spreadsheet-cell-reference-to-merged-cell
*
* @param {str} target The targeted cell, as a string in A1 format.
* @return The value shown in the merged cell.
* @customfunction
*/
function MERGED_CELL_VALUE(target) {
var sheet = SpreadsheetApp.getActiveSheet();
try {
var targetRange = sheet.getRange(target);
} catch(e) {
if (target == "") {
throw "Empty target specified. Did you forget to enclose the targeted cell in quotes?"
}
else {
throw "Invalid target '" + target + "'. Did you forget to enclose the cell ID in quotes?";
}
}
if (!(targetRange.getNumColumns() == targetRange.getNumRows() == 1)) {
throw "This function does not work on ranges (e.g. 'A1:B3'), only individual cells.";
}
var cell = targetRange.getCell(1, 1); // n.b. Range.getCell() is 1-indexed because FML.
if (!cell.isPartOfMerge()) {
// If the cell is not part of a merge, return the cell's value.
return cell.getValue();
}
else if (cell.getValue() != "") { // n.b. this is a special case for getValue(); empty returns "".
// If the cell has a value, even if it's merged (!!??), return the value.
return cell.getValue();
}
else {
/*
* This might turn out to be a bug; we pick getMergedRanges()[0]. Hopefully that matches how GSheets's logic works.
* There is a getDisplayValue(), but that always returns a String, which is unlikely to fit our goal in
* all cases.
*/
return cell.getMergedRanges()[0].getCell(1, 1).getValue();
}
}