Skip to content

SharePoint

SPFx + Microsoft Graph: Complete Developer Guide

Use Microsoft Graph with SPFx securely: delegated authentication, least-privilege permissions, users, groups, Teams, files, paging, throttling, and production readiness.

Suresh Girinathuni
Published
Reading time
23 min read
SPFx Graph guide illustration showing authentication flowing from SPFx through Entra ID to Microsoft Graph under least privilege

What you’ll learn

  • What is Microsoft Graph for SPFx developers
  • When should SPFx use Graph
  • Graph versus SharePoint REST versus PnPjs
  • Authentication flow
  • Authentication versus authorization
On this page (62 sections)

Direct answer: SPFx solutions often need data beyond the current SharePoint site — users, Microsoft 365 Groups, Teams, OneDrive, files, directory information, and other Microsoft 365 workloads. Microsoft Graph provides the unified API surface for many of these services, reached from SPFx through delegated authentication, approved least-privilege permissions, and a service layer that keeps Graph calls out of React components.

Calling Graph is easy. Designing permissions correctly is the important part. Most Graph failures in SPFx are approval, scope, and access problems — not code problems — so this guide treats permissions as the main subject and endpoints as its application.

  1. User — authenticated with single sign-on.
  2. SharePoint Online — hosts the SPFx solution.
  3. SPFx — requests data through the Graph client.
  4. Microsoft Entra ID — authenticates and issues scoped tokens.
  5. Access token — least-privilege proof, never logged.
  6. Microsoft Graph — users, groups, Teams, files, OneDrive, Microsoft 365 services.

For UI architecture, use SPFx and React; for SharePoint data, SPFx and PnPjs; for the lifecycle, the SPFx complete guide; for navigation, the SPFx hub.

What is Microsoft Graph for SPFx developers

Microsoft Graph exposes APIs across Microsoft 365 and Microsoft Entra resources. What matters to an SPFx developer is narrower than the whole platform: endpoints addressed per resource, resources modeled per workload, permissions declared per call, tokens acquired per user session, queries shaped per need, paging followed per response, and errors classified per cause. No history lesson follows — every section below is operational.

When should SPFx use Graph

Route by resource, not by habit. Current SharePoint site data leans toward SharePoint REST or PnPjs; cross-service Microsoft 365 data leans toward Microsoft Graph; custom business-system data leans toward a protected custom API:

SPFx
├── SharePoint REST — current-site SharePoint operations
├── PnPjs — convenient SharePoint operations
├── Microsoft Graph — cross-service Microsoft 365 data
└── External API — business-specific services

Real enterprise solutions routinely use several — Graph for people and files beside PnPjs for list internals, for example.

Graph versus SharePoint REST versus PnPjs

TechnologyBest fitStrengthsConsiderations
SharePoint RESTSharePoint-specific operationsPrecise control, no abstractionVerbose; manual typing and paging
PnPjsDeveloper convenience over supported APIsFluent, typed, batteries includedVersion alignment with the SPFx release
Microsoft GraphCross-Microsoft 365 resourcesOne surface, delegated permissionsNot every SharePoint operation is exposed

Never rank them, never claim Graph replaces SharePoint REST, never claim PnPjs replaces Graph. The resource and the operation decide — and the decision is documented per call, not per project fashion. Depth: SPFx and PnPjs.

Authentication flow

SPFx runs in the user's browser, so authentication uses supported Microsoft and SPFx mechanisms with automatic single sign-on — developers never embed credentials anywhere in this chain:

  1. User — signed in with single sign-on.
  2. SPFx — requests data through the Graph client.
  3. Microsoft Entra ID — authenticates the user.
  4. Access token — scoped, short-lived, never logged or stored casually.
  5. Microsoft Graph — validates token and scope per call.
  6. Resource — returned under the grant and the user's access.

Authentication versus authorization

Authentication answers who is calling; authorization answers what the caller may do. A successful sign-in guarantees nothing about any specific call — the working equation is authenticated user plus approved API permission plus resource authorization equals successful request. Debugging starts by naming which term failed, because sign-in fixes never repair permission denials and vice versa.

