既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する
人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。
約17.6万トークンの節約 (API料金換算で約260円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約17.6万トークン(API料金換算で約260円)・98%のトークンを節約できます。
- カテゴリ
- Google Workspace
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約18万トークン
- この巻物使用時
- 約4,200トークン
- 節約量
- 約17.6万トークン (約260円)
- 更新日
- 2026-10-01
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/google-gas-read-back/raw を読み込んで、この指示書どおりに実装して"
中身
既存の Google フォームへ GAS から自動送信し、回答シートで read-back 検証する
「人が手入力している Google フォーム」を業務システムから自動送信して、手作業を消す。 フォームを作り直さないので、受け取る側(フォーム回答を見ている部署)の運用は一切変わらないのが利点。
想定: 社内の別部署が運用しているフォーム(発注・予約・申請など)に、自分たちのアプリから自動で投げたい。
1. フォームの entry ID を取り出す
フォームが公開(リンクを知っている全員が回答可)なら、認証なしで HTML を取れる。
curl -sL -o form.html 'https://docs.google.com/forms/d/e/<FORM_ID>/viewform'
FB_PUBLIC_LOAD_DATA_ に全質問の定義が入っている。これを解析する:
import fs from 'fs';
const h = fs.readFileSync('form.html', 'utf8');
const m = h.match(/FB_PUBLIC_LOAD_DATA_ = (\[[\s\S]*?\]);/);
const d = JSON.parse(m[1]);
console.log('collectEmail:', JSON.stringify(d[1] && d[1][10]));
for (const it of (d[1][1] || [])) {
const title = it[1], type = it[3], sub = it[4] || [];
for (const s of sub) {
const id = s[0], req = s[2], opts = (s[1] || []).map(o => o[0]);
console.log(`entry.${id}\ttype=${type}\treq=${req}\t${JSON.stringify(title)}\t${JSON.stringify(opts)}`);
}
}
type は 0=短答 / 1=段落 / 2=ラジオ / 3=プルダウン / 9=日付。
req=1 が必須項目。選択肢は文字列を1文字も変えずに送ること(かぎ括弧や全角記号もそのまま)。
落とし穴: メールアドレスは entry.* ではない
「メールアドレス」の質問が一覧に出てこないのに、フォーム画面には入力欄がある場合、
それは質問項目ではなくメール収集機能。パラメータ名は固定で emailAddress を使う。
- 「回答者が入力」モードなら、任意のアドレスを渡せる
- 「自動収集(確認済み)」モードだと、送信したアカウントのアドレスになり上書きできない。 この場合、本人以外のアドレスを入れる運用は自動化できないので、設計を変える(フォーム側に質問項目を足してもらう等)
日付の形式
entry.<id>=YYYY-MM-DD で通る。古い資料にある entry.<id>_year / _month / _day の分割形式は不要。
ただし実際に1回テスト送信して回答シートで確認すること。フォームの設定で変わり得る。
2. GAS から送信する
function submitForm_(values) {
const res = UrlFetchApp.fetch(
'https://docs.google.com/forms/d/e/<FORM_ID>/formResponse',
{ method: 'post', payload: values, muteHttpExceptions: true }
);
return res.getResponseCode(); // 200 が成功
}
payload はフラットなオブジェクト({'entry.123': '値', emailAddress: '...'})でよい。
3. 回答シートの read-back 検証 — ここに罠が2つある
「送ったつもり」で終わらせず、回答シートに行が入ったことを確認する。ただし次の2点で確実に失敗する。
罠1: ファイル名とタブ名は別物
回答先スプレッドシートのファイル名が「○○申込フォーム(回答)」でも、 タブ名が同じとは限らない。しかも1つのブックに別業務のタブが同居していることがある (目次・質問シート・集計など)。タブ名で探すと失敗するか、最悪、別業務のタブを読み書きする。
→ gid で特定する。
function responseSheet_(ssId, gid) {
const sheets = SpreadsheetApp.openById(ssId).getSheets();
for (const s of sheets) if (s.getSheetId() === gid) return s;
// 見つからない時は「無い」で終わらせず、全タブ名と gid を添えて失敗させる
const list = sheets.map(s => s.getName() + '(gid=' + s.getSheetId() + ')').join(', ');
throw new Error('回答タブが見つかりません。候補: ' + list);
}
gid はブラウザで該当タブを開いた時の URL 末尾 #gid=... から取る。設定値として外出しし、既定値を持たせる。
さらに、特定したタブのヘッダー行がフォームの質問と噛み合うかを検証してから使う。 gid が変わっていた場合に別タブを書き換える事故を防げる。
罠2: getLastRow() が実データの最終行より大きい値を返す
回答シートの末尾にテンプレート由来の数式が入った行が並んでいると、
getLastRow() も getDataRange() も数式がある行までを返す。
実測例: 実際の回答は32行目までなのに getLastRow() が 131 を返した。
「送信前の最終行より後ろを探す」実装にしていたため、**探索ループが一度も回らず、常に「未反映」**になった。
→ タイムスタンプ列(通常1列目)の、値が入っている最後の行で判定する。
function lastDataRow_(sheet) {
const n = sheet.getLastRow();
if (n < 2) return 1;
const col = sheet.getRange(1, 1, n, 1).getValues(); // 1列だけ読む
for (let i = col.length - 1; i >= 1; i--) {
if (String(col[i][0] || '').trim() !== '') return i + 1;
}
return 1;
}
4. 反映は遅い。同期で待つ設計にしない
フォーム POST からシートに行が現れるまで、実測で約52秒かかった。保証された値ではなく、 もっと遅いこともある。
ここで「30秒待って見つからなければ失敗」という同期実装にすると、 成功した送信が毎回失敗として記録される。待ち時間を延ばしても、反映時間が保証されていない以上は再発する。
→ POST が 200 なら「送信済み・未確認」として状態を保存し、戻り値は成功にする。 確認は後追いのトリガ(10分おき等)で行い、見つかったら「確認済み」に更新する。 一定回数(例: 6回=約1時間)確認できなければ、そこで初めて失敗として通知する。
状態は最低3つに分ける。ここを2値にすると事故る。
| 状態 | 意味 | 再送してよいか |
|---|---|---|
| 未送信 | POST していない(設定で止めた等) | してよい |
| 送信済み・未確認 | POST は 200。シート未確認 | 絶対にしない |
| 確認済み | シートに行を確認 | しない |
「未送信で止まった」と「送ったが結果が分からない」を区別できないと、 前者が永久に再開できない(手動復旧が必要)か、後者で二重送信する。 外部への申請や発注では二重送信が実害になるので、迷ったら送らない側に倒す。
5. 本番を汚さないための3段モード
いきなり本番送信を有効にしない。設定値ひとつで3段に分ける。
| モード | 挙動 |
|---|---|
off(既定) | 外部送信を一切しない |
test | 実フォームへ送るが、識別子と注記を付ける |
live | 本番 |
off は引数で上書きできない絶対の停止条件にする。 関数の opts.mode で off を破れる実装にすると、
「止めたはずなのに送られた」が起きる。上書きを許すのは test ↔ live の間だけ。
test で守ること:
- 案件名などの識別子に
__TEST__<一意なUUID>を付け、受け取る側が一目で分かるようにする - 実在の第三者のメールアドレスを入れない(専用のテスト用アドレスか、実行アカウント)
- 備考欄の先頭に「これはテスト送信です。対応不要です。」を入れる
- チャット通知など他システムへの書き込みは一切しない(テストで関係者に通知が飛ぶのが一番迷惑)
live への切替には確認トークンを必須にする:
function setMode(mode, opts) {
if (['off', 'test', 'live'].indexOf(mode) < 0) throw new Error('不正なモード: ' + mode);
if (mode === 'live' && (!opts || opts.confirm !== 'ENABLE-LIVE-SENDING')) {
throw new Error('live にするには confirm が必要です');
}
// 書き込み後に必ず読み戻して確認する
}
6. 検証の順序
- モックのテストを書く(送信経路が
offで一切発火しないことを含める) testで1件だけ送る- 回答シートを直接読み、送った値と入った値を1項目ずつ突き合わせる。特に日付
- 同じ対象をもう一度送って、行が増えないこと(二重送信防止)を確認する
- 複数パターン(地域別の分岐など計算ロジックが変わるもの)で2〜3件試す
liveに切り替える
モックのテストが何百件通っても、実データで初めて出るバグがある。
実際この手順で、テスト762件が全通過した状態から「タブ名が違う」「getLastRow() が実データを超える」
の2件が実送信で初めて露見した。どちらもモックでは再現しない種類のもの。
7. 運用前に確認すること
- 回答先スプレッドシートの所有者を確認する。個人アカウント所有のまま共有で使われていることがあり、 その場合は共有が切れた瞬間に自動化も手作業も同時に止まる。組織アカウントへの移管を先に提案する
- 送信が失敗した時に誰にどう通知するかを決める。通知は重複抑止を入れる (後追い確認のたびに失敗通知が飛ぶと、通知が意味を失う)
- フォームの質問が変更されると entry ID や選択肢文字列が変わる。定期的に entry ID を取り直して突き合わせる か、送信前にヘッダー照合で検知する
よくある質問
+「既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する」とは何ですか?
人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約18万トークンかかりますが、この巻物を使えば約4,200トークンで済みます。差し引き約17.6万トークン(API料金換算で約260円)・98%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
GAS完全自動化テンプレ — Driveコマンドキュー方式
Google Apps Script の「毎回エディタで▶実行」を根絶。Drive 経由のコマンドキューで、初回1クリック以降は AI がすべての GAS 関数をリモート実行できるようになるテンプレート指示書。
人間の手入力台帳を壊さずに自動更新する — GAS Web App upsert 設計
各PC/各拠点の点検結果を、人間が手運用しているスプレッドシート台帳へ自動反映する。手入力列とコメントを絶対に壊さない突合設計、タブ/列の解決、並行POST対策、配布シークレットの落とし穴まで。
数式まみれの業務スプレッドシートを、Webアプリから壊さずに編集させる型
ArrayFormula と per-row 数式が混在する台帳を、セル単位 allowlist・dry-run 既定・適用前バックアップ・触っていないセルの数式不変検査で安全に書き換える設計手順。列ごとの数式復元と、テストが緑のまま壊れる典型例つき。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア