Notion × kintone連携!JavaScriptカスタマイズでデータ同期

目次

はじめに

今回は、kintoneとNotionをAPIで連携する方法を紹介します。
kintoneは柔軟性とカスタマイズ性が高く、他のサービスとの連携も容易です。
この記事では、kintoneのJavaScriptカスタマイズ機能を活用して、Notionからデータを取得する方法を解説します。

Notionの特徴と、連携のメリット

Notion (External link) は、ノートテイキングやタスク管理、データベース管理など、多様な機能をもつワークスペースです。
ビジネスの現場で必要とされるさまざまなアプリケーションを簡単に作成、管理できるクラウドサービスのkintoneと、 Notionを連携することで、Notionに蓄積された情報をkintoneのアプリでも活用できます。
これにより、業務の効率化や情報共有のスムーズ化が期待できます。

kintoneとNotionの連携方法

kintoneのJavaScriptカスタマイズ機能を使うと、kintoneのアプリケーションに独自の機能を追加できます。
JavaScriptプログラミングによるkintoneのカスタマイズの概要
たとえば、特定の条件でフィールドの表示を変更したり、外部サービスとのデータ連携ができます。
また、Notionも、外部と連携するためのインターフェースとして、Notion APIを用意しています。
そこで、今回はJavaScriptカスタマイズを利用し、kintone側からNotion APIへリクエストすることによって、NotionのAPIと連携してみます。
今回のカスタマイズ例では、次の流れで行ってみましょう。

  1. Notionにデータベースを用意する。
  2. Notionに新しいインテグレーションを作成する。
  3. 作成したインテグレーションからデータベースへアクセスができるようにする。
  4. kintoneにアプリを作成する。
  5. kintoneにJavaScriptカスタマイズを適用する。
    今回は例として商品管理アプリを作成し、Notion側にある商品リストをkintone側へ連携するようにしてみます。

1. Notionにデータベースを用意する

Notion側にデータベースを作成しましょう。
データベースの概要、作り方についてはNotionのヘルプページ データベースの基本 (External link) を確認してください。

テーブルのプロパティは次のように設定してください。

プロパティ名 プロパティの種類 備考
商品名 タイトル
販売ステータス セレクト 販売中 販売一時停止 販売終了を項目に設定
金額 数値 数値の形式にを指定
詳細 テキスト

2. Notionに新しいインテグレーションを作成する

kintoneからNotionのデータにアクセスするため、Notion APIを利用します。
そのための設定が必要ですので、 インテグレーション設定画面 (External link) を開き、新しいインテグレーションを作成してください。
基本情報を設定し、コンテンツの読み取り権限を付与してください。

最後にJavaScriptカスタマイズへ利用するため内部インテグレーションシークレットをコピーして控えておいてください。

インテグレーションの詳細については、ヘルプページ Notion APIを使用したインテグレーションの作成 (External link) を参照ください。

3. 作成したインテグレーションからデータベースへアクセスができるようにする

インテグレーションは作成しても、最初はワークスペース内のどのコンテンツにもアクセスできません。
そのため、今回利用するデータベースで設定する必要があります。

1. Notionにデータベースを用意する で作成したデータベースのページ上で下記を行ってください。

  1. 右上の三点リーダをクリック
  2. 「接続先」をクリック
  3. 作成したインテグレーションをクリック
  4. ダイアログが表示されるので「はい」をクリック

これで作成したインテグレーションからデータベースにアクセスできます。

4. kintoneにアプリを作成する

今回は下記フィールドをもつアプリを用意します。
わかりやすくするためにここではフィールド名とフィールドコードは同一とします。

フィールド名/フィールドコード フィールドタイプ 備考
商品名 文字列一行
販売ステータス ドロップダウン Notion側に合わせて販売中 販売一時停止 販売終了を項目に設定
金額 数値 桁区切りや単位記号の設定は任意
詳細 複数行文字列
notion_id 文字列一行 Notion側との紐づけのために利用する。
値の重複を禁止するをチェック

5. kintoneにJavaScriptカスタマイズを適用する

information

