結論:Notionを「原本(マスタ)」、スプレッドシートを「集計・計算専用のビュー」と役割を分け、GASでNotion APIから定期的にデータを自動同期する仕組みを作りました。これにより、同じ数字を2箇所に手入力する作業がなくなりました。
この記事でわかること
- Notion スプレッドシート 連携で二重管理が起きる典型的な原因
- 「どちらをマスタにするか」の設計方針
- GAS+Notion APIで自動同期する実装コード
- 同期後の運用ルール
前回の記事でGASによる新着案件通知を作りましたが、その後Notionの案件管理DBを育てていく中で、金額や案件数をスプレッドシート側にも手入力していることに気づきました。Notionとスプレッドシートを連携させずに併用していると、この二重管理はほぼ必ず起きます。
二重管理が発生していた具体的な業務
| 項目 | 二重管理していた頃 |
|---|---|
| 案件情報の入力先 | Notion + スプレッドシート(両方) |
| 更新のたびに必要な作業 | 2箇所を手動で編集 |
| 月あたりの金額ズレ発生回数 | 2〜3回 |
| ズレに気づくタイミング | 月末の集計時(手遅れ) |
片方を更新してもう片方を更新し忘れると、集計結果が実態と合わなくなります。これは「ツールが2つあるから」ではなく、「どちらが正の情報か決めていなかったから」起きる問題でした。
統合の設計方針(どちらをマスタにするか)
今回決めたルールはシンプルです。
- Notion = マスタ(原本)。案件情報の入力・編集はすべてNotionで行う
- スプレッドシート = ビュー(集計・計算専用)。直接編集はせず、Notionのデータを自動反映したものを見る・計算するためだけに使う
つまり「どちらにも自由に入力できる」状態をやめて、入力する場所を1つに絞り、もう片方は自動生成される「読み取り専用の写し」にする、という方針です。Notionをマスタにする場合、案件管理DBを育てるためにNotionの有料プランでデータベース機能を強化しておくと、この後の運用が安定します。

GAS/Notion APIでの連携実装
NotionのIntegration(APIキー)を発行し、案件管理DBをそのIntegrationと共有した上で、GASから定期的にデータベースをクエリしてスプレッドシートに書き出します。
const NOTION_TOKEN = 'ここにNotion Integrationのシークレットを入れる';
const DATABASE_ID = 'ここに案件管理DBのIDを入れる';
const SHEET_NAME = '案件一覧(同期)';
function syncNotionToSheet() {
const results = queryNotionDatabase_();
const sheet = getOrCreateSheet_();
sheet.clearContents();
sheet.appendRow(['案件名', 'ステータス', '金額', '締切', 'Notionリンク']);
results.forEach(page => {
const props = page.properties;
const name = props['案件名']?.title?.[0]?.plain_text || '';
const status = props['ステータス']?.status?.name || '';
const amount = props['金額']?.number ?? '';
const deadline = props['締切']?.date?.start || '';
const url = page.url;
sheet.appendRow([name, status, amount, deadline, url]);
});
}
function queryNotionDatabase_() {
const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
const response = UrlFetchApp.fetch(url, {
method: 'post',
headers: {
'Authorization': `Bearer ${NOTION_TOKEN}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json'
},
payload: JSON.stringify({})
});
const json = JSON.parse(response.getContentText());
return json.results;
}
function getOrCreateSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) sheet = ss.insertSheet(SHEET_NAME);
return sheet;
}トリガーは「時間主導型・1日1回」で十分です。頻繁に更新するなら30分〜1時間おきでも構いません。
つまずいたポイント:Notionのステータスプロパティは、DBの作り方によってstatus型とselect型のどちらかになっており、取得の書き方が変わります。最初select型のつもりでprops['ステータス']?.select?.nameと書いていて値が取れず、Claude Codeで実際のAPIレスポンスをそのまま出力させて初めてstatus型だったと気づきました。自分のDBがどちらの型か、実際のレスポンスを確認してから書くのが確実です。
統合後の運用ルール
- スプレッドシートの「案件一覧(同期)」シートは直接編集しない(次の同期で上書きされます)
- 集計・グラフ・請求書作成は、この同期シートを参照する別シートで行う
- 案件情報の追加・変更は必ずNotion側で行う
導入前後の比較
| 項目 | 導入前 | 導入後 |
|---|---|---|
| 入力箇所 | 2箇所(Notion+シート) | 1箇所(Notionのみ) |
| 月あたりの金額ズレ | 2〜3回 | 0回(運用2週間) |
| 同期作業 | 手動 | 自動(1日1回) |
よくある質問
Notion APIの無料枠でも使えますか?
Notion APIの利用自体は無料です。今回の同期処理程度のリクエスト数であれば、料金は発生しません。
スプレッドシート側からNotionに書き戻すことはできますか?
技術的には可能ですが、今回はあえて一方向(Notion→シート)に限定しています。双方向にすると「どちらが正か」が再び曖昧になり、二重管理の問題に逆戻りするためです。
Notionのデータベースが複数ある場合はどうすればいいですか?
DATABASE_IDと書き込み先シートをペアにして、同期処理をデータベースの数だけ繰り返す形で対応できます。
次回は、この仕組みをベースに、作業時間の自動集計と時給換算をスプレッドシート側で作っていきます。