Delegated access

Common SPFx Graph scenarios use the delegated, user-context model: the solution holds an approved Graph permission grant, the signed-in user holds their own resource access, and each call succeeds only where both allow. Never imply an approved scope gives a user unrestricted access to every object — actual behavior depends on permission type, resource, API, tenant policy, user access, and endpoint. Delegation means the app borrows the user's identity within the grant's ceiling, not that the grant transfers ownership of the directory.

  1. SPFx — declares the scope it needs.
  2. Graph permission granted plus signed-in user's permitted access — both required.
  3. Graph resource — returned where both allow.

Application permissions need a backend

Normal browser-based SPFx user-context Graph calls are fundamentally different from confidential server-side applications using application permissions. Never place client secrets, certificate private keys, or application credentials inside SPFx — the bundle is public by design. Where application-only access is genuinely required, the architecture becomes SPFx to a secure backend, backend to a Microsoft Entra application, application to Microsoft Graph — with the confidential credential remaining server-side at every step.

Never store client secrets in SPFx

Anything shipped to the browser can be inspected by the user — bundled secrets are published secrets. The only two supported shapes are SPFx with supported user authentication, or SPFx with a protected backend holding server-side credentials in front of Microsoft Graph. There is no third shape where a browser secret stays secret, so designs requiring one are redesigned, not implemented.

SUPPORTED:   SPFx → Supported User Authentication → Graph
SUPPORTED:   SPFx → Protected Backend → Server-Side Credential → Graph
NEVER:       SPFx JavaScript → Client Secret → Graph

Graph client in SPFx

Use the current Graph client approach verified against Microsoft documentation: the factory on the web part context vends the client, which speaks the Graph JavaScript client syntax. Older client generations are superseded — never present them as current guidance:

import type { WebPartContext } from "@microsoft/sp-webpart-base";
import type { MSGraphClientV3 } from "@microsoft/sp-http";

export async function getGraphClient(
  context: WebPartContext
): Promise<MSGraphClientV3> {
  return context.msGraphClientFactory.getClient("3");
}
  1. SPFx context — carries the factory.
  2. Graph client factory — vends authenticated clients.
  3. Graph client — typed calls with user identity.
  4. Graph API — resources behind approved scopes.

Confirm the client generation against current documentation for the targeted SPFx release before implementing; client APIs evolve, and this guide tracks the verified current shape.

First Graph request

Read the signed-in user with an explicitly selected field set, map the response to an application model, and handle failure with a safe message:

const client = await getGraphClient(context);
try {
  const me = await client
    .api("/me")
    .select("id,displayName,mail,userPrincipalName")
    .get();
  return {
    id: me.id as string,
    displayName: me.displayName as string
  };
} catch (error) {
  throw new Error(
    "Profile could not be loaded. Confirm Graph permission approval."
  );
}

Request, typed mapping, error handling — the complete shape every later example in this guide repeats at larger scale.

Service-layer architecture

Graph calls scattered through components cannot be tested, reviewed, or permission-audited. Route every call through hooks into service interfaces with Graph-backed implementations — the same layering as the React and PnPjs guides, repeated deliberately because data features live or die by it:

AVOID:    React Component → Graph calls everywhere

PREFER:   React → Hook → IUserService
          → GraphUserService → Graph Client → Microsoft Graph

Separation buys testability, error handling in one place, permission isolation per service, and maintainability across team changes. Depth: SPFx and React.

Service interface

Components depend on operations, never on Graph response shapes:

import type { IUser } from "../models/IUser";

export interface IUserService {
  getCurrentUser(): Promise<IUser>;
  searchUsers(query: string): Promise<IUser[]>;
}

Graph response to application model

Map Graph responses to application models at the service boundary — maintainability, type safety, testability, and API isolation follow. When Graph reshapes a payload, one mapping function changes instead of every component rendering a name:

Graph Response → Service Mapping → Application Model → React
export interface IUser {
  id: string;
  displayName: string;
  mail: string;
}

