スプレッドシートとkintoneの使い分けと、つなぎ方

公開 書いた人 大林 諒

会員台帳をスプレッドシートで持っていて、行が増え、触る人が増え、見せたくない列が出てきたころに「kintoneに移したほうがいいのか」という話が出ます。この記事では、スプレッドシート・AppSheet・kintoneの3つを同じ物差しで比べ、どこでkintoneへ移すかの目安を書きます。そのうえで、サンプル料理教室の会員台帳をkintoneに移し、講師が入れる出席はスプレッドシートのまま残して、GASで両方を同期するところまでを通しで作ります。kintoneのアカウントがあり、スプレッドシートの台帳がすでにある前提です。

この記事の内容
  1. 先に決めること
  2. スプレッドシート・AppSheet・kintoneの比較
  3. スプレッドシートからkintoneへ移る目安
  4. 手順1:会員台帳をCSVでkintoneに移す
  5. 手順2:GASでkintoneとスプレッドシートを同期する
  6. 応用
  7. できないことと、その代わり
  8. よく詰まるところ
  9. 次にやること

先に決めること

全部移すか、一部だけ移すか

スプレッドシートの台帳を全部kintoneに移す必要はありません。サンプル料理教室では、会員台帳はkintoneへ、出席と月謝の請求はスプレッドシートのままにします。会員の情報は代表と受付が細かく管理したい一方で、出席は講師が毎日のレッスンの後に入れるだけで、すでにAppSheetのアプリで回っているからです。動いているものを全部作り直すと、移行の手間のわりに得るものが少なくなります。

どちらを正にするか

両方に会員の名前が載る以上、どちらの値が正しいかを決めておきます。正(マスター)でない側で書き換えた値は、次の同期で上書きされて消えます。サンプル料理教室では次のように分けます。

  • 会員番号・氏名・連絡先・コース・在籍状況はkintoneが正。スプレッドシートには写しを置くだけで、そこでは直さない
  • 出席の記録はスプレッドシートが正。kintoneには月ごとの出席回数だけを送る

この決め方を、スプレッドシートの写しのシートの1行目に「このシートはkintoneから自動で上書きされます。直すときはkintoneで」と書いておくと、受付がうっかり直す事故が減ります。

何で2つを結ぶか

会員を両方で同じ人と分かるように、変わらない番号を1つ決めます。サンプル料理教室では M-0001 の形の会員番号です。氏名で結ぶと、同姓同名や結婚による改姓で崩れます。kintone側では、この会員番号のフィールドに「値の重複を禁止する」を入れておきます。これがあるとAPIやCSVでの更新のキーに使えます。

kintoneのコース

kintoneのライトコースは、外部サービスとの連携やプラグインといった拡張機能に対応していません(2026年10月時点)。GASからREST APIで同期するなら、スタンダードコース以上が要ります。また、ライトとスタンダードは10ユーザーからの契約です(ワイドは1,000ユーザーから)。スタッフ6名の教室でも10ユーザー分の料金がかかります。

スプレッドシート・AppSheet・kintoneの比較

比べるところスプレッドシートAppSheetkintone
料金(2026年10月時点・税抜)Google Workspaceに含まれる(Business Starterは1ユーザー月800円)AppSheet CoreはGoogle Workspaceのほとんどの有料プランに含まれるライト1,000円・スタンダード1,800円(1ユーザー月額、どちらも10ユーザーから)
権限ファイル単位の共有(閲覧者・閲覧者(コメント可)・編集者)と、範囲の保護アプリごとに使う人を決め、見せる行や列を絞れるアプリ・レコード・フィールドの3段で設定できる
同時に触るとき同じファイルを何人でも同時に編集できるが、同じセルは後から書いた人が勝つ1件ずつ画面で入れる。データはスプレッドシート側にたまる1件ずつ開いて保存する。誰がいつ何を変えたかがレコードごとに残る
件数1ファイル2,000万セルまたは100MBまでデータ元がスプレッドシートならその上限。AppSheetのデータベースはCoreで1つ2,500行まで件数の上限より、API(スタンダードは1アプリ1日1万リクエスト)と動作の重さで考える
スマホアプリで見られるが、横に長い表は入力しにくいスマホで使う前提のアプリが作れる公式のスマホアプリで入力・閲覧できる
自動化GAS・n8nAppSheetの自動化(Automation)プロセス管理・通知・JavaScript・プラグイン(スタンダード以上)
向く使い方集計・一覧・数人で回す台帳現場で決まった項目を入れる入力アプリ何人もが触る顧客台帳、1件ずつ履歴を積む管理

