GASのonEditでセル編集を
きっかけに自動処理する方法

チェックを入れたら承認日時が入る、ステータスを変えたら更新日が入る——こうした「編集した瞬間の自動処理」はonEdit(e)だけで作れます。イベント情報の読み方、対象シート・列の絞り込み、メール送信ができない制約とその回避策まで、動くコードで解説します。

|対象: GAS / スプレッドシート / onEdit

Table of Contents

onEditとは|登録不要で動くトリガー

onEdit(e)は、スプレッドシートのセルが人の手で編集されたときに自動で呼ばれる関数です。GASではこうした「特定の名前をつけるだけで自動実行される関数」をシンプルトリガー(simple trigger)と呼びます。トリガー画面での登録作業は不要で、スプレッドシートに紐づくスクリプトに書いて保存すれば、その瞬間から有効になります。

スクリプトは、対象のスプレッドシートで「拡張機能」→「Apps Script」から開きます。関数名はonEditでなければ呼ばれません(大文字小文字も含めて一致が必要)。

発火する操作

人がシート上でセルの値を変えたとき。手入力、貼り付け、削除、チェックボックスの切り替え、プルダウンの選択など。

発火しない操作

コードからのsetValueによる書き込み、フォーム送信による行追加、数式の再計算、書式だけの変更、閲覧権限しかないユーザーの操作。

イベントオブジェクトeの中身を知る

onEditの引数eには、「どこが」「どう」編集されたかの情報が入ります。これをイベントオブジェクトと呼びます。まずはログに出して中身を確かめるところから始めましょう。

function onEdit(e) {
  // e には「どこが」「どう」編集されたかの情報が入る
  const range = e.range;                 // 編集されたセル範囲
  const sheet = range.getSheet();        // そのセルがあるシート

  Logger.log(sheet.getName());           // 例: 案件管理
  Logger.log(range.getA1Notation());     // 例: C5
  Logger.log(e.oldValue);                // 編集前の値(単一セル編集時のみ)
  Logger.log(e.value);                   // 編集後の値(単一セル編集時のみ)
}

e.range

編集されたセル範囲(Rangeオブジェクト)。行・列・シートはここから取得します。

e.value

編集後の値。単一セルを編集したときだけ入ります。

e.oldValue

編集前の値。単一セル編集時のみ。新規入力(空欄からの入力)では undefined です。

e.source

編集されたスプレッドシート本体。他シートの取得などに使います。

e.user

編集したユーザー。権限や組織の設定によっては取得できないため、使う前に存在確認をします。

注意点は、複数セルをまとめて貼り付けたときにe.valuee.oldValueが入らないことです。値を確実に取りたいときはe.range.getValue()でセルから直接読むほうが安全です。

対象シート・列を絞って誤爆を防ぐ

onEditは、そのスプレッドシート内のすべてのシートのすべての編集で呼ばれます。何も絞らずに書くと、関係ないシートを直しただけで処理が走ってしまいます。最初に「対象外ならreturnして抜ける」条件を並べるのが定石です。

function onEdit(e) {
  const range = e.range;
  const sheet = range.getSheet();

  // 1. 対象シート以外は何もしない
  if (sheet.getName() !== "案件管理") return;

  // 2. ヘッダー行(1行目)の編集は無視する
  if (range.getRow() === 1) return;

  // 3. C列(3列目)以外の編集は無視する
  if (range.getColumn() !== 3) return;

  // ここまで来たら「案件管理シートのC列2行目以降が編集された」ときだけ
  sheet.getRange(range.getRow(), 4).setValue(new Date());
}

このように早い段階で抜ける書き方(ガード節)にすると、条件が増えてもネストが深くならず読みやすくなります。列番号はA列=1から数え、range.getColumn()で取得します。列の位置が変わる可能性があるなら、ヘッダー行の見出し名から列番号を探す作りにしておくと壊れにくくなります。

実例1|チェックで承認日時と承認者を自動入力

いちばん需要が多いのが、チェックボックスをオンにしたら日時と担当者が自動で入る仕組みです。承認一覧のE列にチェックボックスを置き、F列に承認日時、G列に承認者のメールアドレスを書き込みます。

const SHEET_NAME = "承認一覧";
const CHECK_COL = 5;   // E列: 承認チェックボックス
const DATE_COL = 6;    // F列: 承認日時
const USER_COL = 7;    // G列: 承認者