function toUser(payload: {
  id: string;
  displayName: string;
  mail?: string;
}): IUser {
  return {
    id: payload.id,
    displayName: payload.displayName,
    mail: payload.mail ?? ""
  };
}

API permissions

Graph permissions are needed because the default grant covers far less than solutions typically call. Declare every required scope in the solution package so reviewers see the full access surface, deploy so the request surfaces for review, and treat tenant administrator approval as a deployment gate — unapproved scopes fail at runtime, never at build time. Least privilege governs every entry: each scope justified, documented, and no broader than the calls require.

  1. SPFx solution — declares required scopes.
  2. Permission requirement — visible to reviewers.
  3. Deployment — surfaces the request.
  4. Admin review — approve or reject per scope.
  5. Approve or reject — recorded decision.
  6. Runtime Graph access — succeeds only under approval.

Declaring permission requests

Solutions declare each requirement as a resource and scope pair — Microsoft Graph with one specific delegated permission per entry — in the solution package configuration. Request only the scopes the solution calls: one entry per need, no broad scopes added to make examples work, every entry documented with its purpose. Verify exact configuration property names and syntax against current Microsoft documentation before publishing, because packaging schemas evolve with the toolchain.

Permission approval workflow

The administrative flow stays conceptual here by design — admin center experiences change, while the invariant does not: solution requests permission, tenant administrator reviews, permission approved, SPFx runtime can request and use access. Mention exact UI locations only after verification against the current admin experience; describe the gate, the roles, and the recorded decision, never click paths that rot.

  1. Solution requests permission — declared in the package.
  2. Tenant administrator reviews — scope against justification.
  3. Permission approved — recorded per solution.
  4. SPFx runtime can request and use access — under the grant.

Least privilege

Identify the exact API, determine the minimum permission it needs, request only that scope, and document why it is required. Broad scopes requested just in case expand governance and security impact across every user of the solution — and invite rejection at review. Least privilege is a per-call discipline, not a manifest slogan.

AVOID:    Request broad scope "just in case"
PREFER:    Identify API → Minimum permission → Request only that scope → Document why

Permission matrix

Each entry verified against long-standing delegated Graph permissions; still verify before use, because scopes and endpoint requirements evolve. Where multiple scopes could serve, the narrower one is listed with the reason.

ScenarioGraph resourcePossible permissionVerify before use
Read signed-in user/me with selected fieldsUser.ReadEndpoint and select support
Search and read users/users with queryUser.ReadBasic.AllQuery capabilities and headers
Read group information/groupsGroup.Read.AllGroup types in scope
Read group members/groups/{id}/membersGroupMember.Read.AllMembership types returned
Read Teams information/teamsTeam.ReadBasic.AllEndpoint currency
Read files/me/drive, drive itemsFiles.Read.AllDrive versus site scoping

Reading the signed-in user

Select id, displayName, mail, and userPrincipalName explicitly — requesting only required properties keeps payloads small, contracts clear, and privacy reviewers calm. Never assume every property populates for every principal; disabled, external, and system accounts resolve thinly.

const me = await client
  .api("/me")
  .select("id,displayName,mail,userPrincipalName")
  .get();

Users

Current user, user by ID, profile reads, and user search or listing where permissions allow — each with $select, each respecting that directory data varies by principal. Never expose unnecessary directory data in UI or logs; request, render, and retain only what the feature requires and the grant authorizes.

Search syntax, headers, and advanced-query requirements vary by endpoint and change over time — verify current query requirements before publishing search code, and never ship a simplistic query untested against current Graph behavior. Wrap search in debounced, result-limited, paged service calls with permission handling, because directory search is where throttling, privacy, and empty-result UX collide.

Groups

Microsoft 365 Groups, Entra Security Groups, and SharePoint Groups are not interchangeable — different authorities, different APIs, different lifecycles. Route SharePoint Groups to SharePoint APIs, Microsoft 365 and Entra Groups to Microsoft Graph, and document which authority each feature depends on so future maintainers stop guessing:

SharePoint Group → SharePoint APIs
Microsoft 365 Group → Microsoft Graph
Entra Group → Microsoft Graph

