SharePoint SPFx と Microsoft Graph で誕生日ユーザーを月別に自動抽出・表示する方法

SharePoint Online で「今日が誕生日の人をハイライトして表示したい」「9月生まれだけの一覧を SharePoint ページに出したい」と考えたとき、多くの人がまず JSON 列書式で頑張ろうとします。しかし、列書式だけでは「見た目」までが限界です。本記事では、SPFx Web パーツと Microsoft Graph を使って、Entra ID に登録済みの誕生日情報から「月別の誕生日一覧」を自動生成する実践的な方法を解説します。

目次

SharePoint の列書式(JSON)ではなぜ限界が来るのか

まず前提として、SharePoint の 列書式(JSON) ができること・できないことを整理します。

列書式(JSON)でできること

  • 行(アイテム)の値に応じて 背景色やアイコンを切り替える
  • @now や formatDateTime() を使って「今日と同じ日付」のときにメッセージを表示する
  • 誕生日列が Date 型なら、getDate() や getMonth() を使って「日・月」単位の条件分岐を行う

たとえば、「今日が誕生日なら行をハイライトする」列書式は次のように書けます(イメージ)。

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/row-formatting.schema.json",
  "elmType": "div",
  "style": {
    "background-color": "=if((Number(@currentField.getDate()) == Number((new Date()).getDate())) 
      && (Number(@currentField.getMonth()) == Number((new Date()).getMonth())), '#ffe599', '')",
    "padding": "10px",
    "border-radius": "8px"
  },
  "children": [
    {
      "elmType": "span",
      "style": {
        "font-weight": "bold",
        "color": "#cc0000",
        "font-size": "16px"
      },
      "txtContent": "=if((Number(@currentField.getDate()) == Number((new Date()).getDate())) && 
        (Number(@currentField.getMonth()) == Number((new Date()).getMonth())), ' Happy Birthday! ', '')"
    }
  ]
}

このように、列書式は「行ごとの条件に応じて装飾する」用途にはとても便利です。実際、Microsoft Q&A でも同様の JSON で誕生日行をハイライトしようとした相談が紹介されています。

列書式(JSON)で絶対にできないこと

一方で、列書式(JSON)には構造的な限界があります。

  • サイトやテナント全体から 新たにユーザーデータを取得することはできない
  • SharePoint リスト以外(例:Entra ID、Graph)のデータソースにアクセスできない
  • 一覧ビュー全体を 「9月生まれだけに絞り込む」「10月生まれ順に並び替える」といったフィルター・ソートのロジックを持てない

つまり、JSON 列書式はあくまで 「見た目の変更だけ」であり、データの抽出・検索・集計は担当してくれません。

そのため、

  • Entra ID(旧 Azure AD)のユーザー全体から誕生日を取得し、
  • 「9月生まれ」「10月生まれ」ごとの一覧を作成し、
  • SharePoint ページ上でフィルター/検索できるようにする

といった要件は、JSON ではなく SPFx Web パーツで実装するのが現実的な解になります。

列書式と SPFx Web パーツの比較

項目JSON 列書式SPFx Web パーツ
データソース同じリストの列のみMicrosoft Graph, SharePoint リスト, REST API など自由
フィルター・検索ビュー側の機能に依存(JSONでは制御不可)任意の条件で抽出・ソート・ページング可能
誕生日の月別一覧不可(行単位の装飾だけ)クライアントでグルーピングし柔軟に表示
UI カスタマイズ列の表示に限定React で自由な UI(カード・アコーディオン等)
再利用性ビュー単位Web パーツとして任意のページに追加可能
拡張性ほぼなし通知、メール連携、Power Automate 連携など自由

SPFx + Microsoft Graph で「誕生日を月別に自動抽出」する全体像

では、SPFx Web パーツでどのように実現するか、全体像から整理します。

アーキテクチャのイメージ

  • データソース: Microsoft Entra ID (旧 Azure AD) のユーザーオブジェクト
  • 取得手段: Microsoft Graph API
  • プレゼンテーション: SharePoint Framework (SPFx) + React の Web パーツ
  • ロジック:
    • Graph からユーザーと誕生日情報を取得
    • 誕生日を 月別(1〜12月)にグルーピング
    • UI 上で「9月」「10月」などの月を選択して絞り込み

