本サイトはアフィリエイト広告(A8.net/もしもアフィリエイト/アクセストレード等)を利用しています
GASでGoogleフォームのアップロードファイルをDriveに自動整理
📂 ドライブ

GASでGoogleフォームのアップロードファイルをDriveに自動整理

📅 ⏱ 読了 約9分 ✍️ 凛

💡 本記事には広告(アフィリエイトリンク)が含まれる場合があります。

凛です。

先日、子どもの習い事の役員をやることになって、保護者のみなさんから提出書類をGoogleフォームで集めることになりました。フォームに「ファイルのアップロード」項目を付けたら、写真や同意書のPDFがどんどん集まってきて、正直ここまでは順調だったんです。

ところが、いざ集まったファイルを見に行ってみると、全員分のファイルが1つのフォルダにぐちゃぐちゃに入っていて、「田中さんの同意書どこ?」「これ誰の写真だっけ?」と探すのに30分もかかってしまいました。誰が何を出したのか、ファイル名を1個ずつ開いて確認するはめに…。

夜勤明けの眠い頭で「これ、GASで自動的に人ごとのフォルダに振り分けられないかな」と思って作ってみたら、あっさり解決しました。今日はその仕組みを、コピペで動く形で丁寧に共有します。

GASでGoogleフォームのアップロードファイルをDriveに自動整理する

Googleフォームの「ファイルのアップロード」は便利なんですが、集まったファイルが全部同じ場所に入ってしまうのが地味に不便です。この記事では、フォームが送信されるたびに、GAS(Google Apps Script)が自動でファイルを「回答者名」や「日付」ごとのフォルダに振り分けてくれる仕組みを作ります。

一度セットしておけば、あとは放置でOK。回答が来るたびに勝手に整理してくれるので、月末にまとめて手作業…という苦行から解放されます。

なぜファイルが散らかるのか(仕組みの理解)

まず「なぜ散らかるのか」を理解しておくと、あとのコードがすっと入ってきます。ここを飛ばして手を動かすと、あとで「あれ、ファイルが見つからない」と混乱しがちなので、少しだけお付き合いください。

フォームのファイルは自動で1つのフォルダに集まる

Googleフォームに「ファイルのアップロード」項目を作ると、回答者がアップしたファイルは、フォーム所有者(あなた)のGoogleドライブの中に自動で作られる専用フォルダに保存されます。フォルダ名はだいたい「(フォーム名)(ファイル回答)」のような名前です。

つまりファイル自体はちゃんと自分のドライブに来ているんですが、全員分・全項目分が同じ場所に平積みされるので、数が増えると探せなくなるわけです。

スプレッドシートには「ファイルのURL」が記録される

フォームの回答をスプレッドシートに出力していると、ファイルアップロード項目の列には、ファイルそのものではなく**そのファイルのURL(またはID)**が文字列として入ります。

ここが今回のキモです。「回答が来た → そのURLからファイルを特定できる → GASでそのファイルを好きなフォルダに動かせる」という流れになります。散らかる原因を、逆に自動整理のきっかけとして使うイメージですね。

この記事で作るゴール

最終的にこんな動きを目指します。

  • フォームが送信されたら自動で起動する
  • 回答者の名前でサブフォルダを作る(無ければ新規作成)
  • アップロードされたファイルをそのフォルダに移動する
  • ファイル名を「日付_氏名_元のファイル名」にリネームする
  • メールアドレスがあれば、回答者に受付確認メールを送る

事前準備:フォルダIDを調べる

コードを書く前に、整理先の「親フォルダ」を1つ用意して、そのIDを控えます。IDはGASがフォルダを特定するための住所のようなものです。

整理先フォルダを作ってIDを取得する

Googleドライブで、整理先にしたいフォルダを新規作成します(例:「フォーム提出書類」)。作ったフォルダを開くと、ブラウザのアドレスバーが次のようになっています。

https://drive.google.com/drive/folders/1AbCdEfGhIjKlMnOpQrStUvWxYz1234567