JavaScriptカスタマイズのやり方の詳細についてはヘルプページ JavaScriptやCSSでアプリをカスタマイズする (External link) を参照してください。

アプリ設定の[JavaScript / CSSでカスタマイズ]に下記URLとファイルを追加します。

  1. https://unpkg.com/kintone-ui-component@1.16.0/umd/kuc.min.js
    • 今回はボタンの表示にkintone UI Componentを利用しますので、そのためのURLを追加します。
  2. Notion APIの下記コードを保存し、JavaScriptカスタマイズとして適用してください。
    下記はご自身のNotionに合わせて書き換えてください。
    • [YOUR_DATABASE_ID]: 今回作成したNotionのデータベースID。
      • たとえばブラウザーでデータベースにアクセスした時のURLが
        https://www.notion.so/your_domain/f2d589bf26604ebe8cdccc3c6c9d0d6d?v=180a664ab56c41f4bf9049cc5c438284なら、 f2d589bf26604ebe8cdccc3c6c9d0d6dがデータベースIDにあたります。
    • [YOUR_INTEGRATION_SECRET]: 先ほど取得した内部インテグレーションシークレットです。
caution
警告

内部インテグレーション等の秘密情報は、絶対に公開しないでください。
秘密情報が漏洩するとAPIを自由に実行できてしまうため、kintoneのJavaScriptの開発においても慎重に扱ってください。

cybozu develper networkでは、認証情報の秘匿にプラグインを利用する方法を推奨しています。詳しくは以下の記事を確認してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
 * Notionのデータをkintoneに同期するサンプルプログラム
 * Copyright (c) 2024 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */
(() => {
  'use strict';

  // Notion APIの設定
  const NOTION_API_URL = 'https://api.notion.com/v1/databases/[YOUR_DATABASE_ID]/query';
  const NOTION_API_HEADERS = {
    Authorization: 'Bearer [YOUR_INTEGRATION_SECRET]',
    'Notion-Version': '2022-06-28',
    'Content-Type': 'application/json'
  };

  // kintone ui componentのバージョン指定
  const Kuc = Kucs['1.16.0'];

  // 同期ボタンの設定
  const syncButton = new Kuc.Button({
    text: 'Notionデータ同期',
    type: 'submit'
  });
  // 同期が始まったときにボタンを非活性化するための関数
  const disableSyncButton = () => {
    syncButton.text = 'loading...';
    syncButton.disabled = true;
  };

  // Notionのデータを取得する関数
  const fetchNotionData = async () => {
    const response = await kintone.proxy(NOTION_API_URL, 'POST', NOTION_API_HEADERS, {});
    const notionData = JSON.parse(response[0]);
    return notionData;
  };

  // Notionとkintoneを同期させるための関数
  const syncNotionToKintone = async () => {
    // notionのデータを取得し、なければ終了する
    const notionData = await fetchNotionData();
    if (notionData.results.length === 0) {
      alert('Notionのデータが空です');
      return;
    }

    // 取得したデータをもとにデータのidを抽出し、それをもとにkintoneのレコードを取得する
    const notionIds = notionData.results.map(row => row.id);
    const kintoneFetchParams = {app: kintone.app.getId(), query: `notion_id in ("${notionIds.join('","')}")`};
    const kintoneFetchResp = await kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', kintoneFetchParams);
    const existRecords = kintoneFetchResp.records;

    // 取得したNotionのデータとkintoneのデータを元にBulkRequestでデータの作成と更新を行う。
    // 更新のためのパラメータの定義
    const paramsForUpdate = {
      method: 'PUT',
      api: '/k/v1/records.json',
      payload: {
        app: kintone.app.getId(),
        records: existRecords.map(record => {
          const currentNotionId = record.notion_id.value;
          const targetNotionData = notionData.results.find(r => r.id === currentNotionId);
          if (!targetNotionData) {
            return {
              id: record.$id.value,
              record: record,
            };
          }
          return {
            id: record.$id.value,
            record: {
              商品名: {
                value: targetNotionData.properties.商品名.title.map(t => t.plain_text).join()
              },
              販売ステータス: {
                value: targetNotionData.properties.販売ステータス.select?.name,
              },
              金額: {
                value: targetNotionData.properties.金額.number,
              },
              詳細: {
                value: targetNotionData.properties.詳細.rich_text.map(t => t.plain_text).join(),
              }
            }
          };
        })
      }
    };

    // 取得したkintoneのレコードに存在しない分のNotionのデータは、新しくkintoneにもレコードを作成する
    // kintoneに存在しないNotionのデータを洗い出したうえでレコード作成のためのパラメータを作る
    const notExistNotionData = notionData.results.filter(r => !existRecords.some(record => record.notion_id.value === r.id));
    const paramsForCreate = {
      method: 'POST',
      api: '/k/v1/records.json',
      payload: {
        app: kintone.app.getId(),
        records: notExistNotionData.map(targetNotionData => {
          return {
            notion_id: {
              value: targetNotionData.id,
            },
            商品名: {
              value: targetNotionData.properties.商品名.title.map(t => t.plain_text).join()
            },
            販売ステータス: {
              value: targetNotionData.properties.販売ステータス.select?.name,
            },
            金額: {
              value: targetNotionData.properties.金額.number,
            },
            詳細: {
              value: targetNotionData.properties.詳細.rich_text.map(t => t.plain_text).join(),
            }
          };
        }),
      }
    };

    // bulkRequestの実行
    const bulkRequestParams = {requests: [paramsForUpdate, paramsForCreate]};
    await kintone.api(kintone.api.url('/k/v1/bulkRequest.json', true), 'POST', bulkRequestParams);
  };

  // レコード一覧表示時のイベント
  kintone.events.on('app.record.index.show', (event) => {

    // 同期ボタンを設置
    const headerSpace = kintone.app.getHeaderMenuSpaceElement();
    headerSpace.appendChild(syncButton);

    // 同期ボタンを押したときに処理を実行させる
    syncButton.onclick = async () => {
      disableSyncButton();
      await syncNotionToKintone();
      location.reload();
    };

    return event;
  });

})();

