GASでスプレッドシートを自動化してマスターデータから帳票を自動作成できるようになる
Phase 1: GASの基礎とスプレッドシート操作
Googleが提供する無料のプログラミング環境です。スプレッドシート・Gmail・Googleドライブと直接連携でき、サーバーなしでクラウド上で自動処理が実行されます。Googleアカウントがあれば今すぐ使えます。
メール送信: 100通/日 / トリガー実行: 90分/日(累計) / URLフェッチ: 20,000回/日 / スクリプト1回の実行: 最大6分
STEP 1-1: はじめてのGASスクリプト作成
Googleスプレッドシートのメニューバー「拡張機能」→「Apps Script」をクリックすると新しいタブでGASエディタが開きます。以下のコードを貼り付けて実行してみましょう。
▲ Google Apps Script のエディタ画面
// はじめてのGASスクリプト function helloGAS() { // アクティブなスプレッドシートを取得 const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getActiveSheet(); // A1セルに文字を書き込む sheet.getRange('A1').setValue('こんにちは、GAS!'); // B1セルに現在の日時を書き込む const now = new Date(); sheet.getRange('B1').setValue(now); // ログに出力(デバッグ用) console.log('スクリプト実行完了'); Logger.log('実行日時: ' + now); }
1. コードを貼り付けたら「💾 保存」ボタン(Ctrl+S)を押す
2. 「▶ 実行」ボタンを押す
3. 初回は「承認が必要です」ダイアログ → 「権限を確認」→ Googleアカウントを選択 → 「詳細」→「(安全でないページ)に移動」→「許可」
4. スプレッドシートのA1セルに「こんにちは、GAS!」と表示されれば成功
Google AI StudioでGASコードを生成するプロンプト例:
あなたはGoogle Apps Script(GAS)の専門家です。 以下の要件を満たすGASコードを書いてください。 【要件】 - Googleスプレッドシートの「データ」シートのA列に入力された名前と B列に入力された数値を読み取り - C列に「名前: ○○ / 数値: ○○」という形式でテキストを書き込む - 空白行が出てきたら処理を止める 【出力形式】 - コードのみ(説明不要) - コメントを日本語で記述すること
⚠️ エラーが出た場合
「このアプリはGoogleで確認されていません」と表示される
→ これは正常な動作です。「詳細」→「(安全でないページ)に移動」を選んでください。自分で作ったスクリプトなので安全です。
「TypeError: Cannot read properties of undefined」
→ シート名が合っていない可能性があります。ss.getActiveSheet() の代わりに ss.getSheetByName('シート名') で明示的に指定してください。
実行しても何も起きない
→ エディタ上部のドロップダウンで実行したい関数名(例: helloGAS)が選択されているか確認してください。
Google AI Studioで生成したコードをどこに貼ればよいか分からない
→ GASエディタの function myFunction() {} 部分を全選択して削除し、生成されたコードを貼り付けてください。
STEP 1-2: スプレッドシートのデータ操作
GASでスプレッドシートのデータを読み書きする基本的な方法を学びます。getRange・getValues・setValues を使いこなすことが自動化の核心です。
▲ Google AI Studio でGASのデータ操作コードを生成
function readData() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName('データ'); // 単一セルの読み取り const singleValue = sheet.getRange('A1').getValue(); console.log('A1の値: ' + singleValue); // 複数セルを2次元配列で取得(A1からC10まで) const rangeValues = sheet.getRange('A1:C10').getValues(); // 配列をループして処理 rangeValues.forEach(function(row, rowIndex) { if (row[0] !== '') { // 空白行はスキップ console.log('行' + (rowIndex + 1) + ': ' + row[0] + ', ' + row[1]); } }); // データが入っている最終行を取得 const lastRow = sheet.getLastRow(); console.log('最終行: ' + lastRow); } function writeData() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName('データ'); // まとめて書き込む(setValues)※2次元配列で渡す const writeValues = [ ['田中 太郎', 10000], ['鈴木 花子', 25000], ['佐藤 次郎', 15000], ]; sheet.getRange(2, 1, writeValues.length, 2).setValues(writeValues); console.log('書き込み完了'); }
⚠️ エラーが出た場合
「Exception: The number of rows in the range must be at least 1」
→ getLastRow() が1(ヘッダーのみ)の場合、データが0件です。if (lastRow < 2) return; で早期リターンしてください。
getValuesで取得した配列が思ったサイズと違う
→ getRange(startRow, startCol, numRows, numCols) の引数の順番を確認してください。行番号→列番号→行数→列数の順です。
setValuesで「The number of columns in the data does not match」エラー
→ setValues に渡す2次元配列の列数と getRange で指定した列数が一致していないことが原因です。
STEP 1-3: カスタムメニューの作成
onOpen 関数を使うと、スプレッドシートを開いたときに自動でカスタムメニューが追加されます。これにより、GASの機能をボタン感覚で呼び出せるようになります。
▲ onOpen関数でカスタムメニューを追加するコード
// スプレッドシートを開いたときに自動実行される特別な関数 function onOpen() { const ui = SpreadsheetApp.getUi(); // 「帳票自動化」というメニューを追加 ui.createMenu('帳票自動化') .addItem('見積書を作成', 'createEstimate') .addItem('請求書を作成', 'createInvoice') .addSeparator() .addItem('月次レポート送信', 'sendMonthlyReport') .addToUi(); } // 入力ダイアログ(テキスト入力) function promptExample() { const ui = SpreadsheetApp.getUi(); const result = ui.prompt('顧客IDを入力してください'); if (result.getSelectedButton() === ui.Button.OK) { const inputText = result.getResponseText(); console.log('入力値: ' + inputText); } }
onOpen は保存後、スプレッドシートを一度閉じて再度開くとメニューが表示されます。GASエディタから手動で onOpen を実行してもメニューは追加されません。
⚠️ エラーが出た場合
メニューが表示されない
→ onOpen 関数を保存後、スプレッドシートを一度閉じて再度開いてください。
「TypeError: ui.createMenu is not a function」
→ SpreadsheetApp.getUi() が正しい書き方です。ss.getUi() ではありません。
スクリプトを保存したのにメニューに反映されない
→ ブラウザのキャッシュが残っている場合があります。Ctrl+Shift+R(Mac: Cmd+Shift+R)で強制リロードしてみてください。
☐ GASエディタを開いてHello Worldを実行できた
☐ スプレッドシートのA1セルに文字を書き込めた
☐ getValuesで複数セルのデータを配列で取得できた
☐ onOpen関数でカスタムメニューが表示された
☐ Browser.msgBoxで確認ダイアログを表示できた
☐ console.log / Logger.logでデバッグログを確認できた
Phase 2: マスターデータから帳票を自動生成
スプレッドシートに以下の4つのシートを作成してください:
・顧客マスター: 顧客ID / 顧客名 / 会社名 / 郵便番号 / 住所 / メールアドレス
・商品マスター: 商品ID / 商品名 / 単価 / 単位 / 税率
・番号管理: 種別(見積書/請求書) / 最終番号
・見積書テンプレート: 帳票フォーマットのベース
STEP 2-1: 見積書の自動作成
顧客マスターから顧客情報を検索し、見積書テンプレートをコピーして新しいシートを作成します。見積番号は「EST-0001」形式で自動採番されます。
▲ 見積書自動作成スクリプトのコード例
Apps Scriptエディタの画面イメージです。実際にコードを動かすには、下のコードブロックをコピーしてGASエディタに貼り付けてから実行してください。
初心者向け手順: Google スプレッドシートを開く → 上メニューの「拡張機能」→「Apps Script」→ エディタが開いたらコードを貼り付けて「保存」→「実行」
/** * 見積番号を採番して返す */ function getNextNumber(type) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const mgmtSheet = ss.getSheetByName('番号管理'); const data = mgmtSheet.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0] === type) { const nextNum = data[i][1] + 1; mgmtSheet.getRange(i + 1, 2).setValue(nextNum); const prefix = type === '見積書' ? 'EST' : 'INV'; return prefix + '-' + String(nextNum).padStart(4, '0'); } } return null; } /** * 見積書を自動作成する */ function createEstimate() { const ui = SpreadsheetApp.getUi(); const ss = SpreadsheetApp.getActiveSpreadsheet(); // 顧客IDを入力させる const promptResult = ui.prompt('顧客IDを入力してください(例: C001)'); if (promptResult.getSelectedButton() !== ui.Button.OK) return; const customerId = promptResult.getResponseText().trim(); // 顧客マスターから顧客情報を検索 const customerSheet = ss.getSheetByName('顧客マスター'); const customerData = customerSheet.getDataRange().getValues(); const customer = customerData.find(row => row[0] === customerId); if (!customer) { ui.alert('エラー', '顧客ID「' + customerId + '」は見つかりませんでした。', ui.ButtonSet.OK); return; } // テンプレートをコピーして新しいシートを作成 const templateSheet = ss.getSheetByName('見積書テンプレート'); const estimateNo = getNextNumber('見積書'); const newSheet = templateSheet.copyTo(ss); newSheet.setName('見積書_' + estimateNo); // 発行日・見積番号・顧客情報を書き込む const today = new Date(); const formattedDate = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy年MM月dd日'); newSheet.getRange('B2').setValue(estimateNo); newSheet.getRange('B3').setValue(formattedDate); newSheet.getRange('B5').setValue(customer[2]); // 会社名 newSheet.getRange('B6').setValue(customer[1] + ' 様'); // 顧客名 ui.alert('完了', '見積書「' + estimateNo + '」を作成しました。', ui.ButtonSet.OK); ss.setActiveSheet(newSheet); } /** * アクティブシートをPDFとして保存する */ function exportToPDF() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getActiveSheet(); SpreadsheetApp.flush(); // 計算を確定させる const pdfBlob = sheet.getAs(MimeType.PDF); pdfBlob.setName(sheet.getName() + '.pdf'); const file = DriveApp.createFile(pdfBlob); SpreadsheetApp.getUi().alert('PDF保存完了', 'URL:\n' + file.getUrl(), SpreadsheetApp.getUi().ButtonSet.OK); }
⚠️ エラーが出た場合
「Exception: You do not have permission to call DriveApp.createFile」
→ GASエディタの「プロジェクトの設定」→「スコープ」で https://www.googleapis.com/auth/drive が含まれているか確認し、再度承認してください。
テンプレートシートが見つからない(null)
→ getSheetByName('見積書テンプレート') のシート名がスプレッドシート上のタブ名と完全一致しているか確認してください。全角スペース・半角スペースの違いに注意。
getAs(MimeType.PDF) でエラーになる
→ 'application/pdf' と文字列で書くことでも代用できます。
PDFが空白で出力される
→ SpreadsheetApp.flush() をPDF出力前に呼び出してください。
STEP 2-2: 請求書・納品書の自動作成(一括生成)
Utilities.formatDate で日付を整形し、全顧客に対して請求書を一括生成する関数を実装します。支払期限は翌月末を自動計算します。
/** * 日付フォーマットのサンプル集 */ function dateFormatExamples() { const now = new Date(); const tz = 'Asia/Tokyo'; console.log(Utilities.formatDate(now, tz, 'yyyy年MM月dd日')); // 2026年03月19日 console.log(Utilities.formatDate(now, tz, 'yyyy/MM/dd')); // 2026/03/19 // 翌月末(支払期限に使用) const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 2, 0); console.log(Utilities.formatDate(lastDayOfMonth, tz, 'yyyy年MM月dd日')); } /** * 全顧客に対して請求書を一括生成する */ function createAllInvoices() { const ui = SpreadsheetApp.getUi(); const response = ui.alert('一括生成確認', '全顧客分の請求書を一括生成します。よろしいですか?', ui.ButtonSet.YES_NO); if (response !== ui.Button.YES) return; const ss = SpreadsheetApp.getActiveSpreadsheet(); const customerSheet = ss.getSheetByName('顧客マスター'); const lastRow = customerSheet.getLastRow(); if (lastRow < 2) { ui.alert('顧客マスターにデータがありません。'); return; } const customers = customerSheet.getRange(2, 1, lastRow - 1, 6).getValues(); let createdCount = 0; try { customers.forEach(function(customer) { if (customer[0] === '') return; const invoiceNo = getNextNumber('請求書'); const templateSheet = ss.getSheetByName('請求書テンプレート'); const newSheet = templateSheet.copyTo(ss); newSheet.setName('請求書_' + invoiceNo); const today = new Date(); const dueDate = new Date(today.getFullYear(), today.getMonth() + 2, 0); const tz = 'Asia/Tokyo'; newSheet.getRange('B2').setValue(invoiceNo); newSheet.getRange('B3').setValue(Utilities.formatDate(today, tz, 'yyyy年MM月dd日')); newSheet.getRange('B4').setValue(Utilities.formatDate(dueDate, tz, 'yyyy年MM月dd日')); newSheet.getRange('B5').setValue(customer[2]); newSheet.getRange('B6').setValue(customer[1] + ' 様'); SpreadsheetApp.flush(); exportToPDFInFolder(newSheet, '請求書_' + Utilities.formatDate(today, tz, 'yyyyMM')); createdCount++; }); ui.alert('完了', createdCount + '件の請求書を作成しました。', ui.ButtonSet.OK); } catch (e) { ui.alert('エラーが発生しました: ' + e.message); } }
⚠️ エラーが出た場合
一括生成が途中で止まる
→ GASの1回の実行時間制限は6分です。顧客数が多い場合は処理が中断されます。処理の進捗を「番号管理」シートに記録して中断した番号から再開できるよう設計してください。
同じシート名が既に存在してエラーになる
→ setName 前に同名シートがないか確認してください。
PDFのレイアウトが崩れる
→ テンプレートシートで「ファイル」→「印刷」→用紙サイズ・余白を設定してから再試行してください。
STEP 2-3: 進捗表の自動更新
onEdit トリガーを使って、「進捗管理」シートのステータス列が変更されたら自動で日時を記録し、行の背景色を変更します。
/** * onEdit トリガー: ステータス変更を検知して日付を自動記録 */ function onEdit(e) { const sheet = e.source.getActiveSheet(); if (sheet.getName() !== '進捗管理') return; const editedCol = e.range.getColumn(); const editedRow = e.range.getRow(); // C列(ステータス列)が編集された場合 if (editedCol === 3 && editedRow >= 2) { const status = e.range.getValue(); const dateStr = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm'); sheet.getRange(editedRow, 4).setValue(dateStr); if (status === '完了') { sheet.getRange(editedRow, 5).setValue(dateStr); } setRowColor(sheet, editedRow, status); } } /** * ステータスに応じて行の背景色を変更する */ function setRowColor(sheet, row, status) { const colorMap = { '未着手': '#ffffff', '進行中': '#fff3cd', '確認中': '#cce5ff', '完了': '#d4edda', '中断': '#f8d7da', }; const color = colorMap[status] || '#ffffff'; sheet.getRange(row, 1, 1, sheet.getLastColumn()).setBackground(color); }
⚠️ エラーが出た場合
onEdit が動かない
→ GASエディタから手動実行するとエラーになります(e オブジェクトが存在しないため)。スプレッドシートのセルを実際に編集して動作を確認してください。
onEdit でGmailやドライブの操作ができない
→ シンプルトリガーは権限が制限されています。GmailやDriveを使う場合はインストール可能トリガー(STEP 3-1参照)を使ってください。
setBackground が反映されない
→ setBackgroundColor というメソッドは存在しません。setBackground(引数は16進カラーコード)が正しいメソッド名です。
☐ 顧客マスター・商品マスターシートを設計して作成できた
☐ 見積書テンプレートシートからコピーして帳票を自動生成できた
☐ 見積番号を自動採番する仕組みを実装できた
☐ 生成した帳票をGoogleドライブにPDFとして保存できた
☐ 請求書の日付・番号を自動設定できた
☐ 進捗管理シートのステータス変更で自動日付記録ができた
☐ setBackgroundColorで条件付き書式を自動設定できた
Phase 3: トリガーとGmail連携
STEP 3-1: トリガーの設定(自動実行)
時間ベーストリガーを設定することで、毎日・毎月など指定した時間に関数を自動実行できます。GASエディタの左メニュー「⏰ トリガー」から設定します。
▲ トリガー設定スクリプト
/** * トリガーをコードから登録する(1回だけ実行する) */ function createTriggers() { deleteTriggers('dailyCheck'); ScriptApp.newTrigger('dailyCheck') .timeBased() .everyDays(1) .atHour(8) .create(); console.log('トリガーを登録しました'); } /** * 指定した関数名のトリガーを全削除 */ function deleteTriggers(funcName) { ScriptApp.getProjectTriggers().forEach(trigger => { if (trigger.getHandlerFunction() === funcName) { ScriptApp.deleteTrigger(trigger); } }); }
onOpen: スプレッドシートを開いたとき(カスタムメニューの追加)onEdit: セルを編集したとき(ステータス変更の検知)onChange: シート構造が変わったとき時間ベース: 設定した時間に(定期実行、月次レポート)
⚠️ エラーが出た場合
トリガーが実行されない
→ GASエディタの「実行」→「実行履歴」でトリガーの実行状況を確認できます。
「承認が必要です」メールが届く
→ GASエディタを開いて手動で一度関数を実行し、承認ダイアログを完了させてください。
トリガーが二重に登録されてしまう
→ deleteTriggers で一度削除してから登録し直してください。
STEP 3-2: GmailからのメールをGASで自動処理
GmailApp.search() で条件に合うメールを取得し、スプレッドシートに記録します。「発注」が件名に含まれる未読メールを自動で取り込む例です。
/** * 受信メールを読み取ってスプレッドシートに記録する */ function importEmailsToSheet() { const ss = SpreadsheetApp.getActiveSpreadsheet(); let sheet = ss.getSheetByName('メール受信ログ'); if (!sheet) { sheet = ss.insertSheet('メール受信ログ'); sheet.appendRow(['受信日時', '送信者', '件名', '本文(先頭200文字)', '処理済み']); } try { // 件名に「発注」が含まれる未読メールを最大10件取得 const threads = GmailApp.search('subject:発注 is:unread', 0, 10); threads.forEach(thread => { thread.getMessages().forEach(message => { const date = message.getDate(); const from = message.getFrom(); const subject = message.getSubject(); const body = message.getPlainBody().substring(0, 200); const dateStr = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm'); sheet.appendRow([dateStr, from, subject, body, '未処理']); message.markRead(); }); }); console.log('メール取り込み完了: ' + threads.length + '件'); } catch (e) { console.error('メール取り込みエラー: ' + e.message); } }
⚠️ エラーが出た場合
「Exception: You do not have permission to call GmailApp」
→ エディタから手動で一度実行して承認フローを完了させてください。
threads.length が常に0になる
→ まず GmailApp.getInboxThreads(0, 5) で受信トレイの先頭5件を取得して動作確認してください。
STEP 3-3: GmailへのメールをGASで自動送信
GmailApp.sendEmail() で請求書PDFを添付してメールを送信します。送信履歴をシートに自動記録する仕組みも実装します。
▲ Google AI Studio でメール送信GASを生成
/** * 請求書PDFをGmailに添付して顧客に送信する */ function sendInvoiceEmail(invoiceSheetName, toEmail, customerName) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const invoiceSheet = ss.getSheetByName(invoiceSheetName); if (!invoiceSheet) { console.error('シートが見つかりません: ' + invoiceSheetName); return false; } try { SpreadsheetApp.flush(); const pdfBlob = invoiceSheet.getAs(MimeType.PDF); pdfBlob.setName(invoiceSheetName + '.pdf'); const body = customerName + ' 様\n\nお世話になっております。\n下記の通り、請求書をお送りいたします。\n\n添付のPDFをご確認ください。\n\nどうぞよろしくお願いいたします。'; GmailApp.sendEmail(toEmail, '【請求書送付】' + invoiceSheetName, body, { attachments: [pdfBlob], name: '自動送信システム' }); logEmailSent(toEmail, customerName, invoiceSheetName); console.log('請求書メール送信完了: ' + toEmail); return true; } catch (e) { console.error('メール送信エラー: ' + e.message); return false; } } /** * エラー発生時に通知メールを送る */ function sendErrorNotification(functionName, error) { const adminEmail = getAdminEmail(); const now = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'); GmailApp.sendEmail(adminEmail, '【GAS エラー通知】' + functionName, '発生日時: ' + now + '\n関数名: ' + functionName + '\nエラー内容: ' + error.message ); }
⚠️ エラーが出た場合
「Service invoked too many times in one day: email」
→ GmailAppのメール送信は1日100通の制限があります。複数日に分けて実行する仕組みを追加してください。
添付ファイルサイズエラー
→ Gmailの添付ファイル上限は25MBです。不要な画像を削除するか印刷範囲を絞り込んでください。
メールが送信されるが添付ファイルが壊れている
→ SpreadsheetApp.flush() を getAs() の直前に必ず呼び出してください。
STEP 3-4: 実践 - 月次レポートの完全自動化
月末に自動集計→PDF作成→メール送信を一連のフローで実行します。トリガーは毎月28日UTC13時(日本時間22時)に設定します。
/** * 月次レポートの完全自動化メイン関数 * トリガー:毎月末日 22:00 に実行 */ function sendMonthlyReport() { const tz = 'Asia/Tokyo'; const now = new Date(); const yearMonth = Utilities.formatDate(now, tz, 'yyyy年MM月'); try { // Step 1: 月次データを集計する const reportData = aggregateMonthlyData(now); // Step 2: レポートシートを作成・更新する const reportSheet = createMonthlyReportSheet(yearMonth, reportData); // Step 3: PDFとして保存する SpreadsheetApp.flush(); const pdfFile = exportToPDFInFolder(reportSheet, '月次レポート'); // Step 4: 管理者にメール送信する const adminEmail = getAdminEmail(); sendReportEmail(adminEmail, yearMonth, pdfFile); // Step 5: 送信完了をシートに記録する logEmailSent(adminEmail, '管理者', yearMonth + 'レポート'); console.log(yearMonth + ' 月次レポート送信完了'); } catch (e) { sendErrorNotification('月次レポート送信', e); } }
⚠️ エラーが出た場合
月次レポートが実行されない(トリガーが発火しない)
→ トリガーの「時刻」はGMT(UTC)で設定されます。日本時間22時に実行したい場合はUTC 13時(22 - 9 = 13)を設定してください。
「Exception: Service Spreadsheets failed while accessing document」
→ 他のユーザーが編集中の場合に発生します。LockService を使って排他制御を検討してください。
毎月の集計データが前月分と混在してしまう
→ 終了日に 23:59:59 を加えるか < で翌月1日と比較してください。
☐ 時間ベーストリガーを設定して毎日自動実行できた
☐ onEditトリガーでシート変更を検知できた
☐ GmailApp.getInboxThreads()でメールを取得してシートに記録できた
☐ GmailApp.sendEmail()でメールを送信できた
☐ 請求書PDFをGmailに添付して送信できた
☐ try-catchでエラー通知メールを実装できた
☐ 月次レポートの完全自動化フローを実装できた