スプレッドシートは計算と集計が得意で、関数とピボットで月ごとの数字をすぐ出せます。kintoneは1件の会員に対して、問い合わせ・面談・振替のやりとりを何件もぶら下げ、誰がいつ変えたかを残すのが得意です。AppSheetはその間で、スプレッドシートをデータ置き場にしたまま、スマホの入力画面を作る道具です。3つは競合というより、役割が違います。

スプレッドシートからkintoneへ移る目安

次のうち2つ以上に当てはまったら、kintoneへ移すことを考えます。1つだけなら、スプレッドシートの工夫で済むことが多いです。

起きていることスプレッドシートでの手当てkintoneに移すと
3人以上が同じ台帳を毎日書き換え、上書きの事故が起きる担当ごとにシートを分ける、範囲の保護1件ずつ保存するので、他の人の行を巻き込まない
列ごとに見せたくない人がいる(月謝の金額を講師に見せない)ファイルを分けてIMPORTRANGEでつなぐフィールドのアクセス権で、講師には金額のフィールドを見せない
1人の会員に、やりとりの記録を何件も残したいセルにメモを追記していくコメントや関連レコードで、時系列に積める
行が数千を超え、関数が重くなってきた計算列を減らす、古い年を別ファイルへ一覧と絞り込みで探す使い方に変わる
誰が変えたかを会員1人ごとに追いたいセルの編集履歴を1つずつ見るレコードの変更履歴で追える
「入会手続き中」「在籍」「休会」の流れを決まった順で動かしたいドロップダウンと条件付き書式プロセス管理で、誰が次に動かすかまで決められる
外出先から講師や受付がスマホで台帳を見るAppSheetで入力画面を作る公式のスマホアプリで見られる

逆に、台帳を触るのが2人だけで、行数が数百、見せたくない列も無いうちは、スプレッドシートのままが速くて安上がりです。実際に運用していると、kintoneに移したあとで「集計だけはやっぱりスプレッドシートで見たい」となる会社が多く、片方に寄せきるより、役割で分けたほうが長続きします。

手順1:会員台帳をCSVでkintoneに移す

最初の移行はCSVで一度に移します。GASで1件ずつ送るより速く、間違いにも気づきやすいからです。

スプレッドシート側で整える

移す前に、スプレッドシートの会員台帳を整えます。ここを飛ばすと、kintoneで読み込みのエラーが山ほど出ます。

  1. 会員台帳のファイルをコピーして、作業用の控えを作る
  2. 1行目を項目名だけにし、結合したセルを解除する
  3. 会員番号の列に空欄や重複が無いことを確かめる(=COUNTIF(A:A,A2)>1 を隣の列に入れて TRUE を探す)
  4. 金額の列から「円」と桁区切りのカンマを外す。表示形式を「数値」にして、小数点以下を0桁にする
  5. 日付の列を 2026-11-14 の形にそろえる(表示形式の「カスタム日時」で yyyy-mm-dd)
  6. 担当講師の列は、kintoneのユーザーにするならkintoneのログイン名に書き換える。ドロップダウンにするなら、表記のゆれ(「田中」「田中先生」)をそろえる
  7. 「ファイル」→「ダウンロード」→「カンマ区切り形式(.csv)」を選ぶ