このfolders/より後ろの文字列(1AbCdEfGhIjKlMnOpQrStUvWxYz1234567の部分)がフォルダIDです。これをメモしておきます。あとでコードのPARENT_FOLDER_IDに貼り付けます。

スプレッドシートと列の位置を確認する

フォームの「回答」タブから、回答をスプレッドシートにリンクしておきます。そのシートを開いて、次の3つの列がそれぞれ何列目かを数えておきます。

使う情報メモ
タイムスタンプA列自動で入る送信日時
氏名の項目B列フォームで名前を聞く質問
ファイルアップロード項目C列ファイルのURLが入る

今回は列番号を直接指定せず、質問文(見出し)で値を取り出す方法を使うので厳密な列位置は必須ではありませんが、構造を把握しておくと安心です。

基本のコード:ファイルを人ごとのフォルダに移動する

いよいよ本題のコードです。まずは一番シンプルな「回答者名のフォルダに移動する」版から作ります。

コード全体

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

// 整理先の親フォルダID(自分の環境の値に書き換えてください)
const PARENT_FOLDER_ID = 'ここに親フォルダのIDを貼る';

// フォームの質問文(スプレッドシートの見出しと完全に一致させる)
const Q_NAME = 'お名前';          // 氏名を聞く質問のタイトル
const Q_FILE = '書類のアップロード'; // ファイルアップロード項目のタイトル

// フォーム送信時に自動で呼ばれる関数
function onFormSubmit(e) {
  // e.namedValues は { 質問文: [回答値] } の形で回答が入っている
  const named = e.namedValues;                       // 回答の連想配列を取得
  const answerName = (named[Q_NAME] || [''])[0];     // 氏名の回答を取り出す(無ければ空文字)
  const fileCell = (named[Q_FILE] || [''])[0];       // ファイル項目のセル文字列を取り出す

  // 氏名が空なら「名無し」フォルダにまとめる(エラーで止めないため)
  const safeName = answerName ? answerName.trim() : '名無し';

  // ファイルが1つも無ければ、ここで処理を終える
  if (!fileCell) {
    Logger.log('ファイルが添付されていません: ' + safeName);
    return;
  }

  // 親フォルダを取得する
  const parentFolder = DriveApp.getFolderById(PARENT_FOLDER_ID);

  // 回答者名のサブフォルダを取得(無ければ作成)
  const personFolder = getOrCreateFolder_(parentFolder, safeName);

  // セル文字列から、含まれるファイルのIDをすべて取り出す
  const fileIds = extractFileIds_(fileCell);

  // 今日の日付を yyyyMMdd 形式で作る(ファイル名の頭に付ける)
  const dateStr = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd');

  // 取り出したファイルIDを1つずつ処理する
  fileIds.forEach(function (id) {
    const file = DriveApp.getFileById(id);           // IDからファイルを取得
    file.moveTo(personFolder);                       // 回答者フォルダへ移動(現行API)
    const newName = dateStr + '_' + safeName + '_' + file.getName(); // 新しいファイル名を組み立て
    file.setName(newName);                           // ファイル名をリネーム
  });

  Logger.log(safeName + ' さんのファイルを ' + fileIds.length + ' 件整理しました');
}

// 指定フォルダ内に同名フォルダがあれば返し、無ければ作って返す
function getOrCreateFolder_(parent, folderName) {
  const it = parent.getFoldersByName(folderName);    // 同名フォルダを検索
  if (it.hasNext()) {
    return it.next();                                // 既存フォルダを返す
  }
  return parent.createFolder(folderName);            // 無ければ新規作成して返す
}

// セル内の文字列から、DriveファイルのIDをすべて抜き出して配列で返す
function extractFileIds_(cellText) {
  const ids = [];
  // 「id=xxxx」または「/d/xxxx」形式のURLからIDを取り出す正規表現
  const regex = /(?:id=|\/d\/)([a-zA-Z0-9_-]{20,})/g;
  let m;
  while ((m = regex.exec(cellText)) !== null) {
    ids.push(m[1]);                                  // ()でくくった部分(=ID)を追加
  }
  return ids;
}