Entra ID 側には、Graph API から参照可能な 誕生日用の属性を持たせておきます。代表的なパターンは次の通りです。

格納先型特徴
user.birthday(ビルトイン)DateTimeOffsetGraph から $select=birthday で取得可能。
ただし「ユーザー一覧 API では一度に取得できず、単一ユーザー取得でのみサポート」といった制約がある。
onPremisesExtensionAttributes.extensionAttribute1 など文字列オンプレ AD から同期される拡張属性。
/users?$select=onPremisesExtensionAttributes で一覧取得しやすい。
スキーマ拡張(extension_…)文字列や Date自社スキーマとして DOB を定義可能。Graph から読み書きしやすいが、事前の設計が必要。

この記事では、「誕生日は Entra ID に登録済み」という前提で、

  • まずは 拡張属性やカスタム属性に誕生日が入っているケース
  • 将来的に user.birthday を使うケース

の両方に対応できる実装パターンを紹介します。

アプローチの選択肢:PnP サンプル or 市販 Web パーツ

ゼロから自作する前に、まずは 既存の Web パーツを流用できないかを検討するのがおすすめです。Microsoft の Q&A でも、まさに今回と同じ「誕生日を月別に抽出したい」という質問に対して、次の 2 つの選択肢が紹介されています。

選択肢 1: PnP サンプル Web パーツ「react-birthdays / react-birthdays-per-month」

Community PnP のサンプルとして、誕生日を表示する SPFx Web パーツが公開されています。代表的なものが 「Birthdays」 と 「Birthdays Per Month」 系のサンプルです。

  • SPFx + React 製(ソースコード公開)
  • 社員の誕生日を 月別にアコーディオン表示できる
  • SharePoint サイトに簡単に追加して動作確認が可能
  • 自社の要件に合わせて Graph からの取得ロジックに差し替えることもできる

サンプルを使う場合は、

  1. GitHub からソースを取得
  2. ローカルで gulp serve して動作確認
  3. 必要に応じて「データ取得部分」を Entra ID から Graph で取るように改修
  4. パッケージ化してテナントに展開

といった流れになります。UI や月別アコーディオンなどはほぼ完成しているので、「実装にかかる時間を極力減らしたい」場面に向いています。

選択肢 2: 市販 Web パーツ(Origami など)

開発リソースを確保しづらい場合は、市販の「誕生日・アニバーサリー Web パーツ」を購入してしまうのも有力です。

たとえば、Origami の 「Anniversaries, New Hires, and Birthdays」系の Web パーツは、Microsoft 365 / Entra ID のユーザープロファイルから自動で情報を読み取り、月別の誕生日・入社記念日などを一覧表示できます。

  • Microsoft 365 / Entra ID からの 自動フィードに対応
  • セキュリティ グループで対象ユーザーを絞り込み可能
  • SharePoint ページに Web パーツとして配置するだけで利用開始
  • サポートやアップデートが提供されるため、運用負荷が低い

ノーコード志向・短期導入・保守もアウトソースしたいのであれば、市販 Web パーツがもっとも現実的です。

選択肢 3: 自作 SPFx Web パーツで月別誕生日一覧を作る

ここからは、PnP サンプルを参考にしつつ、自作の SPFx Web パーツで誕生日を月別に表示する場合の実装ポイントを詳しく見ていきます。

前提条件・開発環境

  • SharePoint Online(Modern サイト)
  • 最新の SPFx(記事執筆時点では 1.18+ 〜 1.20 台が主流)
  • Node.js(SPFx 推奨バージョン)、Yeoman, gulp 等
  • クライアントフレームワーク: React

Web パーツの作成は Yeoman ジェネレーターで行います。

yo @microsoft/sharepoint

# 例
# | What is your solution name?      > birthday-directory
# | Which type of client-side component to create? > WebPart
# | Which framework would you like to use? > React

Microsoft Graph 権限の考え方