スプレッドシートの「ファイル」→「ダウンロード」で、カンマ区切り形式(.csv)を選ぶところ
スプレッドシートの「ファイル」→「ダウンロード」で、カンマ区切り形式(.csv)を選ぶところ

スプレッドシートから書き出したCSVはUTF-8です。Excelで一度開いて保存し直すと文字コードが変わることがあるので、そのままkintoneに読み込みます。

CSVからkintoneのアプリを作る

まだkintoneにアプリが無いなら、CSVからアプリを作れます。

  1. kintoneのポータルの「アプリ」の欄で「+」(アプリを作成する)を押す
  2. 「kintoneアプリストア」の「新しくアプリをつくる」で「CSVを読み込んで作成」を押す
  3. 「作成を開始する」を押し、注意事項を確かめて「アップロードへ進む」を押す
  4. 「参照」から書き出したCSVを選ぶ
  5. 「プレビューを確認する」で文字化けや列のずれが無いかを見る。化けていたら「文字コード」を変える
  6. 「アプリの作成を開始する」で、列ごとに自動で選ばれたフィールドの種類を確かめる。要らない列は「(この列を無視する)」にする
  7. 右下の「作成」を押し、確認の画面で「OK」を押す
kintoneの「CSVを読み込んで作成」で、列ごとのフィールドの種類を確かめる画面
kintoneの「CSVを読み込んで作成」で、列ごとのフィールドの種類を確かめる画面

読み込めるのは1ファイル100MB・10万行・500列までです。作ったあと、アプリの設定のフォームで次を直します。

  1. 会員番号のフィールドを開き、「値の重複を禁止する」と「必須項目にする」にチェックを入れる
  2. 各フィールドのフィールドコードを、日本語の項目名と同じにそろえる(会員番号 氏名 コース 在籍状況 など)。GASのコードはフィールドコードで書くので、ここで決めた名前を後で変えない
  3. 「今月の出席回数」という数値のフィールドを足す(出席の同期で使う)
  4. 「アプリを更新」を押す

すでにアプリがあるときは「ファイルから読み込む」

アプリを先に作ってある場合や、2回目以降の読み込みは、レコードの一覧の画面から入れます。

  1. レコードの一覧の右上の「オプション」(3つの点)から「ファイルから読み込む」を選ぶ
  2. 「ファイルを選択」でCSVを選ぶ
  3. 「先頭行をファイルの列名として扱う」にチェックを入れる
  4. 「アプリへの反映方法」で、初回は「レコードの追加のみ」を選ぶ。2回目以降に既存の会員を直すなら「レコードの更新と追加」を選び、「更新キー」に会員番号を選ぶ
  5. エラーのときの処理は「読み込みを中止する(エラー行以降の行は読み込みません)」を選ぶ
  6. フィールドごとに「ファイルの列」を対応させる。入れないフィールドは「(指定しない)」
  7. 右下の「読み込む」を押す
kintoneの「ファイルから読み込む」で、更新キーに会員番号を選んだところ
kintoneの「ファイルから読み込む」で、更新キーに会員番号を選んだところ

最初の読み込みで「中止する」を選ぶのは、エラーの行だけ飛ばして進むと、何件が入らなかったのかを後から探す手間が大きいからです。止まったら、エラーの行をスプレッドシートで直してもう一度読み込みます。

計算のフィールドや、ほかのフィールドから値を持ってくるフィールドには読み込めません。数値の列は、桁区切りの書き方に合わせて「桁区切りと小数点の記号」を選びます。日付の形式も選べますが、スラッシュ以外の区切りは年・月・日の順で読まれます。

手順2:GASでkintoneとスプレッドシートを同期する

移したあとも、出席のアプリ(AppSheet)は会員の名前の一覧をスプレッドシートから読んでいます。そこで、kintoneの会員台帳を毎朝スプレッドシートの「会員」シートに写し、月末には出席シートの回数をkintoneに書き戻します。