Group members

Read members with paging, approved scopes, and membership-type awareness — nested, guest, and service principals resolve differently and surprise code written against uniform test tenants. Never assume every group membership scenario behaves identically; verify per group type in scope.

Teams

Team information, channels, and members flow through current Teams endpoints with appropriate approved scopes — included here as verified-current patterns only, without making Teams the article's focus. Confirm endpoint and permission currency before implementing, and link dedicated Teams and Graph content when it exists rather than duplicating it.

OneDrive and files

Think Drive and DriveItem: the current user's drive, files, folders, and metadata addressed uniformly, with files and folders as DriveItem facets rather than separate universes. Never confuse Graph DriveItem operations with the full SharePoint document-library surface — versioning detail, approval flows, and library policy still favor SharePoint-side APIs:

Graph
↓
Drive
↓
DriveItem
├── File
└── Folder

SharePoint through Graph

Graph exposes SharePoint-related sites, lists, and drives where supported — but never identically to SharePoint APIs. Route per operation: where Graph serves the need well it fits; where it does not, SharePoint REST or PnPjs carries the call. This judgment call, made per operation and documented per service, separates architecture from fashion.

  1. SharePoint requirement — stated operationally.
  2. Graph supports the operation well? — yes fits Graph.
  3. Otherwise — SharePoint REST or PnPjs.

$select: request only what you render

Default property sets are generous; production queries are not. Project every call to the fields the UI renders — smaller payloads, clearer contracts, fewer privacy questions, and measurably faster pages. Untrimmed Graph calls in review are defects, not style choices.

const users = await client
  .api("/users")
  .select("id,displayName,mail")
  .top(25)
  .get();

$filter with verification

Filters belong server-side — but not every property or resource supports every filter operator, and some queries need additional headers or capabilities. Verify per endpoint before publishing filter code, and never generalize one endpoint's filter behavior across all Graph APIs. Unverifiable filters stay out of examples; verified ones ship with their requirements documented.

$expand where supported

Graph OData query-option support varies by resource — verify before using, and never teach expand as universally available. Where supported, expansions replace follow-up calls with one shaped response; where unsupported, the design falls back to explicit secondary reads with the same service-layer handling.

$orderby with verification

Order only against endpoints where current support is verified, and keep ordering inside paged queries so page boundaries stay stable across fetches. Unverifiable ordering claims stay out of production code.

$search with care

Some Graph resources demand specific query syntax, consistency headers, counts, and permissions before search works at all. Verify exact requirements per resource before publishing search examples — search is the query option most likely to fail when copied between endpoints.

Graph responses page: follow the next-link URLs Graph returns instead of reconstructing them, rendering one page at a time and fetching onward on demand. Never assume the first response holds the complete dataset — that assumption is the most common Graph data bug in production solutions:

export interface IPagedUsers {
  users: IUser[];
  nextLink?: string;
}

export async function getUsersPage(
  client: MSGraphClientV3,
  url?: string
): Promise<IPagedUsers> {
  const response = url
    ? await client.api(url).get()
    : await client
        .api("/users")
        .select("id,displayName,mail")
        .top(50)
        .get();
  return {
    users: (response.value ?? []).map(toUser),
    nextLink: response["@odata.nextLink"] as string | undefined
  };
}
  1. Request — first page with select and top.
  2. Page one — rendered immediately.
  3. Next link — followed verbatim when present.
  4. Page two onward — fetched on demand, never prefetched blindly.

Batching

Group independent Graph requests into batches with per-response status handling — verifying current batching limits and behavior before publishing exact numbers. Batch for fewer round trips on genuinely independent calls; dependent sequences stay sequential, and heavy fan-out gets redesigned rather than batched harder.

Request A · Request B · Request C
↓
Graph Batch
↓
Responses (each with its own status)

Batching versus parallel versus sequential

Sequential suits dependent operations, parallel suits independent calls needing separate handling, batching suits API-supported groupings — chosen by dependencies, volume, endpoint behavior, throttling, and error handling. Never fire uncontrolled parallel request sets across large datasets; bound, handle, and redesign instead.