Entra ID のユーザー情報(誕生日含む)を読み取るためには、アプリに対して Microsoft Graph の権限を付与する必要があります。代表的な候補は以下です。

権限種類概要
User.Read.All委任 / アプリ組織内の全ユーザーのフルプロファイルを読み取り可能(管理者同意が必須)。
Directory.Read.All委任 / アプリディレクトリ内のより広いデータ(ユーザー、グループ、アプリ等)を読み取り可能。強い権限のため要注意。
User.ReadBasic.All委任氏名・メールなどの基本情報のみ。誕生日などの機微情報は通常ここには含まれない。

原則として「最小権限」で設計しつつ、誕生日属性がどのプロパティに載っているかを確認したうえで、必要最小限の権限を package-solution.json の webApiPermissionRequests に宣言します。

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-package-solution.schema.json",
  "solution": {
    "name": "birthday-directory-client-side-solution",
    "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "version": "1.0.0.0",
    "webApiPermissionRequests": [
      {
        "resource": "Microsoft Graph",
        "scope": "User.Read.All"
      }
    ]
  },
  "paths": {
    "zippedPackage": "solution/birthday-directory.sppkg"
  }
}

この状態でパッケージを App Catalog に展開すると、テナント管理者に「Graph 権限への同意」が要求されます。

Graph から誕生日情報を取得する

実装での最大のポイントは Graph から誕生日をどう取るか です。現在の仕様では、user.birthday プロパティは Graph のユーザーリソースに存在しますが、ユーザー一覧 API(/users)では一度に取得できない(単一ユーザー取得にのみ対応)といった制約があります。

そのため実務では、次のいずれかのパターンを選ぶのがおすすめです。

  • パターン A: 誕生日を onPremisesExtensionAttributes.extensionAttribute1 などの拡張属性に格納し、/users?$select=id,displayName,mail,onPremisesExtensionAttributes で一括取得する
  • パターン B: バッチ処理(Azure Functions, PowerShell など)で Entra ID の誕生日情報を定期的に SharePoint リストへ同期し、Web パーツは SharePoint リストを読む

ここでは、よりリアルタイム性のある パターン A に沿ってサンプルコードを書いてみます。

Graph クライアントの初期化

// web part クラス内(this.context は SPFx のコンテキスト)
const client = await this.context.msGraphClientFactory.getClient("3");

ユーザー一覧の取得と誕生日の取り出し

interface RawUser {
  id: string;
  displayName: string;
  mail?: string;
  birthday?: string; // もし user.birthday を使う場合
  onPremisesExtensionAttributes?: {
    extensionAttribute1?: string;
    // 他の extensionAttribute も必要に応じて
  };
}

interface Person {
  id: string;
  name: string;
  mail?: string;
  birthdayRaw: string;        // "yyyy-MM-dd" または "yyyy-MM-ddThh:mm:ssZ"
  month: number;              // 1-12
  day: number;                // 1-31
}

// "yyyy-MM-dd..." を月・日に分解(Date オブジェクトは使わない)
const parseBirthday = (raw: string): { month: number; day: number } | null => {
  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!m) {
    return null;
  }
  const month = Number(m[2]);
  const day = Number(m[3]);
  if (!month || !day) {
    return null;
  }
  return { month, day };
};

// 例: 拡張属性 extensionAttribute1 に "yyyy-MM-dd" 形式で格納しているケース
const res = await client
  .api("/users")
  .select("id,displayName,mail,onPremisesExtensionAttributes")
  .top(999) // ページングは実環境に合わせて調整
  .get();

const persons: Person[] = (res.value as RawUser[])
  .map((u) => {
    const raw =
      u.onPremisesExtensionAttributes?.extensionAttribute1 ||
      "";

    const parsed = raw ? parseBirthday(raw) : null;
    if (!parsed) {
      return null;
    }

    return {
      id: u.id,
      name: u.displayName,
      mail: u.mail,
      birthdayRaw: raw,
      month: parsed.month,
      day: parsed.day
    } as Person;
  })
  .filter((p): p is Person => p !== null);