kintoneでAPIトークンを作る

  1. 会員台帳のアプリを開き、右上の歯車「アプリを設定」を押す
  2. 「設定」タブの「カスタマイズ/サービス連携」にある「APIトークン」を押す
  3. 「生成する」を押す
  4. アクセス権で「レコード閲覧」と「レコード編集」にチェックを入れる
  5. メモに GAS 出席同期 と入れる
  6. 右下の「保存」を押す
  7. 右上の「アプリを更新」を押し、確認の画面でも「アプリを更新」を押す
kintoneのAPIトークンの画面。レコード閲覧とレコード編集にチェック
kintoneのAPIトークンの画面。レコード閲覧とレコード編集にチェック

最後の「アプリを更新」を忘れると、トークンが効かずにエラーになります。APIトークンでした操作は、kintoneの上では「Administrator」が更新したものとして記録されます。誰が変えたかを追いたいときは、更新者がAdministratorのものはGASの同期だと読み替えます。

スクリプトにトークンを入れる

  1. 出席のスプレッドシートを開き、「拡張機能」→「Apps Script」を開く
  2. 左の「プロジェクトの設定」(歯車)を開く
  3. 「スクリプト プロパティ」で「スクリプト プロパティを追加」を押す
  4. プロパティに KINTONE_API_TOKEN、値にAPIトークンを入れて保存する

トークンをコードに直接書かないのは、スクリプトをコピーして人に渡したときにトークンまで渡ってしまうからです。

同期のコード

スプレッドシートには「会員」シート(A列から会員番号・氏名・コース・在籍状況・担当講師)と、「出席」シート(A列から日付・会員番号・氏名・レッスン・出欠)があるものとします。

const KINTONE_DOMAIN = 'sample-cooking.cybozu.com';
const MEMBER_APP_ID = 15;
const TZ = 'Asia/Tokyo';

function kintoneRequest_(method, path, body) {
  const token = PropertiesService.getScriptProperties().getProperty('KINTONE_API_TOKEN');
  const options = {
    method: method,
    headers: { 'X-Cybozu-API-Token': token },
    muteHttpExceptions: true
  };
  if (body) {
    options.contentType = 'application/json';
    options.payload = JSON.stringify(body);
  }
  const res = UrlFetchApp.fetch('https://' + KINTONE_DOMAIN + path, options);
  const json = JSON.parse(res.getContentText());
  if (res.getResponseCode() !== 200) {
    throw new Error('kintone ' + res.getResponseCode() + ': ' + json.message);
  }
  return json;
}

// kintoneの会員台帳を「会員」シートに写す(毎朝)
function pullMembers() {
  const rows = [];
  let lastId = 0;
  while (true) {
    const query = '$id > ' + lastId + ' order by $id asc limit 500';
    const path = '/k/v1/records.json?app=' + MEMBER_APP_ID +
      '&query=' + encodeURIComponent(query);
    const records = kintoneRequest_('get', path).records;
    records.forEach(function (r) {
      rows.push([
        r['会員番号'].value,
        r['氏名'].value,
        r['コース'].value,
        r['在籍状況'].value,
        r['担当講師'].value
      ]);
    });
    if (records.length < 500) break;
    lastId = Number(records[records.length - 1]['$id'].value);
  }
  const sheet = SpreadsheetApp.getActive().getSheetByName('会員');
  const last = sheet.getLastRow();
  if (last > 1) sheet.getRange(2, 1, last - 1, 5).clearContent();
  if (rows.length > 0) sheet.getRange(2, 1, rows.length, 5).setValues(rows);
}

