
![]()
スタッフ間でスプレッドシートを共有したいんだけど…
![]()
プロラインのフォーム送信時に外部システムに信号(データ)を送ることができますので、
Googleスプレッドシート+Apps Scriptで、その信号(データ)を受け取って、指定のシートへ自動で追記できます。

設定方法について案内しますね。
Googleスプレッドシート側の設定
まず、Googleスプレッドシート側で下記の流れで設定してください。
手順1:Googleドライブで新規にスプレッドシートを作成してください。
まず、Googleドライブで新規にスプレッドシートを作成してください。

こちらが記録先になります。シート名は任意で構いません。(ここでは、このマニュアル用に連携テストとします。)

手順2:作成したスプレッドシートのApps Scriptを開きます
作成したスプレッドシートの「拡張機能」→「Apps Script」 をクリックして

Apps Script画面を開きます。

手順3:サンプルコードの内容を貼り付けます
デフォルトでは、以下のように既存のコードが入力されておりますので、こちらを全て消してから

以下に記載されているサンプルコードの内容を貼ってください。
function doGet() {
return ContentService.createTextOutput("OK");
}
function doPost(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var debug = ss.getSheetByName('_debug') || ss.insertSheet('_debug');
try {
var p = (e && e.parameter) ? e.parameter : {};
var raw = (e && e.postData && e.postData.contents) ? e.postData.contents : "";
// 固定列(先頭3列)
var sentAt = p.date
? p.date
: (p.unixtime ? fmt_(new Date(Number(p.unixtime) * 1000)) : fmt_(new Date()));
var uid = p.uid || "";
var snsname = p["user_data[snsname]"] || "";
// シート(form_nameベース)
var sheet = getOrCreateSheet_(ss, sheetNameFromFormName_(p.form_name || "unknown"));
// form_data(送信順)
var parsed = parseOrderedFormData_(raw);
var keysOrder = parsed.keysOrder; // 送信順のキー配列
var valuesMap = parsed.valuesMap; // { key: [v1,v2...] }
// ヘッダー準備(初回のみ作成。以後は上書きしない)
ensureHeaderOnceAndExpand_(sheet, keysOrder);
// データ行を「列の位置」で作る
var baseLen = 3; // date, uid, snsname
var lastCol = sheet.getLastColumn();
var row = [];
for (var i = 0; i < lastCol; i++) row.push("");
row[0] = sentAt;
row[1] = uid;
row[2] = snsname;
// form_data は「送信順に」3列目以降へ入れる(ヘッダー名がリネームされててもOK)
// 既存列数を超える分は ensureHeaderOnceAndExpand_ が追加済み
for (var j = 0; j < keysOrder.length; j++) {
var k = keysOrder[j];
var arr = valuesMap[k] || [];
var col = baseLen + j; // 0-based
if (col >= row.length) break;
row[col] = arr.join("\n"); // ファイル複数は改行、チェックボックスは元がカンマ文字列ならそのまま
}
sheet.appendRow(row);
return ContentService.createTextOutput("OK");
} catch (err) {
debug.appendRow([new Date(), "ERROR", String(err), raw]);
return ContentService.createTextOutput("NG");
}
}
/**
* 1行目(ヘッダー)に何か入っているかだけ見て、
* - 空なら作る(date, uid, snsname, + form_dataキー)
* - 既にあるなら上書きしない
* - form_dataの数が増えたときだけ右に列追加(ヘッダー名は来たキー名)
*/
function ensureHeaderOnceAndExpand_(sheet, keysOrder) {
var base = ["date", "uid", "snsname"];
var baseLen = base.length;
var lastCol = sheet.getLastColumn();
var header = [];
if (lastCol > 0) {
header = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
}
// ヘッダー行に何か入っているか?
var hasHeader = false;
for (var i = 0; i < header.length; i++) {
if (header[i] !== "") { hasHeader = true; break; }
}
// 初回:ヘッダーを作る(キー名のまま。あとで自由にリネームOK)
if (!hasHeader) {
var newHeader = base.concat(keysOrder);
sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
return;
}
// 既存ヘッダーあり:足りない列だけ追加
// 既存フォーム列数 = 全列 - 固定3列
var currentFormCols = Math.max(0, lastCol - baseLen);
if (keysOrder.length <= currentFormCols) return;
// 追加が必要な分だけ、右にキー名を入れて列追加
for (var j = currentFormCols; j < keysOrder.length; j++) {
var keyName = keysOrder[j] || ("form_add_" + (j + 1));
sheet.getRange(1, baseLen + j + 1).setValue(keyName); // 1-based
}
}
function parseOrderedFormData_(raw) {
var keysOrder = [];
var valuesMap = {};
if (!raw) return { keysOrder: keysOrder, valuesMap: valuesMap };
var parts = raw.split("&");
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
if (!part) continue;
var eq = part.indexOf("=");
var kEnc = (eq >= 0) ? part.slice(0, eq) : part;
var vEnc = (eq >= 0) ? part.slice(eq + 1) : "";
var key = decodeURIComponent(kEnc.replace(/\+/g, " "));
var val = decodeURIComponent(vEnc.replace(/\+/g, " "));
var m = key.match(/^form_data\[(.+)\]$/);
if (!m) continue;
// "form34-6][0" / "form34-6][0]" を "form34-6" に正規化
var innerKey = m[1].replace(/\]\[\d+\]?$/g, "");
if (!valuesMap[innerKey]) valuesMap[innerKey] = [];
if (val !== "") valuesMap[innerKey].push(val);
if (keysOrder.indexOf(innerKey) === -1) keysOrder.push(innerKey);
}
return { keysOrder: keysOrder, valuesMap: valuesMap };
}
function sheetNameFromFormName_(formName) {
var prefix = "form_";
var maxLen = 100;
var name = String(formName)
.replace(/:/g, ":")
.replace(/[\\\/\?\*\[\]]/g, " ")
.replace(/\s+/g, " ")
.trim();
if (!name) name = "unknown";
var allow = maxLen - prefix.length;
if (name.length > allow) name = name.slice(0, allow).trim();
return prefix + name;
}
function getOrCreateSheet_(ss, name) {
return ss.getSheetByName(name) || ss.insertSheet(name);
}
function fmt_(d) {
return Utilities.formatDate(d, "Asia/Tokyo", "yyyy-MM-dd HH:mm:ss");
}
手順4:デプロイして利用可能な状態にします
右上の 「デプロイ」→「新しいデプロイ」 を選んで、

