GASによるHubDBデータ送信

Google Apps ScriptでスプレッドシートのデータをHubDB APIに送信し、自動同期を実現する実装方法

HubSpotHubDBGASAPI自動化
読了時間: 16分

実装の概要

Google Apps Script(GAS)を使って、スプレッドシートのデータをHubDB APIに送信します。

同期は以下の4ステップで実行されます:

  1. テーブルの公開終了(Unpublish)
  2. 既存行の削除
  3. スプレッドシートから新規登録
  4. テーブルの再公開(Publish)

事前準備

HubSpot Private Appの作成

  1. HubSpot管理画面 → 設定 → 連携 → Private Apps
  2. 「Private Appを作成」をクリック
  3. 必要なスコープを設定:
    • cms.hubdb.tables.read
    • cms.hubdb.tables.write
    • cms.hubdb.rows.read
    • cms.hubdb.rows.write
  4. アクセストークンをコピー

GASにトークンを保存

セキュリティのため、トークンはスクリプトプロパティに保存します。コードに直接書かないでください。

// トークン保存用の関数(初回のみ実行)
function setHubSpotToken() {
  const token = 'YOUR_ACCESS_TOKEN'; // 実際のトークンに置き換え
  PropertiesService.getScriptProperties().setProperty('HUBSPOT_TOKEN', token);

  // 保存確認
  const saved = PropertiesService.getScriptProperties().getProperty('HUBSPOT_TOKEN');
  Logger.log(saved ? '保存成功' : '保存失敗');
}

同期コードの実装

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と連携するスプレッドシートの構成例です。

商品A
hs_pathproduct-a
image_urlhttps://...
description商品Aの説明
order1
商品B
hs_pathproduct-b
image_urlhttps://...
description商品Bの説明
order2
商品C
hs_pathproduct-c
image_urlhttps://...
description商品Cの説明
order3
  • hs_name: HubDBの行名(必須)
  • hs_path: URLパス(動的ページ用)
  • その他のカラムはHubDBテーブルの定義に合わせる

まとめ

GASによるHubDB同期のポイント:

  • セキュリティ: トークンはスクリプトプロパティに保存
  • 同期フロー: Unpublish → Delete → Create → Publish
  • ページング対応: 大量データは100件ずつ処理
  • 定期実行: トリガーで自動同期を設定
  • エラー対策: 通知を追加して運用を安定化

この仕組みを構築すれば、スプレッドシートの更新がHubSpotサイトに自動反映されるようになります。

関連記事