Googleフォーム×GASでファイル添付エラーを防ぐ安全なコード設計
GoogleフォームとGoogle Apps Script(GAS)を組み合わせて、フォームの送信内容を自動でメール通知したり、他のシステムへ連携したりする仕組みは、業務効率化の定番です。しかし、運用を始めてから次のようなトラブルに直面したことはありませんか?
- 「フォームでファイルが添付されずに送信されたら、GASがエラーで止まってしまった…」
- 「DriveApp.getFileById() でエラーが発生し、後続のメール送信処理が行われない…」
添付ファイルが任意項目の場合や、取得タイミングのズレなどにより、ファイルIDが予期せぬ形式で渡されると、プログラム全体が停止してしまうリスクがあります。
本記事では、ファイル未添付や取得エラーが発生しても止まらない、エラーに強いGASの安全なコード設計を具体的に解説します。実際の運用コードと解説をまとめているので、そのまま実業務に取り入れてエラーに強いシステムを構築しましょう!
はじめに:フォーム送信時に添付ファイル取得で失敗する原因とは?
Googleフォームの回答送信をトリガーにしてGASを実行する際、添付ファイル項目の処理にはいくつか落とし穴が存在します。主な失敗の原因は以下の通りです。
添付ファイル未送信時の挙動の不安定さ
フォームでファイルアップロードが「任意」に設定されている場合、ユーザーがファイルを添付せずに送信すると、フォームの応答値(getResponse())には空文字列("")や配列・オブジェクトなど、想定と異なる型のデータが入ることがあります。
DriveApp.getFileById() による例外発生
GASでファイルオブジェクトを取得する際によく使われる DriveApp.getFileById(id) は、引数に無効な文字列や空のIDが渡されるとスクリプト実行エラー(Exception)を投げて処理を即座に停止します。
Error: Exception: No item with the given ID could be found, or you do not have permission to access it.
この例外処理(エラーハンドリング)が行われていないと、ファイルを添付しなかっただけで「メールが送信されない」「後続のデータベース更新が行われない」といった深刻な業務トラブルに繋がります。
本記事で解決できること
本記事では、以下のテクニックを組み込んだ「エラーに強いコード設計」を紹介します。
- 強制的な型変換とトリム処理による入力値の正規化
try...catch構文と正規表現によるIDチェックを用いたフォールバック処理- 一時的な取得遅延を回避するデバッグ対策(待機処理)
2. 実践!エラーに強いGoogleフォーム連携GASの全体コード
まずは、エラー対策をすべて盛り込んだ完全なサンプルコードをご覧ください。このスクリプトは、フォームの回答を受け取り、ファイル添付の有無を安全に判定したうえでメール通知を送信します。
/**
* ====================================================================
* Utility Module for GAS / Civic Tech Akita
* Copyright (c) Civic Tech Akita (https://akita-csmedia.com/)
*
* 本モジュールは Civic Tech Akita の留保知的財産権(自社資産)です。
* ライセンス・実装に関するお問い合わせ: https://akita-csmedia.com/?page_id=1476
* ====================================================================
*/
/**
* フォーム送信時に実行されるメイン関数
*/
function onFormSubmit(e) {
var formResponses = e.response.getItemResponses();
let sendEmailConfirmation = '';
if (formResponses[0]) {
sendEmailConfirmation = formResponses[0].getResponse();
}
// 1番目の質問の回答が「はい」の場合のみメール送信処理を実行
if (sendEmailConfirmation === 'はい') {
sendEmailContent(e);
} else {
Logger.log('メール送信の条件を満たしていません。1番目の質問の回答: ' + sendEmailConfirmation);
}
}
/**
* メール送信およびファイル取得処理を行う関数
*/
function sendEmailContent(e) {
var recipientEmail = 'app-demo@akita-csmedia.com';
var appUrl = 'https://kairan-demo02.glide.page';
var formResponses = e.response.getItemResponses();
var title = '';
var bodyContent = '';
var rawUploadedFileId = '';
if (formResponses[1]) {
title = formResponses[1].getResponse();
}
if (formResponses[2]) {
bodyContent = formResponses[2].getResponse();
}
if (formResponses[3]) {
// ★★★ 対策①: rawUploadedFileIdの取得と強制的な文字列化・トリム ★★★
var responseValue = formResponses[3].getResponse();
if (typeof responseValue === 'string') {
rawUploadedFileId = responseValue.trim();
} else if (responseValue !== null && typeof responseValue !== 'undefined') {
Logger.log('Warning: rawUploadedFileId is not a string, type: ' + typeof responseValue);
rawUploadedFileId = String(responseValue).trim(); // 強制的に文字列化
}
}
var attachmentLink = '';
Logger.log('Attempting to get attachment link for ID: ' + rawUploadedFileId);
// ★★★ 対策③: 遅延処理による非同期タイミング問題の切り分け ★★★
Utilities.sleep(100); // 100ミリ秒 (0.1秒) 待機
// ファイルIDが存在する場合のみ処理を試行
if (rawUploadedFileId && typeof rawUploadedFileId === 'string' && rawUploadedFileId.length > 0) {
try {
// ★★★ 対策②: try...catchによるDriveAPI呼び出し ★★★
var file = DriveApp.getFileById(rawUploadedFileId);
if (file) {
Logger.log('File object successfully obtained for ID: ' + rawUploadedFileId);
attachmentLink = file.getUrl();
} else {
Logger.log('File object is null/undefined after getFileById for ID: ' + rawUploadedFileId);
}
} catch (error) {
Logger.log('Error getting file or URL using DriveApp (catch block): ' + error.message);
Logger.log('rawUploadedFileId that caused error in catch: ' + rawUploadedFileId);
// DriveAppでエラーが発生した場合、正規表現でIDを検証して直リンクを直接生成(フォールバック)
const validFileIdPattern = /^[a-zA-Z0-9_-]{28,40}$/;
if (validFileIdPattern.test(rawUploadedFileId)) {
attachmentLink = 'https://drive.google.com/file/d/' + rawUploadedFileId + '/view?usp=sharing';
Logger.log('Fallback: Generated URL directly from ID: ' + attachmentLink);
} else {
Logger.log('Fallback: rawUploadedFileId does not match valid file ID pattern: ' + rawUploadedFileId);
}
}
} else {
Logger.log('rawUploadedFileId is empty or not a string. No attachment link generated.');
}
var attachmentText = attachmentLink ? attachmentLink : '添付ファイルなし';
Logger.log('Final attachmentLink: ' + attachmentLink);
Logger.log('Final attachmentText: ' + attachmentText);
// メール本文の組み立て
var subject = '【サンプルフォーム】からのお知らせ';
var messageBody = '保護者の皆様\n\n' +
'いつもお世話になっております。【サンプルフォーム】です。\n' +
'新しいお知らせがございますので、ご確認ください。\n\n' +
'----------------------------------------\n' +
'【タイトル】 ' + title + '\n\n' +
'【本文】\n' + bodyContent + '\n\n' +
'【添付ファイルのURL】 ' + attachmentText + '\n' +
'----------------------------------------\n\n' +
'以下のアプリからも内容をご確認いただけます。\n' +
'アプリURL: ' + appUrl + '\n\n' +
'ご不明な点がございましたら、お気軽にお問い合わせください。\n' +
'よろしくお願いいたします。\n\n' +
'--- 【サンプルフォーム】 ---';
// 送信処理の例外保護
try {
MailApp.sendEmail(recipientEmail, subject, messageBody);
Logger.log('メールが正常に送信されました。宛先: ' + recipientEmail + ', 件名: ' + subject);
} catch (error) {
Logger.log('メール送信中にエラーが発生しました: ' + error.message);
}
}
3. ここがポイント!エラーを防ぐ3つの実装テクニック
上記のコードには、Googleフォーム連携で発生しやすいエラーを未然に防ぐ3つの重要テクニックが盛り込まれています。
① 応答値の型チェックとトリム処理(typeof & trim())
フォームから渡される回答データは、一見文字列のように見えても配列や異なる型として扱われることがあります。また、文字列の前後に意図しないスペースが入っていると、正しいファイルIDとして認識されません。
そこで、取得時に以下の安全策を講じています。
var responseValue = formResponses[3].getResponse();
if (typeof responseValue === 'string') {
rawUploadedFileId = responseValue.trim();
} else if (responseValue !== null && typeof responseValue !== 'undefined') {
// 文字列でない場合はログを残しつつ強制的に文字列化
Logger.log('Warning: rawUploadedFileId is not a string, type: ' + typeof responseValue);
rawUploadedFileId = String(responseValue).trim();
}
typeofによる型検証: データが文字列型(string)であるかをまず確認します。String()による強制変換: 万が一オブジェクトや数値などで渡された場合でもString()で文字列に変換し、エラーの発生を阻止します。trim()による空白除去: 前後の余分なスペースを削除し、IDの正確性を向上させます。
② try...catch による安全なファイル取得とフォールバック処理
DriveApp.getFileById() は、IDが無効な場合に例外エラーを出してプログラムを停止させます。これを防ぐために try...catch 構文で囲み、エラーが発生した際のバックアップ処理(フォールバック)を実装します。
try {
var file = DriveApp.getFileById(rawUploadedFileId);
if (file) {
attachmentLink = file.getUrl();
}
} catch (error) {
Logger.log('Error getting file or URL using DriveApp: ' + error.message);
// DriveAppで取得失敗時、正規表現でIDを検証して手動で共有URLを作成
const validFileIdPattern = /^[a-zA-Z0-9_-]{28,40}$/;
if (validFileIdPattern.test(rawUploadedFileId)) {
attachmentLink = 'https://drive.google.com/file/d/' + rawUploadedFileId + '/view?usp=sharing';
Logger.log('Fallback: Generated URL directly from ID: ' + attachmentLink);
}
}
この処理のポイントは以下の2点です。
- スクリプトの停止を防止:
DriveApp.getFileById()が失敗してもcatchブロックに入り、処理を最後まで継続させます。 - URLの手動生成: ファイルIDの形式(28〜40桁の英数字・記号)を正規表現(
validFileIdPattern)で判定し、条件を満たしていればGoogleドライブの標準的な共有URL構造([https://drive.google.com/file/d/](https://drive.google.com/file/d/)[ID]/view)を直接組み立てます。
これにより、権限エラー等で DriveApp がファイル情報を取得できなくても、メール本文には有効なファイルリンクを記載することが可能になります。
③ 非同期タイミング問題へのデバッグ対策(Utilities.sleep)
フォーム送信直後は、Googleドライブ側でファイル処理や権限の割り当てが完了しておらず、GASからのアクセスが一瞬遅れるケースがごく稀に発生します。
// 一時的な取得遅延を切り分けるための短時間の待機
Utilities.sleep(100); // 100ミリ秒 (0.1秒) 待機
処理の途中に Utilities.sleep(100) を挟むことで、実行タイミングのずれによる失敗を抑止し、デバッグ時の動作の安定化を図っています。
4. 放課後児童クラブ・地域での活用例:Glideアプリとの連携
今回ご紹介した安全なフォーム処理は、地域DXや教育現場における情報発信の自動化に非常に有効です。
例えば、「放課後児童クラブ」や「町内会」の運営において、次のような仕組みを構築できます。
[管理者 / 職員]
│
▼ Googleフォームからお知らせ&おたより(PDF・画像)を投稿
[Googleフォーム]
│
▼ GAS(安全な添付ファイル処理&メール自動送信)
├─► 保護者宛へ一斉メール通知(添付リンク付き)
│
▼ データをスプレッドシートに蓄積
[Googleスプレッドシート]
│
▼ リアルタイム同期
[保護者向けGlideアプリ] (例: https://kairan-demo02.glide.page)
地域DXにおけるメリット
- メール送信失敗のリスクゼロ化: ファイルが添付されていなくても、保護者への緊急連絡メールが止まらず確実に届きます。
- アプリとメールの二重フォロー: メール通知を受け取った保護者は、メール内のリンクから直接ファイルを確認できるほか、後からノーコードアプリ(Glide)を開いて過去のお知らせ一覧をまとめて閲覧できます。
- 現場の運用負担を軽減: 職員はGoogleフォームに一度入力するだけで、メール配信・アプリ更新・ファイル共有のすべてが完結します。
5. まとめ
堅牢なGASコードで運用の停滞を防ごう!
GoogleフォームとGASを使った自動化システムは大変便利ですが、エラーハンドリングが不十分だと「ファイルが添付されていないだけで全体が止まる」といった運用上のトラブルが発生してしまいます。
今回解説した3つのポイントを意識してコードを設計しましょう。
| 対策ポイント | 実装内容 | 効果 |
|---|---|---|
| ① 型チェック & トリム | typeof / String() / trim() | 未定義値や空文字によるエラーを回避 |
| ② 例外処理 & フォールバック | try...catch + 正規表現URL生成 | APIエラー時も処理を止めずリンクを補完 |
| ③ タイミング調整 | Utilities.sleep(100) | アクセス遅延による一時的な不具合を防止 |
現場で長く使われるシステムを作るためには、「予期せぬ入力があっても止めない」堅牢な設計が不可欠です。ぜひご自身のプロジェクトでも活用してみてください!
💡 この機能が最初から入った完成品サービスをお探しの方へ
「設定作業をプロに任せたい」「現場ですぐ使えるアプリとして導入したい」方向けに、今回ご紹介した機能が標準搭載された完成品ソリューションを提供しています。
🏢 インフラ構築や組織全体のDXからご相談されたい方へ
Google Workspaceの導入、独自ドメインの整備からアプリ・LINE連携まで、予算最小限で進める全体設計は「地域DX完全ロードマップ」にて詳しく公開しています。