ポイントは、タイムゾーンによるズレを避けるために、Date オブジェクトではなく「文字列としてパース」していることです。誕生日は日付だけ分かればよいので、「年・月・日」を数字に分割して扱うほうが安全です。

月別グルーピングとフィルター処理

取得できた persons 配列を、クライアント側で月別にグルーピングします。

// 1〜12 月ごとに配列を持つマップ
const groupByMonth = (people: Person[]): Map<number, Person[]> => {
  const map = new Map<number, Person[]>();
  for (const p of people) {
    const list = map.get(p.month) || [];
    list.push(p);
    map.set(p.month, list);
  }
  return map;
};

const byMonth = groupByMonth(persons);

// 例: 9 月生まれの人だけを取得
const septemberBirthdays = byMonth.get(9) || [];

ここまでできれば、あとは React 側で ドロップダウンやボタンを使って月を選択し、その月の配列をテーブルやカードで表示するだけです。

React UI の実装イメージ

シンプルな UI の例として、「月選択ドロップダウン + 一覧テーブル」を実装するコードを示します。

import * as React from "react";
import { useMemo, useState } from "react";

type Props = {
  persons: Person[];
};

const monthLabels = [
  "",
  "1月",
  "2月",
  "3月",
  "4月",
  "5月",
  "6月",
  "7月",
  "8月",
  "9月",
  "10月",
  "11月",
  "12月"
];

export const BirthdayDirectory: React.FC<Props> = ({ persons }) => {
  const [selectedMonth, setSelectedMonth] = useState<number>(new Date().getMonth() + 1);

  const personsByMonth = useMemo(() => groupByMonth(persons), [persons]);

  const listForMonth = personsByMonth.get(selectedMonth) || [];

  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedMonth(Number(e.target.value));
  };

  return (
    <div className="birthday-directory">
      <div className="birthday-directory__controls">
        <label>
          表示する月:
          <select value={selectedMonth} onChange={handleChange}>
            {monthLabels.map((label, index) =>
              index === 0 ? null : (
                <option key={index} value={index}>
                  {label}
                </option>
              )
            )}
          </select>
        </label>
      </div>

      <table className="birthday-directory__table">
        <thead>
          <tr>
            <th>氏名</th>
            <th>部署 / メール</th>
            <th>誕生日</th>
          </tr>
        </thead>
        <tbody>
          {listForMonth
            .sort((a, b) => a.day - b.day || a.name.localeCompare(b.name, "ja"))
            .map((p) => (
              <tr key={p.id}>
                <td>{p.name}</td>
                <td>{p.mail}</td>
                <td>{`${p.month}月${p.day}日`}</td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  );
};

上記をベースにして、

  • 「今週の誕生日」「来週の誕生日」のクイックフィルター
  • 部門別タブ(部署情報を追加取得してグループ分け)
  • 誕生日が近い順のカード表示

など、UI をどんどんリッチにしていけます。

パフォーマンスとキャッシュ戦略

ユーザー数が数千~数万になると、毎回 Graph から全件取得するのは現実的ではありません。そのため、次のような対策を組み合わせると運用が安定します。

  • Web パーツ側でのメモリキャッシュ:ページ表示中は Graph 結果を保持し、再読み込みを避ける
  • ブラウザローカルキャッシュ:LocalStorage 等に一時保存し、「最終更新から 3~6 時間以内であればキャッシュを再利用」する
  • サーバーサイドのバッチ:Azure Functions 等で 1 日 1 回 Graph から誕生日を同期し、Web パーツは SharePoint リストを参照する構成に切り替える

JSON 列書式を併用して「今日が誕生日」を目立たせる

ここまでで「月別一覧」を SPFx で実現する方法を紹介しましたが、既存の SharePoint リストに JSON 列書式を併用することで、さらにユーザー体験を高めることができます。

たとえば、誕生日列(Date 型)に対して、今日が誕生日の行だけ背景色を変える JSONは次のように書けます(イメージ)。

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/row-formatting.schema.json",
  "elmType": "div",
  "style": {
    "background-color": "=if(
      (Number(@currentField.getDate()) == Number((new Date()).getDate())) &&
      (Number(@currentField.getMonth()) == Number((new Date()).getMonth())),
      '#fff2cc',
      ''
    )"
  },
  "children": [
    {
      "elmType": "span",
      "txtContent": "=formatDateTime(@currentField, 'MM/dd')"
    }
  ]
}