Throttling

Microsoft Graph throttles busy clients with 429 responses carrying the server's retry guidance. Read that guidance where provided, wait, retry appropriately, then reduce volume through select, paging, batching, caching, and debounced search. Never invent retry delays, never hammer throttled endpoints, and verify current throttling guidance rather than quoting remembered numbers.

  1. 429 — throttled, not broken.
  2. Read retry guidance — server-provided where available.
  3. Wait — honor the guidance.
  4. Retry appropriately — then reduce future volume.

Retry strategy

Retry transient failures — network blips and guided throttling windows — and stop there. Never repeatedly retry permission denials, missing resources, or invalid queries without fixing root causes; exponential backoff applies only where consistent with current Microsoft guidance, and every retry path carries a visible ceiling.

401 Unauthorized

Token, authentication, resource-audience, expiry, context, or custom-API authentication problems — never one universal cause. Isolate the layer: sign-in and token acquisition first, audience and context second, custom API configuration third. Authentication failures get identity fixes, never permission-scope guesses.

403 Forbidden

The highest-value diagnostic in this guide. Work the chain in order — endpoint, required permission, requested scope, admin approval state, user and resource access, tenant policy — then retest. Most Graph 403s in SPFx are unrequested scopes, unapproved grants, or users lacking resource access; code defects trail far behind, so fix identity and approval before touching implementation.

  1. 403 — authorization failed, identity intact.
  2. Which endpoint? — name the exact call.
  3. Required permission? — per current documentation.
  4. Requested? — declared in the package.
  5. Approved? — administrator decision recorded.
  6. User and resource access? — effective access tested.
  7. Tenant policy? — conditional and governance rules.
  8. Retest — one variable at a time.

404 Not Found

Wrong IDs, wrong endpoints, deleted resources, incorrect tenant or resource context, unsupported routes — never assume permission from a 404. Verify addressing before authorization theories; most 404s are typos, renames, and environment mismatches wearing an intimidating status code.

Graph error responses

Inspect status code, error code, and request or correlation identifiers where provided during development — then log categories, never tokens, payloads, or personal data beyond need. Sanitized errors reach users; full diagnostics reach support channels with access controls.

Graph Explorer, correctly understood

Graph Explorer tests endpoints, responses, query parameters, permission requirements, and request diagnosis superbly — under its own context and consents. A query succeeding there proves the endpoint and the query, never the SPFx solution's permissions. Verify solution scopes, approval state, and real-user access separately, every time.

Official tool: Microsoft Graph Explorer.

Development diagnostic workflow

  1. Graph request fails — with status and evidence captured.
  2. Test endpoint and query — isolate syntax from auth.
  3. Check Microsoft documentation — current requirements.
  4. Check required permission — per endpoint.
  5. Check SPFx permission request — declared scope.
  6. Check admin approval — recorded decision.
  7. Check user access — effective, as the user.
  8. Inspect network and error — sanitized diagnostics.
  9. Retest — single variable changed.

Performance

Select explicitly, filter server-side where supported, page results, avoid duplicate calls, batch where appropriate, cache cautiously, avoid N+1 patterns, and debounce search. Directory-shaped data punishes lazy querying hardest: small result sets hide sins that tenant-wide features expose on day one.

N+1 Graph requests

Fetching a hundred users then requesting per-user details creates a hundred-and-one-request page. Prefer better initial queries, batching, different resources, reference-data caching, or architecture redesign where supported — while admitting Graph cannot always expand the needed relationship, in which case the design changes instead of the request count.

Caching Graph data

Configuration and slow-changing reference information cache well; user-specific, permission-sensitive, directory, and fast-changing data cache dangerously. Every entry needs staleness, invalidation, privacy, and tenant-boundary answers before it ships — a shared cache key across users is a data leak with a performance excuse.

Privacy and data minimization

Request only the data the feature requires — directory properties, profile data, and membership information appear in UI and logs only where the business scenario needs them and authorization covers them. No legal compliance claims are made here; the engineering rule stands alone: minimized data is minimized blast radius.

