Googleカレンダーの予定が更新されたら自動でNotionのスケジュールDBに反映する方法

当ブログがオススメする

業務自動化サービスランキング

なお、業務効率化や工数削減を目指しているものの、どの業務から自動化すべきか、どのツールや手段が自社に合っているのかがあいまいな方は、「業務自動化プラン診断」をお試しください。

かかる時間は1分ほど。4つの質問に答えるだけで、あなたに最適な自動化対象業務や、推奨される自動化手段を診断してもらえます。

効率よく自社に適した業務効率化を知りたい方は、ぜひ一度お試しください。

\ 4つの質問に答えるだけ /

目次

Googleカレンダーの予定が更新されたら自動でNotionのスケジュールDBに反映する方法

【この記事を読む前に】

  • マウスでクリックができれば大丈夫です
  • プログラミングの知識は一切不要で、コピペで動作します
  • 画面の写真を見ながら進められます

はじめに

「Googleカレンダーで予定を管理しているけど、Notionでもタスク管理したい…」 「カレンダーの予定をいちいちNotionに転記するのが面倒…」 「チームの予定をNotionで一元管理したいけど、みんなGoogleカレンダーを使っている…」

こんな悩みを抱えている方は多いのではないでしょうか。

本記事では、Googleカレンダーの予定が追加・変更されたときに、自動でNotionのスケジュールデータベースに反映する方法を解説します。Google Apps Script(GAS)を使いますが、コードはすべてコピー&ペーストでOK。手順通りに進めれば誰でも設定できます。

こんなシーンでの活用に適しています

  • Googleカレンダーで予定管理しつつ、Notionでプロジェクト管理したい
  • チームメンバーの予定をNotionで可視化したい
  • 会議の予定と議事録をNotionで一元管理したい
  • カレンダーの予定からNotionのタスクを自動生成したい
  • 外部の人との予定共有はGoogleカレンダー、内部管理はNotionで行いたい

Googleカレンダー × GAS × Notion連携の概要

スクロールできます
メリットリアルタイム同期:カレンダーの変更が即座にNotionに反映
二重管理不要:Googleカレンダーに入力するだけでNotionにも自動登録
完全無料:追加費用なしで利用可能
重複防止機能付き:同じ予定が二重に登録されない仕組み
カスタマイズ自在:必要な情報だけをNotionに転記可能
デメリットNotion APIの設定が必要:初回設定時に英語画面での作業あり
一方向の同期:Googleカレンダー→Notionの一方向のみ(双方向は複雑だが可能)
大量の予定は処理時間がかかる:初回同期時は時間制限に注意
繰り返し予定の扱いが複雑:個別の予定として処理される
難易度・面倒さ難易度:★★☆☆☆〜★★★☆☆(コピペ中心だが設定項目が多め)
所要時間:45〜60分
必要スキル:マウス操作とコピー&ペーストができれば大丈夫
特徴カレンダーが更新されるたびに自動実行
予定のタイトル、日時、場所、説明をNotionに転記
タイトルと日時で重複チェック(同じ予定は上書き更新)
今日から30日先までの予定を同期
価格Google Apps Script:完全無料
Notion:無料プラン〜(個人利用なら無料で十分)
Googleカレンダー:完全無料
合計:完全無料で運用可能

今回の事例は、同じ予定が何度もNotionに重複投稿されないような工夫が施されてあります!

用意するもの

  1. Googleアカウント(Googleカレンダー用)
  2. Notionアカウント(無料プランでOK)
  3. 同期したいGoogleカレンダー

設定手順

🔹Step 1:Notionでスケジュールデータベースを作成

1-1. Notionにログインし、新しいページを作成

1-2. 予定(仮)と任意のタイトルを入力して画面下の「データベース」を選択

1-3. 「AIで構築」を選択

手作業でテーブルを作るのが一般的ですが、今回は項目も決まっているため、AIの力を借りた時短の方法で進めましょう!