このように、

  • 「月別の一覧・フィルター」⇒ SPFx Web パーツで実現
  • 「今日が誕生日の人をビューで目立たせる」⇒ JSON 列書式で装飾

という役割分担にすると、SharePoint の標準機能と拡張のバランスが取りやすくなります。

運用上の注意点とベストプラクティス

個人情報保護・コンプライアンス

  • 誕生日は 個人情報(場合によっては要配慮情報)として扱われます。
  • 就業規則や社内ポリシーで「誕生日の公開範囲」を明確にし、必要に応じて 同意取得を検討します。
  • Web パーツの表示対象を 特定のセキュリティ グループに限定する(管理職だけ等)といった制御も有効です。

データ品質(誕生日未登録ユーザーへの対応)

Entra ID 側で誕生日が未登録のユーザーがいる場合、次のような扱い方を決めておきます。

  • Web パーツでは単純に 対象外(表示しない)とする
  • 「誕生日未登録ユーザー一覧」を別途レポートとして出力し、人事・総務に登録を依頼する
  • 登録されるまでの間は「月不明」として最後にまとめて表示する

うるう年(2/29)の扱い

  • 2/29 生まれのユーザーは、非うるう年にどう祝うかを社内ルールとして決めておきます。
  • 実装としては「2/28 扱い」または「3/1 扱い」に寄せるなど、ロジックを明示しておくと混乱がありません。

タイムゾーン問題を避けるコツ

  • 誕生日は「日付だけ」が重要なので、DateTimeOffset をそのまま Date オブジェクトに変換しないのがポイントです。
  • 文字列から YYYY-MM-DD 部分だけを切り出し、「月・日」を数値に分解して扱うことで、UTC 変換による前日/翌日ズレを回避できます。

権限の最小化とレビュー

  • Graph 権限は、可能な限り 狭いスコープで申請する(例: User.Read.All で足りるなら Directory.Read.All を付けない)。
  • 定期的に Enterprise アプリケーションの権限レビューを行い、不要な権限が残っていないかを確認する。

簡易な代替案:SharePoint リストで二重管理するパターン

もし SPFx や Graph の導入ハードルが高い場合、

  1. SharePoint リストに「社員」「誕生日」「誕生日の月(計算列)」を持たせる
  2. ビューで「9月」「10月」など月別のフィルターを作成
  3. People 列でユーザー情報(写真・部署)を補完

といった ノーコード構成でも、最低限の誕生日一覧は作れます。ただし Entra ID 側の情報とは 二重管理になるため、長期運用では同期ミスやメンテナンスが課題になります。

まとめ:月別誕生日一覧は「SPFx + Graph」が本筋

この記事のポイントを整理すると次の通りです。

  • JSON 列書式だけでは、誕生日の「抽出・フィルター」はできない(見た目の装飾に限定される)。
  • Entra ID に登録された誕生日を使って 9月・10月生まれを自動抽出して表示するには、SPFx Web パーツ + Microsoft Graphの構成が現実的。
  • 実装方法の選択肢は大きく 3 つ:
    • PnP のサンプル Web パーツ(react-birthdays, react-birthdays-per-month)をベースに最小改修で導入
    • Origami などの市販 Web パーツを購入してノーコードで運用
    • 自作 SPFx Web パーツで Graph から誕生日を取得し、クライアント側で月別グルーピング
  • Graph の誕生日プロパティ(user.birthday)や拡張属性の使い方には仕様上の制約があるため、実テナントでの検証と最小権限設計が重要。
  • 誕生日情報は個人情報なので、公開範囲・同意・セキュリティ グループによる制御を含めて設計することが必須。

これらを踏まえれば、自社のポリシーや技術レベルに合わせて、「開発コスト」と「運用のしやすさ」のバランスが取れた誕生日 Web パーツを実現できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次