GASでスプレッドシートに
カスタムメニューを追加する方法
スプレッドシート上部に独自のメニューを追加すれば、利用者はスクリプトエディタを開かずにボタン感覚で処理を実行できます。onOpenでの作成から、サブメニュー・確認ダイアログ・権限が必要な処理まで、動くコードで解説します。
Table of Contents
カスタムメニューとは何か
カスタムメニューとは、スプレッドシート上部の「ファイル」「編集」などが並ぶメニューバーに、GASで独自に追加できる自作のメニューです。 項目をクリックすると、あらかじめ指定したGAS関数が実行されます。
スクリプトエディタを開いて関数を選んで実行、という操作はGASに不慣れな利用者にはハードルが高いものです。 カスタムメニューを用意しておけば、利用者は普段の画面のまま「ボタンを押す感覚」で処理を呼び出せます。次のような場面で役立ちます。
定型処理の実行
日次集計や重複削除など、決まった処理を利用者自身に実行してもらう
帳票の書き出し
選択中の行をPDFやCSVに書き出す処理をワンクリックで呼び出す
外部サービス連携
SlackやAPIへの送信など、タイミングを人が決めたい処理を手動で起動する
設定・初期化
シートの初期化やトリガー登録など、管理者向けの操作をまとめておく
onOpenで基本のメニューを追加する
メニューはonOpen(スプレッドシートを開いた瞬間に自動で実行される特別な名前の関数)の中で組み立てます。getUi()でUI操作の窓口を取得し、createMenu()にメニュー名、addItem()に「表示名」と「実行する関数名」を渡します。
// スプレッドシートを開いた瞬間に自動で呼ばれる特別な関数
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("業務ツール") // メニュー名
.addItem("データを更新する", "updateData") // 表示名, 実行する関数名
.addToUi(); // これを呼ばないと表示されない
}
// メニューから呼ばれる関数
function updateData() {
SpreadsheetApp.getActiveSpreadsheet().toast("更新を実行しました");
}ポイントは最後のaddToUi()です。これを呼ばないとメニューは画面に反映されません。保存後にスプレッドシートを開き直すと、メニューバーに「業務ツール」が表示されます。toast()は画面右下に短いメッセージを出す関数です。
複数項目・サブメニュー・区切り線
addItem()をつなげれば項目を増やせます。項目が多くなってきたら、addSeparator()で区切り線を入れたり、addSubMenu()で入れ子のサブメニューにまとめると見やすくなります。
function onOpen() {
const ui = SpreadsheetApp.getUi();
// サブメニュー(入れ子のメニュー)を先に組み立てる
const exportMenu = ui.createMenu("エクスポート")
.addItem("PDFで書き出す", "exportPdf")
.addItem("CSVで書き出す", "exportCsv");
ui.createMenu("業務ツール")
.addItem("データを更新する", "updateData")
.addItem("重複を削除する", "removeDuplicates")
.addSeparator() // 区切り線
.addSubMenu(exportMenu) // サブメニューを追加
.addToUi();
}サブメニューは、先に子メニューをcreateMenu()で組み立てておき、親メニューのaddSubMenu()に渡すのがコツです。「エクスポート ›」にマウスを合わせると、PDF・CSVの項目が展開されます。
利用者やシートで項目を出し分ける
メニューはonOpenの中でコードとして組み立てるので、条件分岐で項目を出し分けできます。 たとえば「請求データ」シートを開いているときだけ「請求書を発行する」項目を表示する、といった制御が可能です。
function onOpen() {
const ui = SpreadsheetApp.getUi();
const menu = ui.createMenu("業務ツール");
menu.addItem("日次集計を実行", "runDailyReport");
// 開いているシート名によって項目を出し分ける
const sheetName = SpreadsheetApp.getActiveSheet().getName();
if (sheetName === "請求データ") {
menu.addItem("請求書を発行する", "issueInvoice");
}
menu.addToUi();
}管理者だけに特定の項目を見せたい場合は、Session.getActiveUser().getEmail()でメールアドレスを取得して分岐します。ただしこの取得は簡易トリガーでは空になることがあるため、確実に判定したい場合は次章のインストール型トリガーを使います。
メニューから確認ダイアログ・入力を出す
削除など取り消せない処理をメニューに載せるときは、実行前に確認ダイアログを出すと事故を防げます。ui.alert()にButtonSet.YES_NOを渡すと「はい/いいえ」の選択になり、ui.prompt()なら文字入力を受け取れます。
// 確認ダイアログ(はい / いいえ)
function removeDuplicates() {
const ui = SpreadsheetApp.getUi();
const answer = ui.alert(
"確認",
"重複行を削除します。よろしいですか?",
ui.ButtonSet.YES_NO
);
if (answer !== ui.Button.YES) return; // 「いいえ」なら中止
// ここに実際の削除処理を書く
ui.alert("重複行を削除しました。");
}
// 入力プロンプト(文字列を受け取る)
function searchByKeyword() {
const ui = SpreadsheetApp.getUi();
const res = ui.prompt("検索", "キーワードを入力してください", ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() !== ui.Button.OK) return;
const keyword = res.getResponseText();
SpreadsheetApp.getActiveSpreadsheet().toast("検索語: " + keyword);
}alert()は押されたボタン(ui.Button.YESなど)を返します。prompt()は結果オブジェクトを返すので、getSelectedButton()でボタンを、getResponseText()で入力文字列を取り出します。
権限が必要な処理はインストール型トリガーで
onOpenという名前の関数は「簡易トリガー」で動きます。簡易トリガーはUrlFetchApp(外部APIへの通信)やGmailApp(メール送信)など、承認が必要なサービスをメニュー構築の段階で呼べない制約があります。
メニュー自体を承認付きで表示したい場合は、ScriptApp.newTriggerで「インストール型のonOpenトリガー」を作成します。こちらは作成者の承認のもとで動くため、権限が必要な処理を含むメニューも扱えます。
// 一度だけ手動実行して、インストール型のonOpenトリガーを登録する
function setupMenuTrigger() {
const ss = SpreadsheetApp.getActive();
// 二重登録を防ぐため、同じ関数の既存トリガーを削除
ScriptApp.getProjectTriggers().forEach((t) => {
if (t.getHandlerFunction() === "buildMenu") {
ScriptApp.deleteTrigger(t);
}
});
ScriptApp.newTrigger("buildMenu")
.forSpreadsheet(ss)
.onOpen()
.create();
}
// インストール型トリガーは作成者の承認で動くので、
// UrlFetchAppなど権限が必要な処理を呼ぶメニューも作れる
function buildMenu() {
SpreadsheetApp.getUi()
.createMenu("業務ツール(拡張)")
.addItem("外部APIと同期する", "syncExternalApi")
.addToUi();
}setupMenuTriggerを一度だけ手動実行してトリガーを登録すれば、以降はシートを開くたびにbuildMenuが承認付きで動きます。なお、メニューの「表示」自体は簡易onOpenでも十分な場合が多く、個々の項目の関数を実行した時点で承認画面が出る運用でも問題ないケースがほとんどです。要件に応じて使い分けてください。
実務での注意点
1. addItemに渡すのは「関数名の文字列」
addItem("更新", updateData) のように関数そのものを渡すのは誤りで、addItem("更新", "updateData") と関数名を文字列で渡します。引数を直接渡すこともできないため、渡したい値はスクリプトプロパティやシートに置いて関数内で読み込みます。
2. 他の人が開いてもメニューは出るが、実行時に承認が必要
共有相手の画面にもメニューは表示されますが、項目を初めて実行するときには本人の承認画面が出ます。誰が実行しても動く運用にしたい場合は、Webアプリ化やインストール型トリガーなど別の設計を検討します。
3. メニューは開いた時点で1回作られるだけ
onOpenはシートを開いた瞬間だけ動きます。シートを開いたまま条件を変えても、その場ではメニューは更新されません。作り直したい場合はページを再読み込みするか、メニュー項目から再構築用の関数を呼ぶようにします。
まとめ
カスタムメニューは、onOpen で createMenu → addItem → addToUi を呼ぶだけで作れます。 サブメニューや区切り線で整理し、削除など危険な操作には確認ダイアログを添えると、GASに不慣れな利用者でも安心して使える業務ツールになります。
外部API連携など権限が必要な処理を含めるときは、簡易トリガーとインストール型トリガーの違いを押さえておきましょう。 作った関数をメニューから呼び出せるようにするだけで、スプレッドシートは「使える業務システム」に近づきます。
よくある質問
メニューを組み立てる関数名が onOpen になっているか、addToUi() を呼び忘れていないかを確認してください。onOpen はスプレッドシートを開いた瞬間に自動実行される特別な関数名です。エディタから onOpen を手動実行するか、シートを開き直すとメニューが表示されます。反映されない場合はブラウザの再読み込みも試してください。
onOpen という名前の関数は「簡易トリガー」で動くため、UrlFetchApp・GmailApp・別ファイルへのアクセスなど承認が必要なサービスを呼べません。外部APIやメール送信を含む処理をメニューから実行したい場合は、ScriptApp.newTrigger でインストール型の onOpen トリガーを別途作成します。こちらは作成者の承認で動くため制限がありません。
出せます。SpreadsheetApp.getUi().alert() に ui.ButtonSet.YES_NO を渡すと「はい/いいえ」の確認ダイアログを表示でき、戻り値が ui.Button.YES のときだけ処理を進める書き方ができます。入力欄が必要なら ui.prompt() を使うとユーザーに文字列を入力してもらえます。
できます。ui.createMenu() で作った子メニューを、親メニューの addSubMenu() に渡すと入れ子のメニューになります。項目が多いときはサブメニューで「エクスポート」「集計」などにグループ分けすると見やすくなります。addSeparator() で区切り線も入れられます。
onOpen の中で条件分岐を書けば可能です。Session.getActiveUser().getEmail() で開いた人のメールアドレスを取得したり、開いているシート名を見たりして、addItem() を呼ぶかどうかを分岐します。ただし簡易トリガーではメールアドレスが取得できない場合があるため、確実に判定したいならインストール型トリガーを使います。
GAS開発・業務Webシステムを
相談する。
カスタムメニューの設計はもちろん、フォーム連携・帳票出力・Gmail自動化など、日々の業務を自動化するGAS開発をご相談いただけます。