Security checklist

Graph with React

Components consume hooks, hooks consume user services, services call the Graph client into Microsoft Graph — the exact layering from the React guide, repeated because data features live or die by it. Depth: SPFx and React.

React → useEmployees → IUserService
→ GraphEmployeeService → Graph Client → Microsoft Graph

Graph with PnPjs nearby

PnPjs may expose Graph functionality through its graph packages with a parallel factory — useful where SharePoint-adjacent identity data rides along. Never force all Graph access through PnPjs: direct Graph client calls stay first-class, with boundaries drawn per feature. Depth: SPFx and PnPjs.

SPFx ├── PnPjs → SharePoint
└── Graph Client → Microsoft Graph

External APIs versus Graph

Microsoft Graph serves Microsoft 365 APIs; custom and external APIs serve business-specific services. Protected custom APIs use current SPFx mechanisms where verified — never confuse Graph permissions with custom API permissions, which live in different authorities with different approval paths.

Illustrative solution: employee directory

Labeled illustrative architecture, never a customer case. An employee directory web part on SharePoint Online renders React search, names, titles, departments, and contact details only where required and available — through a hook, a user service interface, and a Graph-backed implementation under minimum verified permissions. Broad directory scopes for convenience are explicitly out: the example requests only what its UI renders.

  1. SharePoint Online — host surface.
  2. SPFx — context and lifecycle.
  3. React — search and directory UI.
  4. useEmployees — data, loading, error, refresh.
  5. IEmployeeService — the contract.
  6. GraphEmployeeService — delegated Graph implementation.
  7. Microsoft Graph — users behind approved scopes.

Example: Graph service

The complete service shape — client acquisition, selected fields, typed mapping, classified errors — reusing the patterns established above:

import type { WebPartContext } from "@microsoft/sp-webpart-base";
import type { MSGraphClientV3 } from "@microsoft/sp-http";
import type { IUser } from "../models/IUser";

export async function searchUsers(
  context: WebPartContext,
  query: string
): Promise<IUser[]> {
  const client: MSGraphClientV3 =
    await context.msGraphClientFactory.getClient("3");
  try {
    const response = await client
      .api("/users")
      .select("id,displayName,mail")
      .top(25)
      .get();
    const value = response.value ?? [];
    return value.map((entry: {
      id: string;
      displayName: string;
      mail?: string;
    }) => ({
      id: entry.id,
      displayName: entry.displayName,
      mail: entry.mail ?? ""
    }));
  } catch (error) {
    throw new Error(
      "User search failed. Confirm Graph permission approval " +
        "and the current user's access."
    );
  }
}

Example: error-handling wrapper

One wrapper shape for try, catch, status handling, and safe logging — reused by every Graph service in the solution:

export async function runGraphQuery<T>(
  operation: string,
  action: () => Promise<T>
): Promise<T> {
  try {
    return await action();
  } catch (error) {
    throw new Error(
      operation + " failed. Check permissions, approval, and access."
    );
  }
}

Tokens never enter logs; operation names and categories do. Callers translate these errors into hook state and safe UI messages.

Permission-first development

  1. Define business requirement — the feature, owned.
  2. Identify Graph resource — users, groups, files.
  3. Identify exact API — endpoint and query verified.
  4. Determine minimum permission — narrowest working scope.
  5. Add permission request — declared in the package.
  6. Admin review — justification scrutinized.
  7. Implement — service behind the grant.
  8. Test with real user permissions — personas, not admins.
  9. Validate production — approval and access re-confirmed.

Permissions lead, code follows — the key takeaway teams should carry out of this guide.

Test multiple user personas

Never test only as tenant administrator. Exercise standard users, resource owners and members, and users without resource access — verifying both positive access and negative access. Persona testing detects over-permission and authorization assumptions that admin-only testing structurally cannot see.

Dev, test, and production

Tenant IDs, API permission approval, data, users and groups, tenant policies, custom API registrations, and environment configuration all differ across stages. A Graph request working in development proves nothing about production permissions — re-validate approval and access per environment before sign-off.