6. 動作確認

画像のように同期ボタンが表示されます。
クリックすることで、Notionとkintoneのデータ同期が開始されます。

コードの解説

上記コードにおけるポイントをいくつか解説します。

kintone UI Componentの利用

今回のサンプルでは同期ボタンにkintone UI Componentを利用しています。

19
20
21
22
23
24
25
26
27
28
29
30
31
  // kintone ui componentのバージョン指定
  const Kuc = Kucs['1.16.0'];

  // 同期ボタンの設定
  const syncButton = new Kuc.Button({
    text: 'Notionデータ同期',
    type: 'submit'
  });
  // 同期が始まったときにボタンを非活性化するための関数
  const disableSyncButton = () => {
    syncButton.text = 'loading...';
    syncButton.disabled = true;
  };
128
129
130
131
132
133
134
135
136
137
138
139
    // 中略

    // 同期ボタンを設置
    const headerSpace = kintone.app.getHeaderMenuSpaceElement();
    headerSpace.appendChild(syncButton);

    // 同期ボタンを押したときに処理を実行させる
    syncButton.onclick = async () => {
      disableSyncButton();
      await syncNotionToKintone();
      location.reload();
    };

kintone UI Componentの詳細については、 kintone UI Component v1 を確認してください。
この記事では説明を割愛します。

Notion APIとの通信

Notion APIはkintone外部のAPIなので、通信するためにkintone.proxy()を利用しています。
kintone.proxyの使い方や詳細については 外部のAPIを実行する を参照してください。

33
34
35
36
37
38
// Notionのデータを取得する関数
const fetchNotionData = async () => {
  const response = await kintone.proxy(NOTION_API_URL, 'POST', NOTION_API_HEADERS, {});
  const notionData = JSON.parse(response[0]);
  return notionData;
};

また、Notion APIからは、どのようなレスポンスが返ってくるかなどはNotion APIのリファレンス Query a database (External link) を参照してください。

Notion側との同期