function onEdit(e) {
  const range = e.range;
  const sheet = range.getSheet();

  if (sheet.getName() !== SHEET_NAME) return;
  if (range.getColumn() !== CHECK_COL) return;
  if (range.getRow() === 1) return;

  const row = range.getRow();
  const checked = range.getValue();      // チェックボックスは true / false

  if (checked === true) {
    sheet.getRange(row, DATE_COL).setValue(new Date());
    sheet.getRange(row, USER_COL).setValue(e.user ? e.user.getEmail() : "");
  } else {
    // チェックを外したら記録も消す
    sheet.getRange(row, DATE_COL).clearContent();
    sheet.getRange(row, USER_COL).clearContent();
  }
}

チェックボックスの値はtrue/falseの真偽値です。e.valueは文字列の"TRUE"になる場合があるため、この例ではrange.getValue()でセルから直接読んで判定しています。e.userは取得できない環境もあるので、存在チェックをしてからgetEmail()を呼びます。

自分の書き込みで無限ループにならない理由

onEdit内でsetValueしても、コードによる書き込みではonEditは発火しません。そのため、この実装がループすることはありません。ただし手入力の可能性がある列に書き込む場合は、書き込み先の列を対象条件から外しておくと安全です。

実例2|編集内容をログシートに残す

「誰がいつどのセルを何から何に変えたか」を残しておくと、数字が合わないときの調査が一気に楽になります。appendRow(最終行の下に1行追加するメソッド)でログシートに追記します。

function onEdit(e) {
  const range = e.range;
  const sheet = range.getSheet();
  if (sheet.getName() === "編集ログ") return;   // ログ自身の編集は記録しない

  const log = e.source.getSheetByName("編集ログ");
  if (!log) return;

  log.appendRow([
    new Date(),
    e.user ? e.user.getEmail() : "unknown",
    sheet.getName(),
    range.getA1Notation(),
    e.oldValue === undefined ? "" : e.oldValue,   // 複数セル編集時は undefined
    e.value === undefined ? "" : e.value,
  ]);
}

ポイントは2つです。1つ目は、ログシート自身の編集を除外して記録が延々と増えないようにすること。2つ目は、e.oldValueundefinedになるケース(新規入力や複数セルの貼り付け)を空文字に置き換えることです。ログが増え続けると重くなるため、月次で古い行を削除する時間主導トリガーを別に用意しておくと運用が安定します。

メール送信・API連携ができない制約と回避策

onEditの最大の落とし穴がこれです。シンプルトリガーは、ユーザーの承認(認可)が必要なサービスを呼び出せません。具体的にはMailApp/GmailApp/UrlFetchApp/DriveAppなどで、呼ぶと権限エラーで止まります。「承認したらメールを飛ばしたい」「AIに判定させたい」といった要件は、onEditのままでは実現できません。

回避策は、別名の関数を作り、インストーラブルトリガー(手動で登録するトリガー)として「編集時」に紐づけることです。インストーラブルトリガーは登録した本人の権限で動くため、メール送信も外部API呼び出しもできます。

// シンプルトリガーの onEdit ではメール送信できないため、
// 別名の関数を用意し「編集時」のインストーラブルトリガーとして登録する
function onEditInstallable(e) {
  const range = e.range;
  const sheet = range.getSheet();

  if (sheet.getName() !== "承認一覧") return;
  if (range.getColumn() !== 5) return;
  if (range.getValue() !== true) return;

  const row = range.getRow();
  const title = sheet.getRange(row, 2).getValue();

  // インストーラブルトリガーなら MailApp / UrlFetchApp が使える
  MailApp.sendEmail({
    to: "sales@example.com",
    subject: "【承認】" + title,
    body: title + " が承認されました。\n行: " + row,
  });
}

// トリガーの登録(1回だけ手動実行する。UIのトリガー画面から登録してもよい)
function createEditTrigger() {
  const ss = SpreadsheetApp.getActive();

  // 二重登録を防ぐため、同名の既存トリガーを削除してから作る
  ScriptApp.getProjectTriggers()
    .filter((t) => t.getHandlerFunction() === "onEditInstallable")
    .forEach((t) => ScriptApp.deleteTrigger(t));

  ScriptApp.newTrigger("onEditInstallable")
    .forSpreadsheet(ss)
    .onEdit()
    .create();
}

関数名をonEdit以外にするのが重要です。onEditのままインストーラブルトリガーも登録すると、1回の編集で2回実行されてしまいます。トリガーはエディタ左の時計アイコン(トリガー画面)から手で登録してもよく、上のようにScriptAppで作る場合は、二重登録を防ぐため既存の同名トリガーを削除してから作成します。

