
「GAS×Googleフォーム」をコピーして利用する
▍何の話?
Google Apps Script(GAS)を使うと、Googleフォームで回答があったときに自動処理を行えます(例:メールやチャットへの転送、カレンダーへの予定追加、データベースへのデータ追加)。
この自動処理については様々な方が書かれているので、他の記事に譲らせていただきます。たとえば次の記事などが分かりやすいです。
GASのスクリプトが付いたフォームについて、1つのフォームを使い続ける場合は問題ありません。
一方、1つのフォームをコピーして利用する場合、単純にコピーするとスクリプトの自動処理がうまくいきません。これはたとえば、イベントアンケートのひな形があって、イベントの開催に合わせてコピーして利用する場合などです。
▍フォームコピーの問題と対処
自動処理がうまくいかない理由は、コピー時にトリガーが削除されるためです(この動きはフォームだけでなく、ドキュメントでもスプレッドシートでも、スクリプトが付いたファイルに共通です)。

トリガーは、イベント(きっかけ)と関数(処理内容)の設定を持ち、フォーム回答時の自動処理に必要となるものです。ですので、コピー後にトリガーを設定し直す必要があります。
▍GASでの実装(考え方)
GASでフォームコピー・トリガー追加を行う場合、少し工夫が求められます。やり方は様々でしょうが、以降はうちの会社の例で紹介します。
準備
まず、フォームとスクリプトを分離します。スクリプトには、次の2点を記述します。
フォーム回答時の自動処理内容
フォームコピーの処理(後述)
ということで、コピー処理もこのスクリプトで行います。なお、ここではまだトリガーは登場しません。

フォームコピーの処理
フォームコピーの処理内容は次のとおりです。
テンプレートのフォームをコピーする
コピーした後に、コピー先のフォームにトリガーを設定する
なお、トリガーの指定先はフォームなのですが、トリガー自体はスクリプト側が持ちます。


コピー数に注意
GASでは、1つのスクリプトで設定できるトリガーは20までという制約があります(次のリンクの「Triggers」)。
なので、紹介した方法は、コピー数が多い場合には向きません。
うちでは1週間に1つ新しいフォームをコピーしているので、普通に使うと20週でパンクしてしまいます。そこで、最新の5程度で回るような運用にして、古いフォームおよびトリガーを削除しています。
ちなみに、フォームとスクリプトが分離している場合、フォームを削除してもトリガーは残ります。20の上限が危ぶまれる際には、削除済みフォームのトリガーも削除ください。

▍GASでの実装(コード)
ScriptAppクラスに次のようなトリガー操作のメソッドがあるので、これらを利用します。
作成:newTrigger
削除:deleteTrigger
一括取得:getProjectTriggers
詳しく知りたい方は公式ドキュメントも参照ください。
フォームコピー&トリガー追加のスクリプト
// フォームコピーの処理
function copyForm() {
// ① コピー元のファイル(フォーム)を指定
const sourceFile = DriveApp.getFileById("コピー元のフォームのファイルID");
// ② コピーし、コピー先のフォームのファイルIDをdestFileに格納
const destFile = sourceFile.makeCopy("コピー先のフォーム名");
// ③ コピー先のフォームを指定
const form = FormApp.openById(destFile.getId());
// ④ コピー先のトリガーを設定
ScriptApp.newTrigger("フォーム回答時の自動処理を記述した関数の名前")
.forForm(form)
.onFormSubmit()
.create()
}
// ⑤ フォーム回答時の自動処理
function respondForm(e) {
// フォーム回答時の自動処理内容;
}
②の補足:
「"コピー先のフォーム名"」を省略した場合、「〜のコピー」というフォーム名になります。
④の補足:
この例の場合、「フォーム回答時の自動処理を記述した関数の名前」は⑤の「respondForm」になります。
この例でのトリガーのイベントは「フォーム送信時」です。他のイベントを指定する場合、次をドキュメントを参照ください。
トリガー削除のスクリプト
削除済みフォームのトリガーを削除するスクリプトです。新たにトリガーをつける前に、この関数を実行しています。
/**
*削除済みフォームのトリガーを削除
*/
function DeleteTrigger() {
// トリガーを一括取得
ScriptApp.getProjectTriggers()
// フォームに絞り込み
.filter(trigger => trigger.getEventType() == ScriptApp.EventType.ON_FORM_SUBMIT)
.forEach(trigger => {
try {
// フォームのソースIDのファイルを取得
DriveApp.getFileById(trigger.getTriggerSourceId());
} catch (e) {
// 取得に失敗(エラーが発生)したらトリガーを削除
ScriptApp.deleteTrigger(trigger);
}
});
}
やっていることはコメントのとおりですが、ここでは filter、forEach とアロー関数を使っているので、JavaScriptに不慣れだと面を食らうかもしれません。公式の deleteTrigger のサンプルは for で書かれているので、必要に応じてご確認ください。
おまけ
フォームコピー&トリガー追加について、うちで実際に使っているスクリプトです(関数名は変えています)。前述のコードとの違いは以下の4点です。
関数化している
フォームを所定のフォルダにコピーしている
フォームのタイトルを変えている
回答URLを短縮形で取得している
/**
*アンケートフォームをコピー
*@param {string} fileId - アンケートフォーマットのファイルID
*@param {string} folderId - フォームのコピー先フォルダID
*@param {string} eventId - イベントID
*@param {string} dateString - 開催日(yyyyMMdd)
*@return {string} フォーム回答用のURL
*/
function xxxCopyForm(fileId, folderId, eventId, dateString) {
// IDをもとに、コピー元のフォームのファイルを指定
const sourceFile = DriveApp.getFileById(fileId);
// IDをもとに、フォームのコピー先フォルダを指定
const destFolder = DriveApp.getFolderById(folderId);
// フォームのファイルを所定のフォルダにコピー
const destFile = sourceFile.makeCopy(`${dateString}_${eventId}`, destFolder);
// コピー先のフォームを指定
const form = FormApp.openById(destFile.getId());
// フォームのタイトルを設定
form.setTitle("ご参加者アンケート");
// フォームにトリガーを追加
ScriptApp.newTrigger("xxxRespondForm")
.forForm(form)
.onFormSubmit()
.create();
// フォームの回答URLを返却
return form.shortenFormUrl(form.getPublishedUrl());
}
▍おわりに
鍵となる注意事項は次の2点ですので、是非抑えておいていただければと。
スクリプト付きのファイルをコピーするとトリガーが削除される
1つのスクリプトのトリガー上限は20
スクリプト付きのフォームをコピーするケースが稀なのか、情報が少なく、うちでは少しハマりました。これから対応する方のご参考になれば嬉しいです。
私たちのIT活用のメモが次のマガジンにあります。よろしければ覗いてみてください!