Google Apps Script(GAS)でスプレッドシートの自動処理を組むと、トリガー(自動実行の設定)が必要になります。GAS エディタの画面から手動で設定できますが、トリガーの数が増えると管理が煩雑になりがちです。
この記事では、トリガーの設定・一覧・削除をスクリプトで一元管理する方法を紹介します。config.js
にトリガー定義を書くだけで、カスタムメニューや
clasp run からワンアクションで操作できます。
なぜトリガーをコードで管理するのか
GAS エディタのトリガー管理画面は、少数のトリガーであれば十分に使えます。しかし、トリガーが増えてくると次のような問題が起きやすくなります。
- トリガーの重複 — 同じ関数に複数のトリガーが付いてしまい、処理が二重実行される
- 設定内容が見えない — 何時に何を実行しているか、画面を開かないと把握できない
-
clasp push後の再設定忘れ — コードを更新してデプロイしたあと、トリガーの再設定を忘れてしまう - チーム共有が難しい — 設定内容がエディタの画面に閉じているため、メンバー間で共有しにくい
コードで管理すれば、これらの問題を解決できます。
config.jsを見れば全トリガーの設定内容が一覧できる- Git で変更履歴を追える
- トリガーの設定・一覧・削除がワンアクション(メニュー選択 or コマンド1行)
コード処理の流れと主な内容
今回のテンプレートは4ファイルで構成されています。処理の流れは以下の通りです。
- 定義(config.js) — トリガーの対象関数・スケジュール種別・実行時刻を宣言的に定義
- 管理(triggers.js) — 定義に基づいてトリガーの設定・一覧表示・削除を実行
- UI(menu.js) — スプレッドシートのカスタムメニューから操作できるようにする
- 実行(main.js) — トリガーから呼び出される実際の処理(差し替え前提のサンプル)
プロジェクト構成
trigger-manager/
├── config.js ← トリガー定義(TRIGGER_CONFIGS)・設定値
├── triggers.js ← トリガー管理(設定・一覧・削除のコア処理 + UI版 + Headless版)
├── menu.js ← カスタムメニュー(スプレッドシート上の操作 UI)
├── main.js ← メイン処理(差し替え前提のサンプル関数)
├── appsscript.json ← GAS マニフェスト(タイムゾーン: Asia/Tokyo)
└── .clasp.json ← clasp 設定(Script ID)
各ファイルの役割:
-
config.js —
TRIGGER_CONFIGS配列でトリガーを定義する。設定変更時はこのファイルだけを修正すればよい -
triggers.js — トリガーの設定・一覧表示・削除のロジック。UI
版(カスタムメニュー用)と Headless 版(
clasp run用)の2系統を提供 -
menu.js —
onOpen()でスプレッドシートにカスタムメニューを追加する - main.js — トリガーから呼び出されるハンドラ関数。このテンプレートではログ出力のみのサンプルを配置
GAS では同一プロジェクト内の
.js ファイルがすべてグローバルスコープを共有するため、import
/ export なしでファイル間の関数や変数を参照できます。
完全なコードは GitHub リポジトリで公開しています。
対応トリガータイプ
時間主導型(ClockTriggerBuilder)
| タイプ | type 値 |
GAS メソッド | 設定例 |
|---|---|---|---|
| 分ごと | minutes |
everyMinutes(n) |
5分ごと(1, 5, 10, 15, 30) |
| 時間ごと | hourly |
everyHours(n) |
1時間ごと |
| 日ごと | daily |
everyDays(1) |
毎日 9時 |
| 週ごと | weekly |
everyWeeks(1).onWeekDay() |
毎週月曜 10時 |
| 月ごと | monthly |
onMonthDay(day) |
毎月1日 9時 |
イベント型(SpreadsheetTriggerBuilder)
| タイプ | type 値 |
GAS メソッド | 発火タイミング |
|---|---|---|---|
| 表示時 | onOpen |
.forSpreadsheet(ss).onOpen() |
スプレッドシートを開いたとき |
| 編集時 | onEdit |
.forSpreadsheet(ss).onEdit() |
セルが編集されたとき |
| 変更時 | onChange |
.forSpreadsheet(ss).onChange() |
構造変更時(行挿入・列削除等) |
| フォーム送信時 | onFormSubmit |
.forSpreadsheet(ss).onFormSubmit() |
フォーム回答送信時 |
シンプルトリガーとインストーラブルトリガーの違い
GAS のトリガーにはシンプルトリガーとインストーラブルトリガーの2種類があります。
-
シンプルトリガー —
onOpen()やonEdit()のように、予約された関数名を定義するだけで自動的に動作する。設定不要だが、外部サービスの呼び出しや認証が必要な操作は実行できない -
インストーラブルトリガー —
ScriptApp.newTrigger()で明示的に作成する。シンプルトリガーより広い権限で実行でき、外部 API の呼び出しやメール送信なども可能
このテンプレートの menu.js にある
onOpen() はシンプルトリガーです。一方、TRIGGER_CONFIGS
で定義する
onOpen
タイプのトリガーはインストーラブルトリガーとして作成されます。
関数名が衝突しないよう、ハンドラ関数には
myOnOpenTask のように別名を使用してください。
config.js — トリガー定義
TRIGGER_CONFIGS
配列にトリガーを定義します。テンプレートには時間主導型5種 + イベント型2種 =
計7件のサンプルが含まれています。
var TRIGGER_CONFIGS = [
// --- 分ごと ---
{
functionName: 'myFrequentTask',
label: '高頻度タスク(5分ごと)',
type: 'minutes',
minutes: 5
},
// --- 時間ごと ---
{
functionName: 'myHourlyTask',
label: '毎時タスク',
type: 'hourly',
hours: 1,
minute: 30
},
// --- 日ごと ---
{
functionName: 'myDailyTask',
label: 'デイリータスク',
type: 'daily',
hour: 9,
minute: 0
},
// --- 週ごと ---
{
functionName: 'myWeeklyTask',
label: 'ウィークリータスク',
type: 'weekly',
hour: 10,
weekDay: ScriptApp.WeekDay.MONDAY
},
// --- 月ごと ---
{
functionName: 'myMonthlyTask',
label: 'マンスリータスク',
type: 'monthly',
hour: 9,
monthDay: 1
},
// --- イベント型(スプレッドシート) ---
{
functionName: 'myOnOpenTask',
label: 'スプレッドシート表示時タスク',
type: 'onOpen'
},
{
functionName: 'myOnEditTask',
label: 'セル編集時タスク',
type: 'onEdit'
}
];
各タイプの設定プロパティ
共通プロパティ
| プロパティ | 説明 |
|---|---|
functionName |
トリガーで呼び出す関数名(main.js で定義) |
label |
管理画面やログに表示するラベル |
type |
トリガータイプ(上記の表を参照) |
共通オプション(省略可)
| プロパティ | 説明 | 対象タイプ |
|---|---|---|
hour |
実行時刻(0〜23)。atHour() に対応 |
daily, weekly, monthly |
minute |
分指定(0〜59)。nearMinute() に対応。±15分の揺れあり
|
時間主導型(minutes 以外) |
timezone |
タイムゾーン。inTimezone()
に対応。省略時はスクリプトのタイムゾーン
|
時間主導型 |
spreadsheetId |
対象スプレッドシートID。省略時はバインド先 | イベント型 |
※ minute の
nearMinute() は、GAS
が指定した分の前後15分の範囲で実行する仕組みです。正確な分指定ではなく、おおよその目安として使用します。
triggers.js — トリガー管理のコア処理
triggers.js は3層の構造になっています。
-
コア処理(
_サフィックス付き) — UI に依存しないトリガー操作の本体 - UI 版 — カスタムメニューから呼び出す。確認ダイアログを表示
-
Headless 版 —
clasp runから呼び出す。戻り値とコンソール出力で結果を返す
※ GAS では関数名の末尾に _(アンダースコア)を付けると、GAS
エディタの関数一覧やカスタムメニューの候補に表示されなくなります。外部から直接呼び出さないヘルパー関数には
_ を付けるのが慣習です。
setupTriggerCore_() — トリガー作成の仕組み
トリガーの作成は setupTriggerCore_() が担当します。config
のタイプに応じて、時間主導型とイベント型で処理を分岐します。
function setupTriggerCore_(config) {
var eventTypes = ['onOpen', 'onEdit', 'onChange', 'onFormSubmit'];
var trigger;
var builder;
if (eventTypes.indexOf(config.type) !== -1) {
// イベント型(スプレッドシート)
var ss;
if (config.spreadsheetId) {
ss = SpreadsheetApp.openById(config.spreadsheetId);
} else {
ss = SpreadsheetApp.getActive();
if (!ss) {
throw new Error(
config.functionName + ': イベント型トリガーを clasp run から設定する場合は '
+ 'config に spreadsheetId を指定してください。'
);
}
}
builder = ScriptApp.newTrigger(config.functionName).forSpreadsheet(ss);
switch (config.type) {
case 'onOpen': builder.onOpen(); break;
case 'onEdit': builder.onEdit(); break;
case 'onChange': builder.onChange(); break;
case 'onFormSubmit': builder.onFormSubmit(); break;
}
trigger = builder.create();
} else {
// 時間主導型
builder = ScriptApp.newTrigger(config.functionName).timeBased();
switch (config.type) {
case 'minutes':
builder.everyMinutes(config.minutes);
break;
case 'hourly':
builder.everyHours(config.hours || 1);
break;
case 'daily':
builder.everyDays(1);
break;
case 'weekly':
builder.everyWeeks(1).onWeekDay(config.weekDay);
break;
case 'monthly':
builder.onMonthDay(config.monthDay);
break;
}
if (config.hour !== undefined) {
builder.atHour(config.hour);
}
if (config.minute !== undefined) {
builder.nearMinute(config.minute);
}
if (config.timezone) {
builder.inTimezone(config.timezone);
}
trigger = builder.create();
}
saveStoredConfig_(trigger.getUniqueId(), config);
var description = formatSchedule_(config);
Logger.log(config.functionName + ' を設定しました: ' + description);
return {
handlerFunction: config.functionName,
label: config.label,
schedule: description
};
}
処理の流れ:
-
config.typeがイベント型(onOpen/onEdit/onChange/onFormSubmit)かどうかを判定 -
イベント型の場合は
forSpreadsheet()でスプレッドシートを指定してビルダーを作成 -
時間主導型の場合は
timeBased()でビルダーを作成し、タイプに応じてスケジュールを設定 -
共通オプション(
hour/minute/timezone)があれば追加 builder.create()でトリガーを作成saveStoredConfig_()で設定値を ScriptProperties に保存
deleteTriggerCore_() — トリガーの削除
deleteTriggerCore_() は
functionName でマッチングして削除します。
function deleteTriggerCore_(config) {
var triggers = ScriptApp.getProjectTriggers();
var count = 0;
triggers.forEach(function(trigger) {
if (trigger.getHandlerFunction() === config.functionName) {
deleteStoredConfig_(trigger.getUniqueId());
ScriptApp.deleteTrigger(trigger);
count++;
}
});
Logger.log(config.functionName + ': ' + count + '件削除');
return count;
}
ScriptApp.getProjectTriggers()
でプロジェクト内の全トリガーを取得し、getHandlerFunction()
が一致するものだけを削除します。同じ関数名のトリガーが複数あっても、すべて削除されます。
ScriptProperties による設定値の永続化
トリガー作成時に、config
の設定値(ラベル・タイプ・スケジュール等)を ScriptProperties に保存しています。
function saveStoredConfig_(triggerId, config) {
var snapshot = {
label: config.label,
functionName: config.functionName,
type: config.type
};
if (config.hour !== undefined) { snapshot.hour = config.hour; }
if (config.minute !== undefined) { snapshot.minute = config.minute; }
if (config.minutes !== undefined) { snapshot.minutes = config.minutes; }
// ... 他のプロパティも同様
var props = PropertiesService.getScriptProperties();
props.setProperty('trigger_' + triggerId, JSON.stringify(snapshot));
}
保存した設定値はトリガー一覧表示(showTriggerStatus)でスケジュールの日本語表示に使われます。resolveConfigForTrigger_()
が保存値を優先的に参照し、見つからなければ TRIGGER_CONFIGS から
functionName で検索するフォールバック構成です。
function resolveConfigForTrigger_(trigger) {
var stored = getStoredConfig_(trigger.getUniqueId());
if (stored) return stored;
return findTriggerConfig_(trigger.getHandlerFunction());
}
formatSchedule_() — スケジュール表示
トリガーのスケジュールを日本語で返す関数です。一覧表示や確認ダイアログで使用されます。
function formatSchedule_(config) {
var minuteSuffix = (config.minute !== undefined) ? '(' + config.minute + '分頃)' : '';
var hourSuffix = (config.hour !== undefined) ? ' ' + config.hour + '時' : '';
switch (config.type) {
case 'minutes':
return config.minutes + '分ごと';
case 'hourly':
return (config.hours || 1) + '時間ごと' + minuteSuffix;
case 'daily':
return '毎日' + hourSuffix;
case 'weekly':
var dayLabels = {};
dayLabels[ScriptApp.WeekDay.MONDAY] = '月';
// ... 他の曜日も同様
var dayLabel = dayLabels[config.weekDay] || '?';
return '毎週' + dayLabel + '曜' + hourSuffix;
case 'monthly':
return '毎月' + config.monthDay + '日' + hourSuffix;
case 'onOpen':
return 'スプレッドシート表示時';
case 'onEdit':
return 'セル編集時';
// ...
}
}
UI 版と Headless 版
setupTrigger() /
deleteTrigger() は UI 版で、SpreadsheetApp.getUi()
を使って確認ダイアログを表示します。カスタムメニューから呼び出します。
setupTriggerHeadless() /
deleteTriggerHeadless() は Headless 版で、clasp run
から呼び出します。引数なしで実行すると確認モード(ドライラン)になり、--params '[true]'
を付けると実際に実行します。
# 確認モード(設定内容を表示するのみ)
clasp run setupTriggerHeadless
# 実行モード(実際にトリガーを設定)
clasp run setupTriggerHeadless --params '[true]'
SpreadsheetApp.getUi() は Headless 実行(clasp run)では使えないため、この2系統に分けています。
menu.js — カスタムメニュー
スプレッドシートを開くと「トリガー管理」メニューが自動的に追加されます。
/**
* スプレッドシートを開いたときに「トリガー管理」メニューを追加する(Simple Trigger)
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('トリガー管理')
.addItem('トリガー一覧を確認', 'showTriggerStatus')
.addItem('トリガーを設定', 'setupTrigger')
.addItem('トリガーを削除', 'deleteTrigger')
.addToUi();
}
この
onOpen()
はシンプルトリガーとして動作します。スプレッドシートを開いたときに GAS
が自動的に呼び出すため、明示的なトリガー設定は不要です。
| メニュー項目 | 実行される関数 | 用途 |
|---|---|---|
| トリガー一覧を確認 | showTriggerStatus() |
設定済みトリガーのラベル・スケジュール・種別を一覧表示 |
| トリガーを設定 | setupTrigger() |
TRIGGER_CONFIGS のトリガーを一括作成 |
| トリガーを削除 | deleteTrigger() |
管理対象トリガーを一括削除 |
main.js — ハンドラ関数
main.js
にはトリガーから呼び出されるサンプル関数を配置しています。実際のプロジェクトでは、ここに業務ロジックを実装してください。
/** デイリータスク(毎日実行) */
function myDailyTask() {
Logger.log('デイリータスクを実行しました: ' + new Date().toLocaleString('ja-JP'));
// TODO: ここに毎日実行したい処理を実装
}
/** セル編集時タスク(インストーラブルトリガー) */
function myOnEditTask(e) {
if (!e || !e.range) {
Logger.log('myOnEditTask: イベントオブジェクトがありません(トリガー経由で実行してください)');
return;
}
Logger.log('セルが編集されました: ' + e.range.getA1Notation());
// TODO: ここにセル編集時の処理を実装
}
イベントオブジェクト
イベント型トリガーで呼び出される関数には、イベントオブジェクト
e が渡されます。
| type | 主要プロパティ | 説明 |
|---|---|---|
onOpen |
e.source |
開かれたスプレッドシート |
onEdit |
e.range, e.value,
e.oldValue
|
編集されたセル範囲・編集後の値・編集前の値 |
onChange |
e.changeType |
変更種別(EDIT,
INSERT_ROW 等)
|
onFormSubmit |
e.values,
e.namedValues, e.range
|
フォーム回答の値・名前付き値・書き込み先範囲 |
イベントオブジェクトはトリガー経由でのみ渡されるため、GAS エディタから手動実行した場合は
undefined になります。上記の
myOnEditTask のように、e
の存在チェックを入れておくと安全です。
カスタマイズ方法
main.jsのサンプル関数を実際の業務ロジックに書き換える-
関数名を変更する場合は、
config.jsのTRIGGER_CONFIGSのfunctionNameも合わせて変更する - 不要なトリガーは
TRIGGER_CONFIGSから削除する - 変更後は
clasp push→ トリガーの再設定を実行
セットアップと使い方
前提
本記事は次の環境を前提とします。
- Google アカウント(GAS プロジェクトを作成・所有できる)
- Node.js v20.x 以降
@google/claspv3.x 以降
初回セットアップ
1. GAS プロジェクトを作成
新規スプレッドシートを作成し、「拡張機能」>「Apps Script」で GAS プロジェクトを作成します。
2. clasp を設定
GAS エディタの 設定(歯車アイコン)から Script ID を取得し、.clasp.json
を作成します。
# .clasp.json を作成し、scriptId を実際の値に書き換え
cp .clasp.json.example .clasp.json
# clasp にログイン(未ログインの場合)
clasp login
# ログイン確認(ブラウザで GAS エディタが開けば OK)
clasp open-script
3. コードをデプロイ
# GAS プロジェクトにコードを反映
clasp push
初回 push 時に「Manifest file has been updated. Do you want to push and overwrite?」と表示されます。Yes を選択してください。
4. 権限を承認
初回の push 後、GAS エディタで
myDailyTask
を手動実行してください。「承認が必要です」ダイアログが表示されます。
- 権限を確認 をクリック
- Google アカウントを選択
- 「アクセスできる情報を選択してください」で すべて選択 にチェック
- 続行 をクリック
※ この承認は初回のみ必要です。appsscript.json
のスコープを変更して再 push した場合は、再度承認が必要になります。
5. トリガーを設定
以下のいずれかの方法で、TRIGGER_CONFIGS
に定義されたトリガーを一括設定します。
方法 A: カスタムメニューから操作
スプレッドシートを開き、「トリガー管理」メニュー →「トリガーを設定」を選択します。確認ダイアログに設定予定のトリガー一覧が表示されるので、「はい」を選ぶとトリガーが一括作成されます。
方法 B: clasp run から操作
clasp run を使う場合は、事前に GCP プロジェクトの設定と実行可能
API のデプロイが必要です。詳細は
リポジトリの README
を参照してください。
# ステップ1: 設定内容を確認(ドライラン)
clasp run setupTriggerHeadless
現在のトリガー状態と設定予定のトリガーが表示されます。内容を確認したら、実際に設定を実行します。
# ステップ2: 実際にトリガーを設定
clasp run setupTriggerHeadless --params '[true]'
※ *Headless 関数は
clasp run 専用です。GAS
エディタから直接実行した場合、引数を渡せないため確認モード(ドライラン)で停止します。
トリガーの削除
トリガーを削除したい場合は、カスタムメニューの「トリガーを削除」、または以下のコマンドで削除できます。
# 削除対象を確認
clasp run deleteTriggerHeadless
# 実際に削除
clasp run deleteTriggerHeadless --params '[true]'
トリガー一覧の確認
設定済みトリガーの一覧を確認するには、カスタムメニューの「トリガー一覧を確認」、または以下のコマンドを使用します。
clasp run showTriggerStatusHeadless
制限事項と注意点
コード管理と手動管理の混在を避ける
トリガーの管理方法はどちらか一方に統一してください。GAS エディタのトリガー管理画面からの手動操作と、コードによる管理を混在させると、トリガーの重複や削除漏れの原因になります。
操作対象の範囲
setupTrigger /
deleteTrigger は
TRIGGER_CONFIGS の
functionName に一致するトリガーだけを操作します。GAS エディタのトリガー管理画面から手動追加されたトリガーとの関係は以下の通りです。
| 手動追加したトリガーの関数名 | 一覧表示 | トリガー設定 | トリガー削除 |
|---|---|---|---|
TRIGGER_CONFIGS と同じ |
表示される | 削除して再作成される | 削除される |
TRIGGER_CONFIGS と異なる |
表示される | 影響なし(残る) | 影響なし(残る) |
- GAS エディタから手動でトリガーを追加すると、同じ関数名のトリガーが重複し、処理が二重実行される原因になります
-
手動追加したトリガーを
deleteTriggerで削除するには、該当のfunctionNameがTRIGGER_CONFIGSに定義されている必要があります
サンプルコード全体
完全なコードは GitHub リポジトリで公開しています。リポジトリには clasp の設定サンプルやマニフェストファイルも含まれています。
GAS によるスプレッドシート連携の実用例として、トリガーを使った自動メール通知の記事も公開しています。