外部サービス連携

Salesforceの取引先をスプレッドシートへ書き出す手順

Salesforceのレコードをスプレッドシートで見たい場面があります。Apps ScriptからREST APIのクエリを呼び、2,000件を超える結果も取り切ってシートへ書き込む実装を説明します。

2024.06.10

なぜSalesforceのレコードをシートに写すのか

Salesforceに入っている取引先のデータを、Salesforceのライセンスを持たない人と共有したい、あるいは表計算で加工してから使いたい場面があります。Apps ScriptからSalesforceのREST APIを呼び、結果をGoogleスプレッドシートへ書き込めば、閲覧用のコピーを都度作れます。

認証はApps ScriptからSalesforceを呼ぶときと同じ

Apps ScriptからSalesforceへアクセストークンを取るところは、取引先を登録する連携と同じ考え方です。ユーザー名パスワードフローは、Summer '23以降に作成された組織では既定でブロックされています。ユーザーの操作を挟まない定期連携なので、ここでもOAuth 2.0クライアントクレデンシャルフローを使います。外部クライアントアプリでクライアントクレデンシャルフローを有効化し、クライアントIDとシークレットはApps Scriptのスクリプトプロパティに保存して、ソースには書きません。

クエリ結果は1回で取り切れるとは限らない

SalesforceのREST APIでSOQLを実行すると、結果は最大2,000件ずつ返ってきます。件数が多いクエリでは、1回のレスポンスにすべてのレコードが入っていません。レスポンスの done が false のとき、nextRecordsUrl に続きを取るためのURLが入っています。この値を使って、done が true になるまで呼び続ける必要があります。

⚠️ ここをwhile文で無限に回す実装にすると、done を見ずに同じ条件で呼び続けてしまい、止まらなくなります。必ず done を終了条件にします。

Apps Scriptの実装

function getAccessToken_() {
  var props = PropertiesService.getScriptProperties();
  var response = UrlFetchApp.fetch(
    'https://xxxx.my.salesforce.com/services/oauth2/token',
    {
      method: 'post',
      payload: {
        grant_type: 'client_credentials',
        client_id: props.getProperty('SF_CLIENT_ID'),
        client_secret: props.getProperty('SF_CLIENT_SECRET')
      },
      muteHttpExceptions: true
    }
  );
  return JSON.parse(response.getContentText());
}

スクリプトプロパティのクライアントクレデンシャルで、アクセストークンを取得します。

function fetchAccounts_() {
  var auth = getAccessToken_();
  var headers = { Authorization: 'Bearer ' + auth.access_token };
  var soql = 'SELECT Id, Name, CreatedDate FROM Account ORDER BY CreatedDate DESC';
  var url = auth.instance_url + '/services/data/v67.0/query/?q=' + encodeURIComponent(soql);
  var rows = [];
  while (url) {
    var response = UrlFetchApp.fetch(url, { headers: headers, muteHttpExceptions: true });
    var result = JSON.parse(response.getContentText());
    result.records.forEach(function (record) {
      rows.push([record.Id, record.Name, record.CreatedDate]);
    });
    url = result.done ? null : auth.instance_url + result.nextRecordsUrl;
  }
  return rows;
}

nextRecordsUrlがある間、doneがtrueになるまでクエリ結果を取り切ります。

function syncAccountsToSheet() {
  var rows = fetchAccounts_();
  if (rows.length === 0) {
    return;
  }
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Account');
  var startRow = sheet.getLastRow() + 1;
  sheet.getRange(startRow, 1, rows.length, rows[0].length).setValues(rows);
}

取得した行を、Accountシートの末尾へ追記します。

fetchAccounts_ は done が true になるまでループし、nextRecordsUrl が返ってこなくなった時点で終わります。syncAccountsToSheet は取得した行を、既存の行の下へ追記します。同じ範囲を上書きしたい場合は、書き込み前にシートをクリアする処理を足します。

動作確認

  1. Salesforce側にテストレコードを作る

    取引先を数件作成しておきます。件数が少ないうちは、ページングの動きまでは確認できません。

  2. スクリプトを実行する

    Apps Scriptのエディタで関数「syncAccountsToSheet」を選び、実行します。

  3. シートを確認する

    Accountシートの末尾に、取引先のId・Name・CreatedDateが追記されていることを確かめます。

  4. 件数が多いときの動きを確かめる

    2,000件を超える取引先がある組織では、実行ログで fetchAccounts_ が複数回リクエストを送っていることを確認します。

Salesforceの取引先詳細。取引先名に「同期サンプルテスト用」が入っている
同期の確認用に作った取引先です。ここの取引先名が、このあとシートへ書き出される値になります。
Apps Scriptエディタの実行バー。実行する関数としてsynchroが選ばれている
Apps Scriptのエディタです。当時の関数名はsynchroで、本文のsyncAccountsToSheetにあたります。実行する関数を選んでから実行します。
スプレッドシート。1行目がIdとName、2行目に取引先が1件書き出されている
実行後のシートです。見出しの下に取引先のIdと取引先名が追記されているかを確かめます。記事を最初に書いた当時の画面のため、CreatedDateの列はありません。Idは黒く塗りつぶしています。

ここで間違えやすい

間違い何が起きるか
done を見ずにループを続ける終了条件がなく、止まらなくなります
nextRecordsUrl に instance_url を付け足さない相対パスのままリクエストしてしまい、失敗します
SOQLの文字列をencodeURIComponentせずに送るスペースや記号を含むクエリが正しく渡りません
ユーザー名パスワードフローのままにする新規組織では既定でブロックされ、認証エラーになります
書き込み前にシートをクリアせず追記し続ける実行のたびに同じデータが重複して増えていきます

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、クエリ結果のページングとユーザー名パスワードフローの既定ブロックを確認しています
  • コードはAPIバージョン67.0で書いています

まとめ

  • SalesforceのREST APIによるクエリは、最大2,000件ずつしか返りません
  • done が true になるまで nextRecordsUrl を追い続け、取り切ってから処理します
  • 認証は取引先登録の連携と同じく、クライアントクレデンシャルフローに直します
  • クライアントIDとシークレットはスクリプトプロパティに保存し、ソースには書きません
  • シートへの書き込みは、追記か上書きかを決めてから実装します

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。