Common SPFx and Graph mistakes

MistakeWhy it causes problemsBetter direction
Requesting broad permissionsExpands governance impact and invites rejectionLeast privilege per call, documented
Putting secrets in SPFxBundles are public; secrets leak on deliveryServer-side credentials behind a backend
Assuming approval equals unrestricted user accessGrants bound calls, not users, to resourcesTest effective access per persona
Using beta endpoints unnecessarilyPreview behavior changes without noticev1.0 where available; label beta explicitly
Ignoring pagingSilent data loss past the first pageFollow next links on every collection
Ignoring 429Escalating throttling into outagesHonor guidance, then reduce volume
No select projectionOversized payloads with privacy surfaceProject every query to rendered fields
N+1 requestsRequest storms per rendered rowBetter queries, batching, or redesign
Graph calls scattered in ReactUntestable, unreviewable data flowServices behind interfaces
Testing only as adminBlindness to real authorization behaviorPersona matrix per feature
Confusing SharePoint Groups and Entra GroupsWrong API, wrong authority, wrong resultsRoute per group authority
Assuming Graph replaces all SharePoint RESTMissing capabilities mid-projectValidate per operation, keep both
Logging tokensCredential exposure in support channelsCategories and correlation only
Hard-coded tenant and resource IDsEnvironment breakage on every moveExternalize and verify per stage
Treating Explorer success as SPFx proofDifferent contexts, different consentsVerify solution scopes and approval separately

Troubleshooting matrix

SymptomCheckLikely categoryNext step
401Sign-in, token, audience, contextAuthenticationFix identity before code changes
403Endpoint, scope, approval, access, policyAuthorizationWork the 403 chain in order
404IDs, endpoints, context, deletionAddressingVerify the address before theories
429Volume, patterns, guidanceThrottlingHonor guidance, reduce volume
Empty resultsFilters, permissions, scopingQuery or accessLoosen filters, then check access
Missing propertySelect list, permissions, populationProjection or dataRequest explicitly, verify population
Paging incompleteNext-link handlingClient logicFollow links on every collection
Permission request pendingAdmin center stateApproval gapObtain the recorded decision
Approved but still failingUser access, scopes, policyAuthorization layersTest effective access per persona
Works in Explorer, not SPFxSolution scopes versus Explorer consentsContext mismatchVerify solution approval separately
Works for admin, not usersPersona access, scopesAuthorization assumptionsTest the failing persona directly
Teams endpoint failsEndpoint and scope currencyAPI driftRe-verify docs before code changes
Group members incompleteTypes, paging, scopesShape assumptionsPage fully, check types and scopes

Architecture blueprint: SPFx and Microsoft Graph

Labeled solution blueprint, never a customer case. A user reaches SharePoint Online, where SPFx renders React UI backed by services calling the Graph client — authenticated by Entra ID into users, groups, Teams, drives, and Microsoft 365 resources — surrounded by least privilege, API permissions, paging, throttling, error handling, logging, configuration, and data minimization.

  1. User — authenticated with single sign-on.
  2. SharePoint Online — host surfaces.
  3. SPFx — context and lifecycle.
  4. React — components and hooks.
  5. Service layer — typed Graph operations.
  6. Graph client — authenticated delegated calls.
  7. Microsoft Entra ID — identity beneath every call.
  8. Microsoft Graph — users, groups, Teams, drives, Microsoft 365 resources.

Production readiness checklist

Architecture: service layer used; Graph calls separated from React; models and interfaces defined; environment configuration separated.

Permissions: endpoints documented; minimum permissions identified; requests configured; admin approval verified; multiple personas tested.

Security: no secrets; tokens never logged; least privilege; sensitive data minimized; errors sanitized.

Queries: select used where appropriate; server-side filtering where supported; paging handled; N+1 reviewed; batching considered.

Reliability: 401, 403, 404, and 429 handled; retry behavior appropriate.

Production: dev, test, and prod differences documented; tenant approval validated; logging reviewed; ownership documented; beta dependencies identified.

Migration and modernization connection

