実装の概要
Google Apps Script(GAS)を使って、スプレッドシートのデータをHubDB APIに送信します。
同期は以下の4ステップで実行されます:
- テーブルの公開終了(Unpublish)
- 既存行の削除
- スプレッドシートから新規登録
- テーブルの再公開(Publish)
事前準備
HubSpot Private Appの作成
- HubSpot管理画面 → 設定 → 連携 → Private Apps
- 「Private Appを作成」をクリック
- 必要なスコープを設定:
cms.hubdb.tables.readcms.hubdb.tables.writecms.hubdb.rows.readcms.hubdb.rows.write
- アクセストークンをコピー
GASにトークンを保存
セキュリティのため、トークンはスクリプトプロパティに保存します。コードに直接書かないでください。
// トークン保存用の関数(初回のみ実行)
function setHubSpotToken() {
const token = 'YOUR_ACCESS_TOKEN'; // 実際のトークンに置き換え
PropertiesService.getScriptProperties().setProperty('HUBSPOT_TOKEN', token);
// 保存確認
const saved = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
Logger.log(saved ? '保存成功' : '保存失敗');
}
セキュリティ注意
トークンをコードに直書きしたままGitHubなどにアップしないでください。スクリプトプロパティに保存し、コード上のトークン文字列は削除しましょう。
同期コードの実装
1. 公開終了(Unpublish)関数
function unpublishHubDBTable(tableId) {
const token = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
if (!token) throw new Error('トークンが設定されていません');
const url = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}/unpublish`;
const res = UrlFetchApp.fetch(url, {
method: 'POST',
muteHttpExceptions: true,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const code = res.getResponseCode();
if (code === 204 || code === 200) {
Logger.log(`テーブル ${tableId} の公開を終了しました`);
} else {
Logger.log(`公開終了に失敗しました: ${res.getContentText()}`);
}
}
2. 既存行の削除関数
HubDBのAPIはページング対応が必要です。100件ずつ取得して削除します。
function deleteAllHubDBRows(tableId) {
const token = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
if (!token) throw new Error('トークンが設定されていません');
let offset = 0;
let hasMore = true;
while (hasMore) {
// 下書き行を取得
const url = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}/rows?limit=100&offset=${offset}`;
const res = UrlFetchApp.fetch(url, {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
});
const json = JSON.parse(res.getContentText());
// 各行を削除
for (const row of json.results) {
const deleteUrl = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}/rows/${row.id}/draft`;
UrlFetchApp.fetch(deleteUrl, {
method: 'DELETE',
muteHttpExceptions: true,
headers: { 'Authorization': `Bearer ${token}` }
});
Logger.log(`削除: rowId=${row.id}`);
}
// 次のページがあるか確認
hasMore = json.paging && json.paging.next;
if (hasMore) {
const nextLink = json.paging.next.link;
const match = nextLink.match(/offset=(\d+)/);
offset = match ? parseInt(match[1], 10) : 0;
}
}
}
3. スプレッドシートから新規登録
function createHubDBRowsFromSheet(sheetName, tableId) {
const token = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
if (!token) throw new Error('トークンが設定されていません');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (!sheet) throw new Error(`シート '${sheetName}' が見つかりません`);
const data = sheet.getDataRange().getValues();
const headers = data.shift(); // 1行目をヘッダーとして取得
const url = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}/rows`;
// HubDBのカラム名を取得
const validKeys = getHubDBColumnNames(tableId, token);
let count = 0;
for (const row of data) {
const values = {};
let name = '';
let path = '';
// ヘッダーに従ってデータをマッピング
headers.forEach((key, i) => {
if (typeof key === 'string' && key.trim() !== '' && row[i] !== '') {
let value = row[i];
// 数値は文字列に変換
if (typeof value === 'number') {
value = value.toString();
}
// 特殊フィールドの処理
if (key === 'hs_name') name = value;
else if (key === 'hs_path') path = value;
// 有効なカラムのみ追加
if (validKeys.includes(key)) {
values[key] = value;
}
}
});
// 空データはスキップ
if (Object.keys(values).length === 0 && !name && !path) continue;
const payload = { values };
if (name) payload.name = name;
if (path) payload.path = path;
// APIリクエスト
const res = UrlFetchApp.fetch(url, {
method: 'POST',
muteHttpExceptions: true,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload)
});
if (res.getResponseCode() === 201) {
count++;
} else {
Logger.log(`登録失敗: ${res.getContentText()}`);
}
}
Logger.log(`${count} 件を HubDB に登録しました`);
}
// HubDBテーブルのカラム名を取得
function getHubDBColumnNames(tableId, token) {
const url = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}`;
const res = UrlFetchApp.fetch(url, {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
});
const json = JSON.parse(res.getContentText());
return json.columns.map(col => col.name);
}
4. 再公開(Publish)関数
function publishHubDBTable(tableId) {
const token = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
if (!token) throw new Error('トークンが設定されていません');
const url = `https://api.hubapi.com/cms/v3/hubdb/tables/${tableId}/draft/publish`;
const res = UrlFetchApp.fetch(url, {
method: 'POST',
muteHttpExceptions: true,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const code = res.getResponseCode();
if (code === 204 || code === 200) {
Logger.log(`テーブル ${tableId} を再公開しました`);
} else {
Logger.log(`再公開に失敗しました: ${res.getContentText()}`);
}
}
メイン同期関数
4つのステップをまとめて実行するメイン関数です。
function syncHubDBTable(sheetName, tableId) {
Logger.log(`[${sheetName}] の同期を開始します`);
try {
Logger.log('Step 1: 公開終了');
unpublishHubDBTable(tableId);
Logger.log('Step 2: 既存行の削除');
deleteAllHubDBRows(tableId);
Logger.log('Step 3: 新規登録');
createHubDBRowsFromSheet(sheetName, tableId);
Logger.log('Step 4: 再公開');
publishHubDBTable(tableId);
Logger.log(`完了: '${sheetName}' → Table ID ${tableId}`);
} catch (e) {
Logger.log(`エラー発生: ${e.message}`);
}
}
複数テーブルの同期
複数のシートをそれぞれ異なるHubDBテーブルに同期する場合の例です。
function syncAllTables() {
const mappings = [
{ sheet: 'Products', tableId: '12345678' },
{ sheet: 'Stores', tableId: '23456789' },
{ sheet: 'Rankings', tableId: '34567890' },
];
for (const { sheet, tableId } of mappings) {
syncHubDBTable(sheet, tableId);
Logger.log('----------------------------------------');
}
}
定期実行の設定
GASのトリガー機能を使って、同期を定期実行できます。
GASエディタ → 時計アイコン → トリガーを追加
syncAllTables など、メインの同期関数を選ぶ
「時間主導型」→ 毎日/毎週/毎時間など
設定を保存して完了
推奨の実行頻度
| データの性質 | 推奨頻度 |
|---|---|
| 店舗一覧(変更少ない) | 週1回 |
| 商品ランキング(日次更新) | 毎日1回 |
| 在庫状況(リアルタイム性重視) | 毎時間 |
| キャンペーン情報(更新時のみ) | 手動実行 |
エラーハンドリング
実運用では、エラー時の通知を追加することをおすすめします。
function syncWithNotification() {
try {
syncAllTables();
} catch (e) {
// Slackやメールで通知
MailApp.sendEmail(
'admin@example.com',
'HubDB同期エラー',
`エラーが発生しました: ${e.message}`
);
}
}
スプレッドシートの構成例
HubDBと連携するスプレッドシートの構成例です。
| hs_name | hs_path | image_url | description | order |
|---|---|---|---|---|
| 商品A | product-a | https://... | 商品Aの説明 | 1 |
| 商品B | product-b | https://... | 商品Bの説明 | 2 |
| 商品C | product-c | https://... | 商品Cの説明 | 3 |
- hs_name: HubDBの行名(必須)
- hs_path: URLパス(動的ページ用)
- その他のカラムはHubDBテーブルの定義に合わせる
まとめ
GASによるHubDB同期のポイント:
- セキュリティ: トークンはスクリプトプロパティに保存
- 同期フロー: Unpublish → Delete → Create → Publish
- ページング対応: 大量データは100件ずつ処理
- 定期実行: トリガーで自動同期を設定
- エラー対策: 通知を追加して運用を安定化
この仕組みを構築すれば、スプレッドシートの更新がHubSpotサイトに自動反映されるようになります。