// 今月の出席回数をkintoneに書き戻す(毎日の夜か月末)
function pushAttendanceCounts() {
  const ss = SpreadsheetApp.getActive();
  const ym = Utilities.formatDate(new Date(), TZ, 'yyyy-MM');
  const counts = {};
  ss.getSheetByName('会員').getDataRange().getValues().slice(1).forEach(function (row) {
    if (row[0]) counts[row[0]] = 0;
  });
  ss.getSheetByName('出席').getDataRange().getValues().slice(1).forEach(function (row) {
    const date = row[0], memberNo = row[1], status = row[4];
    if (!(date instanceof Date) || status !== '出席') return;
    if (Utilities.formatDate(date, TZ, 'yyyy-MM') !== ym) return;
    if (memberNo in counts) counts[memberNo] += 1;
  });
  const records = Object.keys(counts).map(function (no) {
    return {
      updateKey: { field: '会員番号', value: no },
      record: { '今月の出席回数': { value: counts[no] } }
    };
  });
  for (let i = 0; i < records.length; i += 100) {
    kintoneRequest_('put', '/k/v1/records.json', {
      app: MEMBER_APP_ID,
      records: records.slice(i, i + 100)
    });
  }
}

MEMBER_APP_ID はkintoneのアプリのURL https://sample-cooking.cybozu.com/k/15/ の数字です。pullMembers は500件ずつ取り、取ってきた最後のレコードの番号より後を次に取ります。kintoneは1回に500件までしか返さず、offset の指定は1万件までなので、件数が増えても止まらないように番号で区切っています。

pushAttendanceCounts は「会員」シートの全員を0回から数え始めるので、今月1回も来ていない会員もkintoneで0回に直ります。kintoneの更新は1回100件までなので、100件ずつに分けて送ります。100件の中に1件でも失敗があると、その100件はすべて取り消されます。会員番号がkintoneに無い行が混じると全部止まるのはこのためです。

動かして、トリガーを入れる

  1. 関数の選択で pullMembers を選び、「実行」を押す
  2. 初回は承認の画面が出るので、自分のアカウントを選んで許可する
  3. 「会員」シートにkintoneの会員が並んだことを確かめる
  4. 続けて pushAttendanceCounts を実行し、kintoneの「今月の出席回数」が入ったことを確かめる
kintoneの会員台帳の一覧に、GASから送った「今月の出席回数」が入ったところ
kintoneの会員台帳の一覧に、GASから送った「今月の出席回数」が入ったところ
  1. 左の「トリガー」(時計)を開き、「トリガーを追加」を押す
  2. pullMembers、イベントのソース「時間主導型」、「日付ベースのタイマー」、「午前6時〜7時」で保存する
  3. 同じように pushAttendanceCounts を「午後11時〜午前0時」で保存する
GASのトリガーの一覧に、pullMembersとpushAttendanceCountsの2つが並んだところ
GASのトリガーの一覧に、pullMembersとpushAttendanceCountsの2つが並んだところ

出席回数を毎晩送っておけば、月の途中でもkintoneで「今月まだ1回も来ていない会員」を絞り込めます。受付はその一覧を見て、休みがちな会員に声をかけます。

応用

kintoneの変更をすぐにスプレッドシートへ流す

毎朝の写しでは、昼に入会した会員が夜のレッスンの出席一覧に出てきません。すぐに反映したいときは、kintoneのWebhookを使い、レコードが追加・編集されたときにn8nへ知らせて、n8nからスプレッドシートの行を足すか書き換えます。kintoneのWebhookは送るだけで、GASのWebアプリに直接受けさせることもできますが、失敗の記録が残るn8nを挟んだほうが追いやすくなります。この組み方は「Google Workspaceとn8nをつなぐ」の記事で書いています。

フォームの申込をkintoneに入れる

体験申込のGoogleフォームの回答も、kintoneの顧客台帳に入れられます。GASのフォーム送信時のトリガーから POST /k/v1/record.json を呼ぶか、n8nでシートの行の追加を拾って送ります。体験の段階ではスプレッドシートの予約一覧で受付が対応状況を付け、入会が決まった人だけkintoneに入れる、という分け方もできます。台帳を体験の人で埋めたくないなら、こちらが向きます。

