今回は、kintoneとNotionをAPIで連携する方法を紹介します。
kintoneは柔軟性とカスタマイズ性が高く、他のサービスとの連携も容易です。
この記事では、kintoneのJavaScriptカスタマイズ機能を活用して、Notionからデータを取得する方法を解説します。
Notionの特徴と、連携のメリット
固定リンクがコピーされました
Notion
は、ノートテイキングやタスク管理、データベース管理など、多様な機能をもつワークスペースです。
ビジネスの現場で必要とされるさまざまなアプリケーションを簡単に作成、管理できるクラウドサービスのkintoneと、
Notionを連携することで、Notionに蓄積された情報をkintoneのアプリでも活用できます。
これにより、業務の効率化や情報共有のスムーズ化が期待できます。
kintoneとNotionの連携方法
固定リンクがコピーされました
kintoneのJavaScriptカスタマイズ機能を使うと、kintoneのアプリケーションに独自の機能を追加できます。
JavaScriptプログラミングによるkintoneのカスタマイズの概要
たとえば、特定の条件でフィールドの表示を変更したり、外部サービスとのデータ連携ができます。
また、Notionも、外部と連携するためのインターフェースとして、Notion APIを用意しています。
そこで、今回はJavaScriptカスタマイズを利用し、kintone側からNotion APIへリクエストすることによって、NotionのAPIと連携してみます。
今回のカスタマイズ例では、次の流れで行ってみましょう。
- Notionにデータベースを用意する。
- Notionに新しいインテグレーションを作成する。
- 作成したインテグレーションからデータベースへアクセスができるようにする。
- kintoneにアプリを作成する。
- kintoneにJavaScriptカスタマイズを適用する。
今回は例として商品管理アプリを作成し、Notion側にある商品リストをkintone側へ連携するようにしてみます。
1. Notionにデータベースを用意する
固定リンクがコピーされました
Notion側にデータベースを作成しましょう。
データベースの概要、作り方についてはNotionのヘルプページ
データベースの基本
を確認してください。
テーブルのプロパティは次のように設定してください。
プロパティ名 |
プロパティの種類 |
備考 |
商品名 |
タイトル |
|
販売ステータス |
セレクト |
販売中 販売一時停止 販売終了 を項目に設定 |
金額 |
数値 |
数値の形式に円 を指定 |
詳細 |
テキスト |
|
2. Notionに新しいインテグレーションを作成する
固定リンクがコピーされました
kintoneからNotionのデータにアクセスするため、Notion APIを利用します。
そのための設定が必要ですので、
インテグレーション設定画面
を開き、新しいインテグレーションを作成してください。
基本情報を設定し、コンテンツの読み取り権限を付与してください。
最後にJavaScriptカスタマイズへ利用するため内部インテグレーションシークレット
をコピーして控えておいてください。
インテグレーションの詳細については、ヘルプページ
Notion APIを使用したインテグレーションの作成
を参照ください。
3. 作成したインテグレーションからデータベースへアクセスができるようにする
固定リンクがコピーされました
インテグレーションは作成しても、最初はワークスペース内のどのコンテンツにもアクセスできません。
そのため、今回利用するデータベースで設定する必要があります。
1. Notionにデータベースを用意する
で作成したデータベースのページ上で下記を行ってください。
- 右上の三点リーダをクリック
- 「接続先」をクリック
- 作成したインテグレーションをクリック
- ダイアログが表示されるので「はい」をクリック
これで作成したインテグレーションからデータベースにアクセスできます。
4. kintoneにアプリを作成する
固定リンクがコピーされました
今回は下記フィールドをもつアプリを用意します。
わかりやすくするためにここではフィールド名とフィールドコードは同一とします。
フィールド名/フィールドコード |
フィールドタイプ |
備考 |
商品名 |
文字列一行 |
|
販売ステータス |
ドロップダウン |
Notion側に合わせて販売中 販売一時停止 販売終了 を項目に設定 |
金額 |
数値 |
桁区切りや単位記号の設定は任意 |
詳細 |
複数行文字列 |
|
notion_id |
文字列一行 |
Notion側との紐づけのために利用する。 値の重複を禁止する をチェック |
5. kintoneにJavaScriptカスタマイズを適用する
固定リンクがコピーされました
アプリ設定の[JavaScript / CSSでカスタマイズ]に下記URLとファイルを追加します。
https://unpkg.com/kintone-ui-component@1.16.0/umd/kuc.min.js
- 今回はボタンの表示にkintone UI Componentを利用しますので、そのためのURLを追加します。
- Notion APIの下記コードを保存し、JavaScriptカスタマイズとして適用してください。
下記はご自身のNotionに合わせて書き換えてください。
[YOUR_DATABASE_ID]
: 今回作成したNotionのデータベースID。
- たとえばブラウザーでデータベースにアクセスした時のURLが
https://www.notion.so/your_domain/f2d589bf26604ebe8cdccc3c6c9d0d6d?v=180a664ab56c41f4bf9049cc5c438284
なら、
f2d589bf26604ebe8cdccc3c6c9d0d6d
がデータベースIDにあたります。
[YOUR_INTEGRATION_SECRET]
: 先ほど取得した内部インテグレーションシークレット
です。
警告
内部インテグレーション等の秘密情報は、絶対に公開しないでください。
秘密情報が漏洩すると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;
});
})();
|
画像のように同期ボタンが表示されます。
クリックすることで、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
を参照してください。
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の連携に挑戦してみてください!