GASのHtmlServiceで
入力フォーム画面を作る方法
GASだけで自社専用の入力画面を作れます。HtmlService(GASからHTML画面を返す仕組み)とgoogle.script.runを使い、画面からスプレッドシートへ保存するまでを動くコードで解説します。
Table of Contents
HtmlServiceでできること・向いている場面
HtmlServiceは、GASからHTMLの画面を返すための仕組みです。サーバーを借りなくても、Googleアカウントだけで自社専用のWeb画面を公開できます。 「Googleフォームでは足りないが、システムを作るほどではない」場面にちょうど当てはまります。
入力途中で計算したい
単価×数量の自動計算や、在庫数のリアルタイム表示など、フォームでは実現できない挙動を作れる
既存データを表示したい
スプレッドシートの現在の値を画面に出し、その場で確認・修正してもらえる
見た目を業務に合わせたい
自社のロゴや配色、項目の並びを自由に設計でき、現場の入力ミスを減らせる
スマホから使わせたい
viewportを設定すればスマホでも崩れず、現場からの報告入力画面として使える
doGetでHTML画面を返す最小構成
Webアプリとして公開したURLにブラウザでアクセスすると、GASのdoGet関数が呼ばれます。ここでHTMLを返せば画面が表示されます。 まずはスクリプトエディタで「ファイル」→「HTML」からindex.htmlを作り、次のコードを書きます。
// コード.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("index")
.setTitle("備品申請フォーム")
.addMetaTag("viewport", "width=device-width, initial-scale=1");
}createHtmlOutputFromFileは指定したHTMLファイルをそのまま返します。addMetaTag("viewport", ...)はスマホ表示のために必須です。HTMLファイル内に直接書いたmetaタグは無視されるため、この書き方で指定します。
画面(HTML)側を作る
画面は普通のHTMLで書けます。ここでは備品申請フォームを例にします。 各入力欄のname属性は、後でサーバー側が受け取るキーになるため必ず付けます。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: sans-serif; max-width: 480px; margin: 40px auto; }
label { display: block; margin-top: 16px; font-size: 14px; }
input, select { width: 100%; padding: 8px; box-sizing: border-box; }
button { margin-top: 24px; padding: 10px 20px; }
</style>
</head>
<body>
<h1>備品申請</h1>
<form id="requestForm">
<label>申請者名
<input type="text" name="applicant" required>
</label>
<label>品名
<input type="text" name="item" required>
</label>
<label>数量
<input type="number" name="quantity" value="1" min="1" required>
</label>
<button type="submit">送信する</button>
</form>
<p id="message"></p>
</body>
</html><base target="_top">は忘れずに入れてください。GASの画面はiframe(ページ内に埋め込まれた別ページ)の中で動くため、これがないとリンクが小さな枠の中で開いてしまいます。
google.script.runでサーバー関数を呼ぶ
画面側からGASの関数を呼ぶにはgoogle.script.runを使います。非同期で動くため戻り値はwithSuccessHandlerのコールバックで受け取り、失敗はwithFailureHandlerで拾います。
<!-- index.html の </body> の直前に追加 -->
<script>
document.getElementById("requestForm").addEventListener("submit", function (e) {
e.preventDefault();
var button = e.target.querySelector("button");
button.disabled = true;
setMessage("送信中...");
google.script.run
.withSuccessHandler(function (result) {
setMessage(result.message);
e.target.reset();
button.disabled = false;
})
.withFailureHandler(function (error) {
setMessage("エラー: " + error.message);
button.disabled = false;
})
.saveRequest(e.target); // form要素をそのまま渡せる
});
function setMessage(text) {
document.getElementById("message").textContent = text;
}
</script>ポイントは、form要素をそのまま引数に渡している点です。GASが各inputのnameをキーにしたオブジェクトへ自動変換してくれるため、項目が増えてもコードを直す必要がありません。 送信中はボタンをdisabledにして、二重送信を防いでいます。
受け取った入力値をスプレッドシートに保存する
サーバー側では、受け取ったオブジェクトを検証してからappendRowで1行追記します。HTMLのrequired属性はブラウザ側の簡易チェックにすぎないため、サーバー側でも必ず検証します。
// コード.gs
function saveRequest(formObject) {
const sheet = SpreadsheetApp.getActive().getSheetByName("申請一覧");
// 画面から来た値は必ずサーバー側でも検証する
const applicant = String(formObject.applicant || "").trim();
const item = String(formObject.item || "").trim();
const quantity = Number(formObject.quantity);
if (!applicant || !item) {
throw new Error("申請者名と品名は必須です");
}
if (!(quantity > 0)) {
throw new Error("数量は1以上の数値で入力してください");
}
sheet.appendRow([
new Date(),
applicant,
item,
quantity,
Session.getActiveUser().getEmail(),
"未処理",
]);
return { message: applicant + " さんの申請を受け付けました。" };
}サーバー側でthrow new Error(...)すると、画面側のwithFailureHandlerが呼ばれ、そのメッセージがerror.messageに入ります。入力エラーの伝え方としてそのまま使えます。
テンプレートで既存データを画面に埋め込む
プルダウンの選択肢をスプレッドシートで管理したい場合は、createTemplateFromFileを使います。HTMLを返す前にサーバー側の値を差し込めるため、画面表示後に読み込む方式より表示が速くなります。
// コード.gs
function doGet() {
const template = HtmlService.createTemplateFromFile("index");
template.categories = getCategories(); // テンプレートに値を渡す
return template.evaluate().setTitle("備品申請フォーム");
}
function getCategories() {
return SpreadsheetApp.getActive()
.getSheetByName("カテゴリ")
.getRange("A2:A")
.getValues()
.flat()
.filter(String);
}HTML側ではスクリプトレット(テンプレート内でGASのコードを書く記法)で値を展開します。<? ?>は処理だけ、<?= ?>は値をエスケープして出力します。
<!-- index.html -->
<label>カテゴリ
<select name="category">
<? categories.forEach(function (name) { ?>
<option value="<?= name ?>"><?= name ?></option>
<? }); ?>
</select>
</label>CSS・JSを別ファイルに分けるincludeパターン
GASのHTMLファイルは.cssや.jsを直接置けません。そこでstyle.htmlやscript.htmlとしてHTMLファイルを分け、次のinclude関数で読み込むのが定番です。1ファイルが長くなりすぎるのを防げます。
// コード.gs
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}呼び出し側は<?!= ?>(エスケープせずにそのまま出力する記法)を使います。この方法はcreateTemplateFromFileで読み込んだときだけ動く点に注意してください。
<!-- index.html -->
<head>
<base target="_top">
<?!= include("style"); ?>
</head>
<body>
<!-- 画面の中身 -->
<?!= include("script"); ?>
</body>公開設定と実務での注意点
1. 「実行するユーザー」の選び方で権限が変わる
デプロイ時に「自分」を選ぶと、アクセスした人にシートの閲覧権限がなくても書き込めます。 社内の誰でも申請できる画面にするならこちらです。一方「アクセスしているユーザー」を選ぶと、その人自身の権限で動くため、シートの共有設定が必要になります。
2. コードを直したらデプロイを更新する
公開URLは保存した時点のバージョンを表示します。修正が反映されないときは「デプロイを管理」から新しいバージョンに更新してください。 動作確認だけならエディタの「テストデプロイ」を使うと、公開URLに影響を与えずに試せます。
3. 同時アクセスの書き込み競合に備える
複数人が同時に送信すると、行の取り合いでデータが壊れることがあります。LockServiceで排他制御をかけるか、追記だけで済む設計にしておくと安全です。
4. iframeの制約を先に確認する
GASの画面はiframe内で動くため、カメラ起動など一部のブラウザ機能が制限されます。 実装したい機能がある場合は、作り込む前に小さな検証コードで動くかどうかを確かめておくと手戻りを防げます。
まとめ
HtmlServiceを使えば、doGetでHTMLを返し、google.script.runでサーバー関数を呼ぶだけで、自社専用の入力画面が作れます。 テンプレートで既存データを埋め込み、includeでファイルを分割すれば、項目が増えても管理しやすい構成を保てます。 サーバー側の入力検証・デプロイ更新・同時アクセス対策の3点を押さえておけば、日々の業務で使える申請・報告画面として十分に運用できます。
よくある質問
アンケートのように「回答を集めるだけ」ならGoogleフォームで十分です。既存データを画面に表示したい、入力途中で在庫や単価を計算したい、ログインユーザーによって表示を変えたい、といった要件が出てきたらHtmlServiceの出番です。HtmlServiceは自分でHTMLとJavaScriptを書ける分、画面の自由度が高くなります。
google.script.run(画面側からサーバー側のGAS関数を呼ぶ仕組み)は非同期で動くため、return では値を受け取れません。withSuccessHandler(コールバック関数) を先に指定してから関数を呼び、コールバックの引数として結果を受け取ります。エラー時は withFailureHandler が呼ばれます。
あります。渡せるのは文字列・数値・真偽値・日付・配列・単純なオブジェクトまでです。DOM要素や関数はそのまま渡せません。フォーム全体を渡したい場合は、form要素をそのまま引数にすると各inputのname属性をキーにしたオブジェクトへ自動変換されます。
エディタの「デプロイ」→「新しいデプロイ」で種類に「ウェブアプリ」を選び、実行ユーザー(自分/アクセスしているユーザー)とアクセスできるユーザーを設定するとURLが発行されます。コードを直した後は「デプロイを管理」からバージョンを更新しないと、公開URLの内容は古いままになります。
読み込めます。HTML内のlinkタグやscriptタグでCDNのURLを指定すれば利用できます。ただしGASのWebアプリはiframe(ページ内に埋め込まれた別ページ)の中で動くため、カメラやクリップボードなど一部のブラウザAPIは制限されます。動作させたい機能がiframe内で使えるか、事前に確認しておくと安全です。