集計はスプレッドシートで見る

kintoneのグラフでも月ごとの入会数は出せますが、出席や月謝と組み合わせた集計はスプレッドシートとデータポータル(旧Looker Studio)のほうが速く作れます。pullMembers で写した「会員」シートに入会日と退会日の列も足しておけば、データポータルで月ごとの在籍数が見られます。

できないことと、その代わり

できないこと理由代わりの手
ライトコースでGASから同期するライトコースは外部サービス連携に対応していないスタンダードコースにする。ライトのままならCSVの書き出しと読み込みで手で同期する
スプレッドシートとkintoneの両方で同じ項目を直す次の同期で片方の値が消える項目ごとに正を決め、正でない側は書き換えない
APIトークンで「誰が」変えたかを残すトークンの操作はAdministratorとして記録される変えた人を残したいなら、スプレッドシートに「更新者」の列を作って一緒に送る
計算のフィールドやステータスにAPIやCSVで値を入れるkintone側で計算・管理される項目は外から入れられない元になるフィールドに値を入れて、kintone側で計算させる
1回で何千件も更新する1回の更新は100件まで100件ずつに分けて送る。GASの実行は1回6分までなので、件数が多いときは日を分ける
6名だけで契約するライトとスタンダードは10ユーザーから(ワイドは1,000ユーザーから)10ユーザー分の料金で考えるか、触る人が少ないならスプレッドシートとAppSheetで続ける
kintoneのレコードをスプレッドシートで直接編集する2つは別のデータ編集はkintoneで。スプレッドシートは写しとして見るだけにする

よく詰まるところ

症状原因直し方
GASの実行ログに kintone と状態コードの付いた認証のエラーが出て止まるトークンの生成後に「アプリを更新」を押していない、または別アプリのトークンアプリの設定で「アプリを更新」を押す。トークンを作ったアプリとIDが合っているか確かめる
403で「権限がありません」トークンのアクセス権に「レコード編集」が無いAPIトークンの画面でチェックを足し、保存して「アプリを更新」
CSVの読み込みで文字化けするExcelで開いて保存し直し、文字コードが変わったスプレッドシートから書き出したCSVをそのまま読み込むか、「文字コード」をUTF-8にする
金額の列でエラーになる「12,000円」のように円や桁区切りが入っているスプレッドシートで数値だけにしてから書き出す
「更新キー」に会員番号が出てこない会員番号のフィールドに「値の重複を禁止する」が入っていないフィールドの設定でチェックを入れて「アプリを更新」
更新したつもりが、同じ会員が2件に増えた「レコードの追加のみ」で読み込んだ増えた分を消し、次からは「レコードの更新と追加」と更新キーを使う
pushAttendanceCounts が全部失敗するkintoneに無い会員番号が1件混じり、その100件がまとめて取り消された「会員」シートを pullMembers で写し直してから実行する。会員番号はkintoneで作る
TypeError: Cannot read properties of undefined (reading 'value')コードのフィールドコードとkintoneのフィールドコードが違うアプリの設定のフォームでフィールドコードを確かめ、コードを合わせる
日付がずれて前の日になるスプレッドシートやスクリプトのタイムゾーンが日本でない「ファイル」→「設定」とApps Scriptの「プロジェクトの設定」でタイムゾーンを東京にする
担当講師の列が [object Object] になる担当講師をkintoneのユーザー選択にしていて、値が配列で返るr['担当講師'].value.map(function (u) { return u.name; }).join('、') に書き換える
1万件を超えたあたりで取得が止まるoffset を使って取っていて、上限の1万件に当たったこの記事のコードのように $id で区切って取る
朝の同期が時々失敗している一時的な通信の失敗や、同じ時刻の大量のAPIでの混雑トリガーの失敗通知をメールで受け、続くようなら実行の時刻をずらす

次にやること

あわせて読む