歯車のアイコンにカーソルを合わせて、種類を 「ウェブアプリ」 にします。

そして、以下のように設定します。
実行するユーザー:自分
アクセスできるユーザー:全員

そして、「デプロイ」をクリックして実行します。
初回は権限許可が求められるので許可してください。

承認時に、「Googleはこのアプリを検証していません」 という警告画面が表示されることがあります。
これは 不正な動作が検出された という意味ではなく、個人(または自社)で作成した Apps Script は、Google の“検証済みアプリ”として登録されていないことが多いため に出る、一般的な案内です。
本手順は、お客様ご自身の Google アカウント内で作成した Apps Script に、弊社が提供するサンプルプログラムを貼り付けて利用するため、
上記の警告が表示されても、内容が正しければ通常は問題ありません。
もし以下のような画面になったら、左下をクリックして、

さらに、「無題のプロジェクトへ(危険)」をクリックして、

「続く」をクリックしてください。

手順5:デプロイ後に表示される ウェブアプリのURL をコピーしておきます
以下の箇所の、ウェブアプリのURL をコピーしておきます(URLは .../exec で終わるものを使います)

Apps Script側のコードを修正する際に
再度「新しいデプロイ」をしてしまうとウェブアプリのURLが変わってしまいます。
URLを変えずにデプロイするために、コード編集後に
デプロイ > 「デプロイを管理」 から

鉛筆マークをクリックして

バージョンで「新バージョン 」を選択して、

最後に「デプロイ」ボタンをクリックすることで、URLを変えずにデプロイすることが可能です。
プロライン側の設定
手順6:プロライン側にウェブアプリのURLを貼って連携します
プロラインフリー管理画面のフォームページを開いて

スプレッドシートに回答を反映させたい対象のフォームを選択して、
「登録発生時に外部システムにデータを送信する」 にチェックを入れて、
URL欄に 先ほどコピーしたウェブアプリURL を貼り付けて、その下の「保存」ボタンをクリックします。

フォーム送信して動作を確認ください
最後に、実際にフォームを送信して動作をご確認ください。
新たにシートを作成してフォーム名と共に記録されますので、該当のシートを選択し、

スプレッドシートに以下の内容が自動で追記されることをご確認ください。
- date(フォーム送信日時)
- uid(ユーザーID)
- snsname(表示名)
- form_data の内容

スプレッドシートに出力された1行目(ヘッダー行)の項目名は、変更可能ですので、例えば以下のように変更していただいても構いません。

スプレッドシート側で途中に行を挿入するなど編集すると、回答時に反映される際にズレる原因となりますので、
項目追加が必要な場合はサンプルコードを編集していただくか、フォーム質問を追加するか、等での対応をご検討ください。
