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

目次

はじめに

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

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

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

kintone と Notion の連携方法

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 の開発においても慎重に扱ってください。

  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 で動作を確認しています。