1-4. 入力フィールドに以下の箇条書きテキストをコピペして送信:

  • タイトル(Title):デフォルトで存在
  • 日付(Date):プロパティタイプを「Date」に設定
  • 開始時刻(Text):例「10:00」
  • 終了時刻(Text):例「11:30」
  • 内容(Text):予定の詳細内容
  • 場所(Text):会議室やURLなど
  • カレンダー登録済み(Checkbox):同期確認用

AIが項目を作ってくれるので、そのまま画面を進んでいく

そのままの状態で「完了」を選択

Notionの中に予定表が出来上がりました。ここにGoogleカレンダーの予定が、自動的にどんどん追加・更新されていくことがゴールです。

🔹Step 2:Notion APIの設定

2-1. 画面右上の「・・・」→「接続」→「インテグレーションを開発」をクリック

2-2. 「New integration」ボタンをクリック

2-3. 以下の情報を入力し「Save」をクリック:

  • Integration Name:「日報カレンダー連携」(任意の名前)
  • Associated workspace:使用するワークスペースを選択

2-4. 表示される「Internal Integration Token」をコピーしてメモ帳に保存 (「show」をクリックすると表示されます)

必要な3つのテキスト情報の内、1つ目のテキストです!あと2つあります!

3-1. Step2 に引き続き、ページ内の「Access」というタブを選択し、「+ Select Pages」をクリック

3-2. 検索欄に先ほどAIが作成してくれたページのタイトルで検索→選択→「Update access」をクリック

以下のように追加されていれば成功です

3-3. Step 1で作成した日報データベースのページを開き、URLから「データベースID」を取得:

  • URL例:https://www.notion.so/xxxxx?v=yyyyy
  • xxxxxの部分(32文字)がデータベースID
  • これもメモ帳に保存

あとで使う3つのテキストのうち、2つ目のテキストです。あと1個あります。

🔹Step 4:Googleカレンダーの準備

4-1. Googleカレンダーにアクセス

4-2. 左側の「他のカレンダー」の「+」をクリック

4-3. 「新しいカレンダーを作成」を選択

4-4. カレンダー名を「Notion日報」などに設定して作成

4-5. 作成したカレンダーが左側に追加されるので選択