シンプルトリガー(onEdit)

登録不要ですぐ動く。実行時間は最大30秒。メール送信・外部API・Drive操作は不可。軽い自動入力向き。

インストーラブルトリガー

トリガー登録が必要。登録者の権限で動き、メール送信やAPI連携が可能。実行時間の上限も通常の関数と同じ約6分。

onEditが動かないときのチェックリスト

「書いたのに動かない」ときは、次の順に確認すると原因がすぐ絞れます。

関数名が正確か

onedit や OnEdit では呼ばれません。onEdit と正確に書きます。

コードから書き込んでいないか

setValueによる変更ではonEditは発火しません。手でセルを編集してテストします。

フォーム送信で試していないか

フォーム回答の行追加では発火しません。この場合はフォーム送信時トリガーを使います。

シート名・列番号の判定が合っているか

シート名の全角スペースや列のズレが定番の原因です。Logger.logで実際の値を出して確認します。

編集権限があるアカウントか

閲覧のみの権限では発火しません。編集権限のあるアカウントで試します。

エラーが出ていないか

エディタ左の「実行数」画面に、シンプルトリガーの実行結果とエラーが記録されます。まずここを見ます。

なお、onEditをエディタから直接「実行」ボタンで動かすとエラーになります。引数eが渡されないためです。テストしたいときは実際にセルを編集するか、eに相当するオブジェクトを自分で組み立てて渡す確認用の関数を別に用意します。

まとめ

onEdit(e)は、登録作業なしで「編集した瞬間の自動処理」を作れる最短ルートです。コツは、e.rangeからシート名と行・列を取り出し、対象外なら先にreturnで抜けること。承認日時の自動入力や編集ログの記録は、これだけで実用レベルになります。

一方で、メール送信・外部API連携・重い処理はシンプルトリガーの範囲外です。その場合は関数名を変えてインストーラブルトリガーに切り替える、あるいはonEditではフラグだけ立てて重い処理は時間主導トリガーに任せる——この2つの設計を知っていれば、たいていの「編集をきっかけにした自動化」は形にできます。

よくある質問

スプレッドシートに紐づくスクリプト(メニューの「拡張機能」→「Apps Script」で開くエディタ)に、function onEdit(e) という名前で書けば動きます。トリガーの登録作業は不要で、保存するだけで有効になります。関数名のつづりが違うと呼ばれないため、onEditの大文字小文字も含めて正確に書いてください。

編集イベントの情報が入ります。よく使うのはe.range(編集されたセル範囲)、e.value(編集後の値)、e.oldValue(編集前の値)、e.source(スプレッドシート本体)、e.user(編集した人。権限により取得できないことがあります)です。e.valueとe.oldValueは単一セルの編集時のみ入り、複数セルをまとめて貼り付けた場合は入りません。

できません。onEditは「シンプルトリガー」と呼ばれ、承認(認可)が必要なサービスを使えない制約があります。MailApp・GmailApp・UrlFetchApp・DriveAppなどを呼ぶと権限エラーで止まります。メール送信やAI連携をしたい場合は、別名の関数(例: onEditInstallable)を用意し、トリガー画面から「編集時」のインストーラブルトリガーとして登録してください。

まず、コードによる書き込み(setValue)や別ファイルからの更新では発火しない点を確認してください。onEditは人がシート上で編集したときだけ動きます。次に関数名のつづり、対象シート名の判定、閲覧権限(編集権限がないユーザーの操作では動きません)を確認します。ログはエディタの「実行数」画面から確認できます。

チェックボックスの値はtrue/falseの真偽値として入ります。e.value は文字列の "TRUE" / "FALSE" になる場合があるため、e.range.getValue() === true のようにセルから直接読むと確実です。オンにされた瞬間だけ処理したいときは、値がtrueのときだけ実行するよう分岐します。

シンプルトリガーの実行時間は最大30秒とされています。重い集計や大量の書き込みをonEditの中で行うと途中で止まるため、onEditでは「フラグを立てる」「1セル書き込む」程度に留め、重い処理は時間主導トリガーの別関数に任せる設計が安全です。

GAS開発・業務Webシステムを
相談する。

チェックひとつで承認日時が入る、編集と同時に通知が飛ぶ——こうした「現場が入力するだけで回る」スプレッドシート運用の設計から実装までご相談いただけます。

AI×GAS業務自動化を見る