Legacy SharePoint JavaScript calling old endpoints modernizes operation by operation into service-layer Graph, PnPjs, or REST inside SPFx. Depth: Script Editor to SPFx modernization, Classic to Modern SharePoint, and the migration hub. Graph is never automatically required during modernization — the operation decides.

Building an SPFx and Microsoft Graph solution

If you are working through Graph permissions, authentication, users and groups, Teams, data access, throttling, or complex Microsoft 365 integrations, share the technical challenge with nextM365: Discuss Your SPFx Project.

Continue with Explore SPFx, the SPFx complete guide, SPFx and React, SPFx and PnPjs, and Microsoft Graph for beginners.

Related resources

Share this:

Topics covered

React · Project Structure · Permissions · ALM · Security

Frequently asked questions

Can SPFx call Microsoft Graph?

Yes, through the current Graph client with delegated permissions approved by a tenant administrator. Calls run as the signed-in user, requests use least-privilege scopes, and 401 and 403 responses are handled distinctly.

How does SPFx authenticate to Microsoft Graph?

Through supported SPFx mechanisms with automatic single sign-on in SharePoint Online — developers never embed credentials. Authentication identifies the user; separate authorization layers decide what each call may do.

Does SPFx need an app registration to call Graph?

Not a separate developer-managed registration for standard delegated scenarios — the solution declares permission requirements in its package and a tenant administrator approves them. Confidential server-side scenarios needing application permissions are architected through a secure backend instead.

How are Microsoft Graph permissions approved for SPFx?

The solution declares each required scope, deployment surfaces the request, a tenant administrator reviews and approves or rejects it, and only approved scopes work at runtime. Unapproved permissions fail when called, not when deployed.

What is the difference between delegated and application permissions?

Delegated permissions let an app act as the signed-in user within that user’s access; application permissions let a confidential server-side app act on its own. Browser-based SPFx user-context calls use the delegated model — never application credentials in the bundle.

Can I store a client secret in SPFx?

No. Anything shipped to the browser is inspectable, so client secrets, certificates, and privileged keys belong server-side. Designs requiring confidential credentials add a protected backend between SPFx and Graph.

Why does Microsoft Graph return 403 in SPFx?

Work the chain: which endpoint, which permission it requires, whether that scope was requested, whether an administrator approved it, whether the user has resource access, and whether tenant policy intervenes. Most 403s are approval or access gaps, not code defects.

Why does Graph work in Graph Explorer but not SPFx?

Graph Explorer runs under its own context and consents, so its success proves the endpoint and query — never the SPFx solution’s permissions. Verify the solution’s requested scopes, admin approval state, and the real user’s access separately.

How do I handle Microsoft Graph paging?

Follow the next-link URLs Graph returns instead of reconstructing them, rendering one page at a time and fetching onward on demand. Never assume the first response holds the complete dataset.

How do I handle Graph 429 errors?

Read the server’s retry guidance where provided, wait, and retry appropriately — then reduce request volume through select, paging, batching, caching, and debounced search. Never invent retry delays or hammer throttled endpoints.

Should SPFx use Graph or PnPjs?

Graph for cross-Microsoft 365 resources such as users, groups, Teams, and files; PnPjs or SharePoint REST for SharePoint-specific operations Graph does not expose equally. Real solutions routinely use both, chosen per resource and operation.

Can Microsoft Graph replace SharePoint REST?

No. Graph exposes SharePoint-related resources such as sites, lists, and drives where supported, but not every SharePoint capability identically. Validate per operation and keep SharePoint-side APIs where they fit better.

Can SPFx call Teams APIs through Graph?

Where supported and verified for the scenario — team information, channels, and members through current Teams endpoints with the appropriate approved scopes. Confirm endpoint and permission currency before implementing.

Sources

Have a Microsoft 365 topic idea?

Share article suggestions, community session ideas, corrections, or real-world scenarios for future nextM365 learning notes.

Connect with me

Keep learning Microsoft 365

Explore more practical tutorials for SharePoint, Power Platform, Copilot Studio, migration, automation, governance, and security.

Continue learning