Skip to content
SharePoint SPFx

SPFx Web Parts: The Parts That Actually Matter

The SharePoint Framework is mostly ordinary front-end work with three SharePoint-shaped constraints. Those three are where projects go wrong.

Rajiv KumarFebruary 19, 20266 min read

Building an SPFx web part is TypeScript, a build pipeline and a component tree. What makes it SharePoint is the property pane, the permission model for calling Graph, and a deployment story that has to work for tenant administrators.

Most of an SPFx project is ordinary front-end engineering: TypeScript, a component library, a bundler, a package. If you have shipped a web application you already know four fifths of it.

The remaining fifth is the part that is genuinely SharePoint, and it is where the surprises live: how the property pane works, how permissions to call anything outside the page are granted, and how the thing gets from your machine into a tenant that other people administer.

Where the code runs, and why that matters

An SPFx web part runs in the browser, on the page, in the signed-in user's session. There is no server of yours involved. Three consequences follow immediately, and they explain most of the framework's design:

  • Everything you do runs as the user. A web part cannot read a list the user cannot read. That is a feature, and it means you cannot use a web part to elevate privileges.
  • Your bundle is served to that page. Its size is part of the page's load time, alongside every other web part on it.
  • Secrets cannot live in it. Anything in the bundle is readable by anyone who can open the page. An API key in a web part is a published API key.

The property pane is your configuration API

The property pane is how a site owner configures the web part without opening code. It is worth treating as an interface you are designing, not a form you are filling in.

Two behaviours cause most of the confusion:

  • Reactive versus non-reactive. By default the pane is reactive: every keystroke re-renders the web part. For a field that triggers a network call, that is a request per keystroke. Override disableReactivePropertyChanges to get an Apply button instead.
  • Asynchronous options. A dropdown populated from a list needs its data before the pane renders. Load it in onPropertyPaneConfigurationStart and call refresh() when it arrives, rather than trying to fetch during rendering.

Calling Microsoft Graph and other APIs

This is the topic that most often stalls a project, because the permission request is not in the code — it is in the package, and it is granted by a tenant administrator.

The flow is:

  1. Declare the permission in the package's webApiPermissionRequests, naming the resource and the scope.
  2. Deploy the package to the tenant app catalogue.
  3. An administrator approves the request in the API access page of the SharePoint admin centre.
  4. Your code obtains a client from the context and calls the API.
// Graph: the client is provided by the framework and carries the granted scopes.
const client = await this.context.msGraphClientFactory.getClient("3");
const messages = await client
  .api("/me/messages")
  .top(10)
  .select("subject,receivedDateTime")
  .get();

// A non-Graph API secured by Microsoft Entra ID uses the AAD HTTP client,
// with the resource's application ID URI — not its URL.
const aad = await this.context.aadHttpClientFactory
  .getClient("api://00000000-0000-0000-0000-000000000000");
const response = await aad.get(
  "https://api.contoso.com/v1/orders",
  AadHttpClient.configurations.v1,
);

Two things to understand about this. The permission is granted tenant-wide, not per web part — approving Mail.Read for one solution grants it to any solution that asks. And requesting broad scopes is the fastest way to have a deployment refused by an administrator who is right to refuse it. Ask for the narrowest scope that works.

Theming

A web part sits on someone else's page, and that page has a theme — including a dark one. Reading theme colours from the context and using them, rather than hard-coding hex values, is the difference between a component that belongs on the page and one that visibly does not. The framework provides the current theme and notifies you when it changes; components from the Fluent UI library consume it automatically.

Deployment

The package built by the toolchain is uploaded to an app catalogue — tenant-wide, or a site collection catalogue for a narrower rollout. Two options at that point matter more than they look:

  • Tenant-wide deployment makes the web part available on every site without a site owner adding the app. Convenient, and a decision with tenant-wide blast radius.
  • Where the assets are hosted. Hosting them in the tenant's own app catalogue library keeps everything inside the tenant boundary, which is what most organisations want. A public CDN is faster and puts your code somewhere else.

Version numbers matter for the same reason they do everywhere: an unchanged version can be served from cache, and you will debug code that is not running.

Common mistakes

  • Shipping a large bundle. Your web part shares a page with others. Import the components you use, not the library that contains them.
  • Assuming an administrator will approve broad permissions. Request minimum scope, and document why each one is needed.
  • Hard-coding site URLs. The context knows where it is. Reading it from the context is what makes the same package work in development and production.
  • Ignoring the empty state. A newly added web part has no configuration. Render a helpful prompt, not an error or a blank box.
  • Leaving reactive property changes on for expensive fields. Every keystroke becomes a request.
  • Treating the local workbench as a test. It has no SharePoint context and no real data. Test in a hosted workbench on a real site.

Troubleshooting

SymptomLikely cause
Graph calls return 403The permission request was never approved in the admin centre, or the scope is narrower than the call
Web part works for you, not for othersYou have permissions on the underlying list that they do not — the code runs as each user
Changes do not appear after redeployVersion not incremented, or the browser is serving a cached bundle
Property pane dropdown is emptyData loaded after the pane rendered, with no refresh call
Component looks wrong in a dark site themeColours are hard-coded instead of read from the theme
Package will not upload to the catalogueBuilt without the ship flag, so it references localhost assets

Frequently asked questions

Can a web part do something the user is not allowed to do?

No. It runs entirely in the user's session with the user's permissions. If an operation genuinely requires elevation, it belongs behind an API you control that performs its own authorisation — not in the browser.

Do I have to use React?

No. The framework is agnostic and the generator offers a no-framework option. React is the common choice because the Fluent UI components that make a web part look native are React components.

What is the difference between a web part and an extension?

A web part is placed on a page by an author. An extension customises the page itself — an application customiser for headers and footers, a field customiser for how a column renders, a command set for list commands. Extensions apply without anyone adding them to a page, which makes them powerful and worth deploying carefully.

Can SPFx work against SharePoint Server?

Some on-premises versions support a subset of the framework, generally at an older version with fewer capabilities. Verify the specific version's support before planning around it rather than assuming parity with SharePoint Online.

The short version

Write it like any other front-end component, then spend your remaining attention on three things: keep the bundle small, ask for the least permission that works, and read colours and URLs from the context instead of hard-coding them. Those three decisions determine whether a tenant administrator is willing to deploy what you built.

Share