エラーの内容
TypeError: Cannot read properties of undefined (reading 'getValue')
.getValue()、.getValues()、.getRange()などのメソッドを、実行時にundefinedとなっている変数に対して呼び出したことが原因です。よくある3つのトリガー:誤ったインデックスで行をループしている、存在しないシートを参照している、またはgetActiveSheet()が常に有効な値を返すと思い込んでいる。
根本原因
Apps ScriptはV8 JavaScriptで動作します。undefinedに対してメソッドを呼び出すと、例外なくこのエラーが発生します。主な原因はこちらです:
- 配列インデックスの範囲外アクセス:
rows[i]でループしているが、配列の要素数が想定より少ない場合、10行の配列に対してrows[10]はundefinedになります。 - シート名の誤り:
spreadsheet.getSheetByName('Data')はシートが存在しない場合にnullを返します。そのnullに対して.getRange()を呼び出すと同様のエラーが発生します。 - 空のレンジの誤った処理:空のレンジに対して
getValues()を呼び出すと、undefinedではなく空文字の2次元配列が返されます。ただし、次元を読み間違えるとundefinedのセルを参照してしまう可能性があります。 - 行インデックスのオフバイワン:
getRange(row, col)などのシートメソッドは1始まりです。getValues()から得られる配列は0始まりです。混同すると配列の末尾を1つ超えた行を読み取ってしまいます。
修正1:シートの参照を使用前に確認する
名前でシートを取得する場合、存在するとは限りません。スプレッドシートのタブ名を変更するだけでスクリプトが静かに壊れます。2行のガード処理でそれを防げます:
function readData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Data');
if (!sheet) {
Logger.log('Sheet "Data" not found');
return;
}
const value = sheet.getRange(1, 1).getValue();
Logger.log(value);
}
注意:getSheetByName()はundefinedではなくnullを返します。ただし関係ありません — null.getValue()も同じエラーをスローします。if (!sheet)チェックで両方をキャッチできます。
修正2:getValues()を使ったループのオフバイワンを修正する
これは典型的な落とし穴です。getValues()は0始まりの配列を返しますが、以下のループはi = 1から開始しているため、最後のイテレーションでdata[lastRow]が配列の末尾を超えてしまいます:
// BROKEN: starts loop at 1, but array index goes 0 to lastRow-1
function brokenLoop() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
const data = sheet.getRange(1, 1, lastRow, 3).getValues();
for (let i = 1; i {
row.forEach((cell, colIndex) => {
Logger.log(`[${rowIndex}][${colIndex}]: ${cell}`);
});
});
}
修正4:チェーン呼び出しの中間結果を確認する
findNext()は何も見つからない場合にnullを返します。そのまま.getValue()をチェーンすると、キーワードが存在しないたびにクラッシュします:
// BROKEN: assumes the keyword always exists in the sheet
function findAndRead() {
const sheet = SpreadsheetApp.getActiveSheet();
const found = sheet.createTextFinder('keyword').findNext();
const value = found.getValue(); // null.getValue() → TypeError
}
// FIXED
function findAndReadSafe() {
const sheet = SpreadsheetApp.getActiveSheet();
const found = sheet.createTextFinder('keyword').findNext();
if (!found) {
Logger.log('Keyword not found in sheet');
return;
}
const value = found.getValue();
Logger.log(value);
}
修正5:存在しない名前付き範囲へのアクセス
getRangeByName()は、スプレッドシートで定義されていない範囲名に対して静かにnullを返します。読み取り前に必ず確認してください:
// BROKEN
function readNamedRange() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const range = ss.getRangeByName('MyConfig'); // null if not defined
const value = range.getValue(); // TypeError
}
// FIXED
function readNamedRangeSafe() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const range = ss.getRangeByName('MyConfig');
if (!range) {
Logger.log('Named range "MyConfig" does not exist');
return;
}
const value = range.getValue();
Logger.log(value);
}
デバッグのヒント:クラッシュ前に変数をログ出力する
Apps Scriptのスタックトレースは曖昧なことがあります。どの変数がundefinedか不明な場合は、失敗している呼び出しの直前にログ行を追加してください:
Logger.log('sheet: ' + sheet);
Logger.log('range: ' + range);
Logger.log('data type: ' + typeof data);
表示 → ログを開くか(またはスクリプトエディタでCtrl+Enterを押す)と、クラッシュ前にどの変数がnullまたはundefinedを出力したかが正確にわかります。スタックトレースを読むより素早く原因を特定できます。
確認方法
修正を適用したら、実際に問題が解決されていることを確認してください:
- Apps Scriptエディタで実行 → 関数を実行から手動で関数を実行する。
- 表示 → 実行数を開く — 正常な実行では赤いエントリが表示されません。
- 意図的にエッジケースをテストする:空のシート、名前が変更されたタブ、データのないレンジ。これらがバグを露呈した正確な条件です。
- 時間ベースのトリガーを使用している場合は、次回のスケジュール実行後に表示 → 実行数を確認して、サイレント障害がないことを確認してください。
予防策
getSheetByName()、getRangeByName()、findNext()はnullを返す可能性があるものとして扱う。出力を使用する前に必ずif (!result) return;を追加してください。- シートの生の行数ではなく
data.lengthでループを制御する。配列はすでに自分の行数を知っています。 - インデックスの考え方を正確に把握する:シートの行と列は1始まり、配列のインデックスは0始まり。一方が必要な場所でもう一方を使わないようにする。
- シートデータを読み取る関数の先頭に
if (lastRow < 1) return;を追加する。1行追加するだけで、エラーのクラスをまるごと防ぐことができます。