コードのポイント解説

e.namedValuesは、フォームの回答を質問文をキーにした連想配列で受け取れる便利なプロパティです。列番号でズレる心配がないので、私はいつもこちらを使っています。値が配列で返ってくる点に注意して[0]で最初の要素を取り出しています。

file.moveTo(folder)が今回の主役です。以前はgetParents()で親を外してaddFileremoveFileする古い書き方がありましたが、今はmoveTo一発で移動できます。シンプルで読みやすいので、こちらを使いましょう。

extractFileIds_で正規表現を使っているのは、ファイルアップロード項目に複数ファイルを許可していると、セルの中にURLが複数入るからです。1つでも複数でも同じロジックで拾えるようにしています。

インストーラブルトリガーの設定(ここ超重要)

コードを貼っただけでは自動実行されません。ここを間違えると「保存したのに動かない!」と数時間ハマるので、正直にお伝えします。

シンプルトリガーでは動きません

onFormSubmitという名前だとシンプルトリガーで動きそうに見えますが、フォーム送信のシンプルトリガーは実質使えない場面が多いです。特に、後述するGmail送信やほかのユーザーのファイル操作といった「承認が必要な処理」はシンプルトリガーでは実行できません。

なので、必ずインストーラブルトリガーを手動で設定します。関数名はonFormSubmitでなくても構いませんが、分かりやすいのでこの名前のままにしています。

トリガーの設定手順

GASエディタの左メニューにある時計マーク(トリガー)から設定します。

  1. スプレッドシートに紐づいたスクリプトエディタを開く
  2. 左メニューの時計アイコン「トリガー」をクリック
  3. 右下「トリガーを追加」を押す
  4. 実行する関数:onFormSubmit
  5. イベントのソース:スプレッドシートから
  6. イベントの種類:フォーム送信時
  7. 保存 → 権限の承認画面が出るので承認する

「スプレッドシートから」を選ぶのがポイントです。スプレッドシート連携を使う今回の構成では、こちらがe.namedValuesをちゃんと渡してくれます。

権限承認で慌てないために

初回の保存時に「このアプリはGoogleで確認されていません」という警告が出ることがあります。これは自分で書いたスクリプトなので、「詳細」→「(プロジェクト名)に移動」から承認して大丈夫です。自分のドライブとフォームを操作するための許可なので、ここは通します。

回答者に受付確認メールを送る

インストーラブルトリガーにしたことで、GmailAppが使えるようになりました。せっかくなので、書類を受け付けたことを回答者に自動返信してあげましょう。

メール送信を追加したコード

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

// メールアドレスを聞いている質問文(無い場合はこの機能を使わない)
const Q_EMAIL = 'メールアドレス';

// onFormSubmit の末尾に、この処理を足すイメージです
function sendReceiptMail_(named, safeName, fileCount) {
  const email = (named[Q_EMAIL] || [''])[0];   // メールアドレスの回答を取り出す

  // メールアドレスが空、または「@」を含まない不正値なら送らない
  if (!email || email.indexOf('@') === -1) {
    Logger.log('メール未送信(アドレス無し): ' + safeName);
    return;
  }

  const subject = '【受付完了】書類を受け取りました';  // 件名
  // 本文を組み立てる(テンプレートリテラルで改行しやすく)
  const body =
    safeName + ' 様\n\n' +
    'このたびは書類のご提出ありがとうございます。\n' +
    '添付ファイル ' + fileCount + ' 件を確かに受け付けました。\n\n' +
    '内容に不備があった場合のみ、あらためてご連絡します。\n\n' +
    '――――――――――\n' +
    '提出フォーム自動返信';

  // メールを送信する(インストーラブルトリガーなのでGmailAppが使える)
  GmailApp.sendEmail(email, subject, body);
  Logger.log('受付メール送信: ' + email);
}

呼び出し方

先ほどのonFormSubmitのファイル整理が終わったあとに、この1行を足すだけです。

// ファイル整理のあと、確認メールを送る
sendReceiptMail_(named, safeName, fileIds.length);

1日の送信上限に注意