syncNotionToKintone()という関数を用意し、その中でUPSERT処理を行うようにしています。
UPSERTとは、レコードに値がなかったら登録、あったら更新するというような挙動をさします。
それを実現するために、レコード操作を一括処理できるBulkRequestを行っています。
Notion APIからは、データベースの各レコードにIDが付与されてきます。
それをキーとして、kintone側にもあるなら更新、なければ登録、というような処理にしています。

 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 取得したNotionのデータとkintoneのデータを元にBulkRequestでデータの作成と更新を行う。
// 更新のためのパラメータの定義
const paramsForUpdate = {
  method: 'PUT',
  api: '/k/v1/records.json',
  payload: {
    app: kintone.app.getId(),
    records: existRecords.map(record => {
      const currentNotionId = record.notion_id.value;
      const targetNotionData = notionData.results.find(r => r.id === currentNotionId);
      if (!targetNotionData) {
        return {
          id: record.$id.value,
          record: record,
        };
      }
      return {
        id: record.$id.value,
        record: {
          商品名: {
            value: targetNotionData.properties.商品名.title.map(t => t.plain_text).join()
          },
          販売ステータス: {
            value: targetNotionData.properties.販売ステータス.select?.name,
          },
          金額: {
            value: targetNotionData.properties.金額.number,
          },
          詳細: {
            value: targetNotionData.properties.詳細.rich_text.map(t => t.plain_text).join(),
          }
        }
      };
    })
  }
};

// 取得したkintoneのレコードに存在しない分のNotionのデータは、新しくkintoneにもレコードを作成する
// kintoneに存在しないNotionのデータを洗い出したうえでレコード作成のためのパラメータを作る
const notExistNotionData = notionData.results.filter(r => !existRecords.some(record => record.notion_id.value === r.id));
const paramsForCreate = {
  method: 'POST',
  api: '/k/v1/records.json',
  payload: {
    app: kintone.app.getId(),
    records: notExistNotionData.map(targetNotionData => {
      return {
        notion_id: {
          value: targetNotionData.id,
        },
        商品名: {
          value: targetNotionData.properties.商品名.title.map(t => t.plain_text).join()
        },
        販売ステータス: {
          value: targetNotionData.properties.販売ステータス.select?.name,
        },
        金額: {
          value: targetNotionData.properties.金額.number,
        },
        詳細: {
          value: targetNotionData.properties.詳細.rich_text.map(t => t.plain_text).join(),
        }
      };
    }),
  }
};

// bulkRequestの実行
const bulkRequestParams = {requests: [paramsForUpdate, paramsForCreate]};
await kintone.api(kintone.api.url('/k/v1/bulkRequest.json', true), 'POST', bulkRequestParams);

BulkRequestについては 複数アプリのレコード操作を一括処理する を参照してください。

kintoneイベントハンドラーの定義

今回はレコード一覧画面に同期ボタンを表示していますので、 レコード一覧画面を表示した後のイベント を利用します。

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
  // レコード一覧表示時のイベント
  kintone.events.on('app.record.index.show', (event) => {

    // 同期ボタンを設置
    const headerSpace = kintone.app.getHeaderMenuSpaceElement();
    headerSpace.appendChild(syncButton);

    // 同期ボタンを押したときに処理を実行させる
    syncButton.onclick = async () => {
      disableSyncButton();
      await syncNotionToKintone();
      location.reload();
    };

    return event;
  });

注意事項

  • 提示しているコードはあくまで連携のサンプルなためNotion側、kintone側ともに100件までのレコード数を想定しており、それ以上のレコード数は想定しておりません。
    • 今回利用しているNotion APIも一度に取得できるのは執筆時点で100件までとなっています。

まとめ

kintoneとNotionの連携は、ビジネスの効率化に大きく貢献します。
kintoneのJavaScriptカスタマイズ機能を活用することで、さまざまな外部サービスとの連携が可能になり、業務の幅が広がります。
この記事を参考に、ぜひkintoneとNotionの連携に挑戦してみてください!

information

このTipsは、2024年2月版kintoneで動作を確認しています。