4-6. 「カレンダーID」をコピーしてメモ帳に保存 (例:xxxxx@group.calendar.google.com

このカレンダーIDをもって、必要な3つのテキスト情報がそろいました!

🔹Step 5:Google Apps Script(GAS)の設定

5-1. Google Apps Scriptにアクセス

5-2. 「新しいプロジェクト」をクリック

5-3. プロジェクト名を「Notion予定連携」に変更(画面上部)

5-4. 以下のコードをすべて削除して、新しいコードをコピー&ペースト:

カスタマイズされたい方の為に、各コードの説明や考え方などの記述も含めました。そのため、コード全体は長くなっていますが、問題なく処理できますので安心してコピペしてください。

// ===== 設定項目(環境に応じて変更)=====
const NOTION_API_KEY = 'STEP2 で取得したNotionのInternal Integration Token'; // Notion APIキー
const DATABASE_ID = 'STEP3 で取得したURLの中のデータベースID'; // NotionデータベースID
const CALENDAR_ID = 'STEP4 で作成したGoogleカレンダーID'; // GoogleカレンダーID

// ===== メイン処理(変更不要)=====
function onCalendarChange() {
  // 今日から未来30日分の予定を取得
  const now = new Date();
  // 今日の0時0分に設定
  const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
  const endTime = new Date(now.getTime() + (30 * 24 * 60 * 60 * 1000)); // 30日後
  
  // Googleカレンダーから予定を取得
  const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
  const events = calendar.getEvents(startTime, endTime);
  
  // Notionの既存データを取得
  const notionEvents = getNotionEvents();
  
  // Googleカレンダーの予定を処理済みマップ
  const processedGoogleEvents = new Map();
  
  // 新規・更新の処理
  events.forEach(event => {
    const eventKey = createEventKey(event);
    processedGoogleEvents.set(eventKey, true);
    
    const notionEvent = findNotionEventByTitleAndDate(notionEvents, event);
    
    if (notionEvent) {
      // 更新が必要かチェック
      if (isEventUpdated(event, notionEvent)) {
        updateNotionEvent(notionEvent.id, event);
      }
    } else {
      // 新規作成
      createNotionEvent(event);
    }
  });
  
  // 削除処理は行わない(コメントアウト)
  // notionEvents.forEach(notionEvent => {
  //   const notionKey = createNotionEventKey(notionEvent);
  //   if (notionKey && !processedGoogleEvents.has(notionKey)) {
  //     deleteNotionEvent(notionEvent.id);
  //   }
  // });
}

// イベントのキー(タイトル+日時)を作成
function createEventKey(event) {
  const title = event.getTitle() || '無題の予定';
  const startDate = formatDate(event.getStartTime());
  const startTime = formatTime(event.getStartTime());
  return `${title}_${startDate}_${startTime}`;
}

// Notionイベントのキーを作成
function createNotionEventKey(notionEvent) {
  const title = notionEvent.properties['タイトル']?.title[0]?.text.content || '無題の予定';
  const date = notionEvent.properties['日付']?.date?.start || '';
  const startTime = notionEvent.properties['開始時刻']?.rich_text[0]?.text.content || '';
  
  if (!date || !startTime) return null;
  
  return `${title}_${date}_${startTime}`;
}

// タイトルと日時でNotionイベントを検索
function findNotionEventByTitleAndDate(notionEvents, googleEvent) {
  const googleTitle = googleEvent.getTitle() || '無題の予定';
  const googleDate = formatDate(googleEvent.getStartTime());
  const googleStartTime = formatTime(googleEvent.getStartTime());
  
  return notionEvents.find(notionEvent => {
    const notionTitle = notionEvent.properties['タイトル']?.title[0]?.text.content || '無題の予定';
    const notionDate = notionEvent.properties['日付']?.date?.start || '';
    const notionStartTime = notionEvent.properties['開始時刻']?.rich_text[0]?.text.content || '';
    
    return notionTitle === googleTitle && 
           notionDate === googleDate && 
           notionStartTime === googleStartTime;
  });
}

// Notionから既存の予定を取得
function getNotionEvents() {
  const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
  
  const options = {
    method: 'post',
    headers: {
      'Authorization': `Bearer ${NOTION_API_KEY}`,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    payload: JSON.stringify({
      page_size: 100
    })
  };
  
  try {
    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());
    return data.results;
  } catch (e) {
    console.error('Notion取得エラー:', e);
    return [];
  }
}

// 予定が更新されたかチェック
function isEventUpdated(googleEvent, notionEvent) {
  // 終了時刻をチェック
  const googleEndTime = formatTime(googleEvent.getEndTime());
  const notionEndTime = notionEvent.properties['終了時刻']?.rich_text[0]?.text.content || '';
  
  // 内容をチェック
  const googleContent = formatEventContent(googleEvent);
  const notionContent = notionEvent.properties['内容']?.rich_text[0]?.text.content || '';
  
  return googleEndTime !== notionEndTime || googleContent !== notionContent;
}

// Notionに新規予定を作成
function createNotionEvent(event) {
  const url = 'https://api.notion.com/v1/pages';
  
  // 日付と時刻を取得
  const startDate = event.getStartTime();
  const endDate = event.getEndTime();
  
  const properties = {
    'タイトル': {
      title: [{
        text: { content: event.getTitle() || '無題の予定' }
      }]
    },
    '日付': {
      date: {
        start: formatDate(startDate)
      }
    },
    '開始時刻': {
      rich_text: [{
        text: { content: formatTime(startDate) }
      }]
    },
    '終了時刻': {
      rich_text: [{
        text: { content: formatTime(endDate) }
      }]
    },
    '内容': {
      rich_text: [{
        text: { content: formatEventContent(event) }
      }]
    },
    '場所': {
      rich_text: [{
        text: { content: event.getLocation() || '' }
      }]
    },
    '場所': {
      rich_text: [{
        text: { content: event.getLocation() || '' }
      }]
    },
    'カレンダー登録済み': {
      checkbox: true
    }
  };
  
  const options = {
    method: 'post',
    headers: {
      'Authorization': `Bearer ${NOTION_API_KEY}`,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    payload: JSON.stringify({
      parent: { database_id: DATABASE_ID },
      properties: properties
    })
  };
  
  try {
    UrlFetchApp.fetch(url, options);
    console.log(`作成: ${event.getTitle()}`);
  } catch (e) {
    console.error('作成エラー:', e);
  }
}

// Notionの予定を更新
function updateNotionEvent(pageId, event) {
  const url = `https://api.notion.com/v1/pages/${pageId}`;
  
  const startDate = event.getStartTime();
  const endDate = event.getEndTime();
  
  const properties = {
    'タイトル': {
      title: [{
        text: { content: event.getTitle() || '無題の予定' }
      }]
    },
    '日付': {
      date: {
        start: formatDate(startDate)
      }
    },
    '開始時刻': {
      rich_text: [{
        text: { content: formatTime(startDate) }
      }]
    },
    '終了時刻': {
      rich_text: [{
        text: { content: formatTime(endDate) }
      }]
    },
    '内容': {
      rich_text: [{
        text: { content: formatEventContent(event) }
      }]
    }
  };
  
  const options = {
    method: 'patch',
    headers: {
      'Authorization': `Bearer ${NOTION_API_KEY}`,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    payload: JSON.stringify({ properties: properties })
  };
  
  try {
    UrlFetchApp.fetch(url, options);
    console.log(`更新: ${event.getTitle()}`);
  } catch (e) {
    console.error('更新エラー:', e);
  }
}

// Notionから予定を削除
function deleteNotionEvent(pageId) {
  const url = `https://api.notion.com/v1/pages/${pageId}`;
  
  const options = {
    method: 'patch',
    headers: {
      'Authorization': `Bearer ${NOTION_API_KEY}`,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28'
    },
    payload: JSON.stringify({ archived: true })
  };
  
  try {
    UrlFetchApp.fetch(url, options);
    console.log('削除完了');
  } catch (e) {
    console.error('削除エラー:', e);
  }
}

// 日付をyyyy-MM-dd形式にフォーマット
function formatDate(date) {
  return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy-MM-dd');
}

// 日付をISO形式(日時含む)にフォーマット
function formatDateTimeISO(date) {
  // NotionのAPIはISO 8601形式を期待
  // 例: "2025-01-20T14:00:00+09:00"
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');
  
  return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}+09:00`;
}

// 時刻をHH:mm形式にフォーマット
function formatTime(date) {
  return Utilities.formatDate(date, 'Asia/Tokyo', 'HH:mm');
}

// イベントの内容を整形(場所は別プロパティに移動)
function formatEventContent(event) {
  let content = '';
  
  // 説明
  const description = event.getDescription();
  if (description) {
    content += description + '\n\n';
  }
  
  // 参加者(オプション)
  try {
    const guests = event.getGuestList();
    if (guests && guests.length > 0) {
      content += '👥 参加者: ' + guests.map(g => g.getEmail()).join(', ') + '\n';
    }
  } catch (e) {
    // ゲストリストへのアクセス権限がない場合は無視
  }
  
  return content.trim();
}

// 初回の全件同期用(手動実行)
function initialSync() {
  onCalendarChange();
  console.log('初回同期完了');
}

5-5. コード内の設定項目を自分の情報に変更:

ここで、先ほど控えた3つのテキスト情報が登場します。

// ===== 設定項目(環境に応じて変更)=====
const NOTION_API_KEY = 'STEP2 で取得したNotionのInternal Integration Token'; // Notion APIキー
const DATABASE_ID = 'STEP3 で取得したURLの中のデータベースID'; // NotionデータベースID
const CALENDAR_ID = 'STEP4 で作成したGoogleカレンダーID'; // GoogleカレンダーID
  • NOTION_API_KEY:Step 2-4でコピーしたトークン
  • DATABASE_ID:Step 3-3でコピーしたID
  • CALENDAR_ID:Step 4-6でコピーしたID

5-6. 「保存」ボタンをクリック(Ctrl+S または Cmd+S)

🔹Step 6:テスト実行

6-1. Googleカレンダーを開いて、お試し予定を登録

6-2. 「実行」ボタンをクリック

6-3. 初回実行時は権限の許可が必要:

  • 「権限を確認」→「詳細」→「安全ではないページに移動」→「許可」

6-4. 実行ログを確認(画面下部)

6-5. Notionを開いて、予定が反映されているか確認

成功です!

🔹Step 7:自動実行の設定(Googleカレンダーが更新されるたびに)

7-1. GASエディタの左側メニューから「トリガー」(時計アイコン)をクリック

7-2. 右下の「トリガーを追加」ボタンをクリック

7-3. 以下の設定を行う:

  • 実行する関数:onCalendarChange
  • イベントのソース:カレンダーから
  • カレンダーの詳細を入力:カレンダー更新済み
  • カレンダーのオーナーのメールアドレス:Step 4-6でコピーしたID

7-4. 「保存」をクリック

つまずきやすいポイント

つまずきやすいポイント

(1) 「カレンダーの更新時」トリガーが選択できない

原因:カレンダーIDがメインカレンダー以外

解決策:トリガー設定では自分のGmailアドレスを使用。コード内のCALENDAR_IDは任意のカレンダーでOK

(2) Notion APIトークンが無効

原因:トークンのコピーミス、または権限不足

解決策:トークンを再度コピーし、Content Capabilitiesがすべて有効か確認

(3) 予定が重複して登録される

原因:タイトルや時刻が微妙に異なる

解決策:このコードは同じタイトル・日付・開始時刻の予定は更新されるため、重複は防げます

(4) 時刻が正しく表示されない

原因:時刻形式の問題

解決策:Notionの「開始時刻」「終了時刻」プロパティがText型になっているか確認

(5) 大量の予定で実行時間エラー

原因:GASの実行時間制限(6分)を超過

解決策:初回同期は期間を短くして実行するか、複数回に分けて実行

(6) 予定が削除されない

原因:このコードは安全のため削除処理を無効化しています

解決策:Notion側の予定を手動にて削除してください。

よくある質問

複数のカレンダーを同期できますか?

可能です。カレンダーごとに別のGASプロジェクトを作成するか、コードを改造して複数カレンダー対応にできます。

NotionからGoogleカレンダーへの逆同期もできますか?

別記事にて可能にした事例を紹介しております。

繰り返し予定はどう扱われますか?

個別の予定として登録されます。元の繰り返し設定は保持されません。

終日予定の時刻はどうなりますか?

開始時刻は「00:00」、終了時刻は「23:59」として登録されます。

過去の予定も同期したいのですが?

コード内のstartTimeを変更すれば可能です。ただし、過去の予定が多いと処理時間が長くなります。

会議のURLを自動抽出できますか?

内容欄に含まれます。説明欄のZoomやGoogle MeetのURLも一緒に転記されます。

プロパティ名を日本語以外にしても大丈夫?

コード内のプロパティ名を合わせて変更すれば、英語でも日本語でも問題ありません。

なかなかうまくいかないときは?

いくらコピー&ペーストで済むとはいえ、初めての方には設定箇所が多く「諦めるしかない…」と挫折するきっかけになりかねません。

「社内で自動化したは良いものの、自分がやめたら引き継ぐ人がいない…」 「一つ自動化したら、他部署からも自動化を依頼されて自分の仕事が進まない…」 と不安な方もいますよね。

そこで、おすすめしたいのが「ジドウカ」です。

ジドウカは、これまで合計800タスク以上の業務の自動化をしてきた実績のある法人専用の自動化サービスです。

業務の一部を“タスク単位”で自動化し、【月額1万円から】安定運用できるサブスクリプション型のサービスです。 タスクを外注するため手離れもよく、技術のことが分からなくても、「こういう作業をラクにしたい」と伝えるだけで自動化することが可能です。

「わたしが自動化したい内容って自動化できるんだろうか⋯」という方には、少額・短期間での「お試し開発」があるので、お気軽にご活用ください。

まとめ

「kintoneのデータを毎回手作業で出力するのが面倒…」そんな悩みは、この自動連携で一発解決!

この記事のステップ通りに進めれば、プログラミングが苦手な方でも“今すぐ・無料で”kintoneデータをGoogleスプレッドシートに自動同期できます。

もしうまくいかない場合や「社内での運用が不安」という方は、
プロの自動化サポートサービスも活用しながら、
ラクに・賢くkintoneのデータ管理をアップデートしていきましょう!

自社で自動化する際によくある失敗ランキング

自動化ツールを導入すれば業務が自動化され、効率が劇的に向上する——。 そう考えている企業は多いですが、実際には多くの企業が自動化ツールを十分に活用できていません。 有名な自動化ツールであるRPAを例にあげても、導入した企業の実態は理想と大きく異なった結果となっています。

RPA導入企業の約60%が「あまり活用できていない」と回答

多くの企業がRPAを導入しているにも関わらず、約60%の企業が「期待したほど活用できていない」と感じているというデータです。これは、「導入しただけでは、業務改善につながらない」という現実を示しています。

RPAの効果を実感できない要因は『RPA開発が進んでいない』

「RPAの効果を実感できていない」と回答した企業の多くが、その理由として「RPA開発が進んでいない」「他業務・他部署への展開ができていない」ことを挙げています。   「〇〇業務はツールを導入すればすぐにラクになるはず」と思って、自社で自動化に挑戦した方も多いのではないでしょうか?しかし私たちには、こんな“あるあるの失敗談”がよく届きます。

🥇 第1位:初期設定でつまずき、結局断念…

思っていたより設定が複雑で、ツールの仕様を理解する前に挫折してしまうケース。 特にRPAツールやノーコードツールは、「慣れるまでが大変」という声が多いです。

🥈 第2位:担当者が辞めて、運用不能に…

担当者が社内で唯一のキーマンだった場合、その人がいなくなると全て止まってしまうという問題が発生します。 しかも、「誰も中身が分からないから触れない」という状況になりがち。

🥉 第3位:不具合や修正対応に時間がかかる…

自動化が止まったとき、「誰が見ればいいのか分からない」「ベンダーに相談するのも手間」という理由で、対応が後手に。 気づけばその対応に何時間も時間を取られ、本業に集中できなくなってしまうことも…。   実はよくある…自社で自動化に挑戦したときの“落とし穴” 共通するのは「すべて自社で完結しようとした」こと。 このようなケースに共通するのが、「最初から最後まで、すべて自社で完結しようとした」点です。 最初はうまくいっても、長期的に安定した自動化運用には、継続的な保守や柔軟な調整が不可欠です。

安定的に自動化したいならジドウカがおすすめ

 「ツールを入れただけ」では業務はラクになりません 「業務をラクにする自動化」のためには、設定・運用・トラブル対応まで含めてプロに任せるのが最も確実です。

ジドウカとは?

業務の一部を“タスク単位”で自動化し、月額で安定運用できるサブスクリプション型のサービスです。 技術のことが分からなくても、「こういう作業をラクにしたい」と伝えるだけでOK。

ジドウカでできること(業務例)

・定期レポートの自動作成とSlack送信 ・受注データのExcel整形とkintone登録 ・競合サイトの自動モニタリングとアラート通知 ・営業リストの自動生成とCRMへの投入 など

ジドウカが選ばれる理由

弊社の自動化サービス「ジドウカ」は、1社1社、1タスク1タスクに合わせて完全オーダーメイドで開発するサービス担っています。
  • ヒアリングから開発・運用まで丸ごとサポート
  • トラブル発生時には即時対応
  • 月額料金内で自由に修正をご依頼可能
実際に多くのお客様から「自社での自動化運用に失敗した後に依頼してよかった」と高評価をいただいています。
目次