GmailApp.sendEmailには1日あたりの送信上限があります(無料のGmailアカウントでは1日100通程度)。少人数のフォームなら気にする必要はありませんが、数百人規模のフォームだと上限に当たることがあるので、その場合は送信を絞るなどの工夫が必要です。

よくある失敗と回避策

私が実際にハマったポイントを、回避策とセットで残しておきます。同じところで悩まなくて済むはずです。

失敗1:保存したのに動かない

一番多いのがこれです。シンプルトリガーのつもりになっていて、インストーラブルトリガーを設定していないパターン。前述の手順で時計アイコンからトリガーを手動追加してください。設定済みなら、フォームにテスト回答を1件送って動作確認しましょう。

失敗2:ファイルが移動できない・IDが取れない

ファイルアップロード項目のセルに入る文字列の形式は、環境やGoogleの仕様変更で微妙に変わることがあります。IDが取れないときは、Logger.log(fileCell)でセルの中身を出力して、実際にどんな文字列が入っているか確認しましょう。今回の正規表現はid=/d/の両パターンに対応していますが、想定外の形式なら正規表現を調整します。

失敗3:複数ファイルの一部しか整理されない

ファイルアップロード項目で「複数ファイルを許可」にしていると、URLが改行やカンマで区切られて複数入ります。1個しか処理されないときは、extractFileIds_が全URLを拾えているか確認してください。今回のコードはwhileループで全部拾う作りなので、この点は対応済みです。

失敗4:フォルダが毎回増殖する

同じ人の回答が来るたびに新しいフォルダが増えてしまう場合は、getOrCreateFolder_が既存フォルダを見つけられていません。氏名の前後に空白が入っていると別名扱いになるので、コードではtrim()で前後の空白を取り除いています。それでも増える場合は、氏名の表記ゆれ(全角スペースなど)を疑いましょう。

応用:日付フォルダとの二段構え

人数が多い、あるいは長期運用する場合は、「日付フォルダ → 回答者フォルダ」の二段構えにすると、後から見返しやすくなります。

月ごとにまとめるコード例

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

// 「親フォルダ → 年月フォルダ → 回答者フォルダ」の順で作る例
function getMonthlyPersonFolder_(parentId, personName) {
  const parent = DriveApp.getFolderById(parentId);              // 親フォルダ
  const ym = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM'); // 例: 2026-08
  const monthFolder = getOrCreateFolder_(parent, ym);          // 年月フォルダを用意
  return getOrCreateFolder_(monthFolder, personName);          // その中に回答者フォルダ
}

getOrCreateFolder_を使い回しているので、追加コードはこれだけです。onFormSubmitの中でgetOrCreateFolder_(parentFolder, safeName)getMonthlyPersonFolder_(PARENT_FOLDER_ID, safeName)に差し替えれば、月ごとに整理される形になります。

用途に応じて「日付ごと」か「人ごと」か、あるいは両方か、柔軟に組み替えられるのがGASのいいところです。

自分でも作れるようになりたい方へ

今回の仕組みは、コピペして自分のフォルダIDと質問文を書き換えるだけで動きます。でも、いざ自分の業務に合わせて少しアレンジしようとすると、「ここを直すとどう変わるんだろう」と手が止まってしまうことも多いはず。

私自身、最初はコピペばかりでしたが、少しずつ「なぜこう書くのか」が分かってくると、身のまわりの面倒な作業をどんどん自動化できるようになって、副業にも本業にも役立っています。看護師の私でも書けているので、大丈夫。基礎からきちんと学べば、こうしたツールを自分の手で作れるようになりますよ。

Dive into Code(未経験からエンジニアを目指すプログラミングスクール)

関連記事

この記事を書いた人:凛

2児2児のママで現役ナース。夜勤明けの細切れ時間を副業GASに投じ、月5〜8万円の副収入を継続中。「看護師でもコードは書ける」を合言葉に、家事育児とプログラミングを両立する等身大の情報を発信しています。

※掲載コードは構文とAPI仕様を確認して載せていますが、お使いの環境に合わせて調整してください。