If you’ve spent any real time building on Microsoft 365, you already know that SPFx is the backbone of modern SharePoint customization. I’ve seen developers with solid JavaScript skills stumble in SPFx interviews simply because they hadn’t connected the dots between the framework’s concepts and how they show up in practice. And I’ve seen the opposite too — developers who knew the theory cold but couldn’t explain why they’d make a particular architectural choice.
That’s exactly what I want to help you with here.
In this guide, I’m walking you through 75 SPFx interview questions and answers — the kind that actually come up when you’re interviewing for SharePoint developer roles, whether it’s your first SPFx position or you’re gunning for a senior architect seat. I’ve structured them to flow naturally: we start with the fundamentals that every developer should have locked down, move into the core concepts that separate good developers from average ones, and then get into the advanced concepts for the experienced professionals.
I’ve deliberately kept the answers current. As of early 2026, SPFx has gone through meaningful changes — the Gulp-to-Heft toolchain transition in version 1.22, the upcoming SPFx CLI replacing Yeoman, the new in-page debugging toolbar rolling out across tenants, and a quarterly release cadence that’s making the platform move faster than it ever has. You won’t find outdated Node.js version advice or references to deprecated APIs here.
One more thing before we get into it: don’t just memorize these answers. The best interviews I’ve seen are conversations, not recitations. If you understand why SPFx works the way it does, you’ll be able to answer questions that aren’t even on this list. That’s what I’m really trying to help you build.
Let’s get into it.
SharePoint Framework (SPFx) Interview Questions and Answers
Below are the list of SPFx interview questions and answers.
If you want to learn it from the beginning, check out our SharePoint Framework (SPFx) training course.
Q1. What is SharePoint Framework (SPFx)?
SPFx is Microsoft’s client-side development model for building customizations and extensions for SharePoint Online, SharePoint Server (Subscription Edition), and Microsoft Teams. It lets you build web parts, extensions, and Adaptive Card Extensions (ACEs) using modern web technologies like TypeScript, React, and Fluent UI. Unlike the older farm solutions or sandbox solutions, SPFx runs entirely in the browser — no server-side code, no full trust required.
Q2. How is SPFx different from the classic SharePoint development model?
The classic model relied on server-side code, farm solutions, and full-trust deployments that ran on the SharePoint server itself. SPFx flips that completely — everything is client-side, everything runs in the browser, and your code never touches the server runtime.
This makes SPFx far more suitable for SharePoint Online, where you simply don’t have access to the server. It’s also more aligned with how modern web development works today.
Q3. What are the main component types you can build with SPFx?
There are three primary component types:
- Web Parts
- The most commonly used component
- Custom UI elements that can be added to SharePoint pages
- Used to display content, data, or interactive features
- Extensions: These customize the SharePoint experience and include four types:
- Application Customizers — modify header, footer, or inject custom scripts
- Field Customizers — customize how list columns are rendered
- Command Sets — add custom actions/buttons to list toolbars and context menus
- List Form Customizers — replace default New/Edit/View forms with custom UI
- Adaptive Card Extensions (ACEs)
- Used in Viva Connections dashboards
- Built using Adaptive Cards
- Optimized for mobile and quick interactions
Q4. What tools do you need to set up an SPFx development environment?
At a minimum, you need:
- Node.js — version compatibility matters a lot here (more on that in a moment)
- npm — comes bundled with Node
- Yeoman (
yo) — historically used to scaffold SPFx projects; being replaced by the new SPFx CLI in 2026 - @microsoft/generator-sharepoint — the Yeoman generator for SPFx
- Gulp — though SPFx 1.22 transitioned from Gulp to a Webpack-based toolchain via Heft
- A code editor — VS Code is essentially the standard
Q5. What Node.js version should you use for new SPFx projects in 2026?
For any new project started today, the recommended baseline is SPFx 1.22 or 1.23 with Node.js 22 LTS. Node.js 22 LTS support was officially introduced from SPFx 1.21.1 onward. If you’re maintaining older projects, keep in mind that SPFx 1.17–1.19 targets Node 16.x–18.x, and anything older than SPFx 1.11 is no longer supported for SharePoint Online at all.
Q6. What is the workbench, and what’s replacing it?
The SPFx workbench (https://your-tenant.sharepoint.com/_layouts/15/workbench.aspx) is a hosted test page where you can preview and debug web parts without deploying them to a real page. It’s been the go-to debugging tool for years.
However, Microsoft is now rolling out a new in-page debugging toolbar (which started rolling out to SharePoint Online tenants in early February 2026) that lets you debug directly on any SharePoint page. The workbench will eventually be retired in favor of this approach.
Q7. What is the purpose of the serve.json file?
serve.json (located in the config folder) controls the behavior of the local development server. It specifies which components to load, their properties, and whether to target the local or hosted workbench.
When you run gulp serve (or the equivalent Heft command in 1.22+), it reads this file to know what to spin up. In the hosted workbench scenario, it also tells the framework which SharePoint tenant to connect to.

Q8. What is the package-solution.json file used for?
This file, found in config/package-solution.json, defines the metadata for your .sppkg file — the package you deploy to the App Catalog. It contains the solution name, version, and whether it supports tenant-wide deployment (skipFeatureDeployment: true), and any web API permissions your solution needs.
If you’ve ever wondered why a web part showed up across the entire tenant without needing to be added to each site, that’s skipFeatureDeployment at work.

Q9 What is a .sppkg file in SPFx?
The .sppkg file is the final deployable package for an SPFx solution. It is generated when you run gulp package-solution --ship (or the equivalent Heft command in newer versions). This file contains your solution metadata, references to bundled assets, and configuration required for deployment.
You upload the .sppkg file to the App Catalog, where it becomes available for installation across SharePoint sites. Depending on your configuration (like skipFeatureDeployment), the solution can be deployed tenant-wide or site-specific.
Q10. What’s the difference between gulp bundle and gulp package-solution?
gulp bundlecompiles and bundles your TypeScript/JavaScript code into output files. Adding--shipgenerates a production-optimized, minified build.gulp package-solutiontakes the bundled output and packages everything into the.sppkgfile for deployment. It also respects--shipto create a release build.
You always run bundle before package-solution. The sequence for a production deployment is: gulp bundle --ship → gulp package-solution --ship.
Q11. What is the App Catalog, and what are the two types?
The App Catalog is a special SharePoint site collection where you upload .sppkg packages to make them available across your environment. There are two scopes:
- Tenant App Catalog — managed by a SharePoint admin; solutions deployed here can be made available to the entire tenant
- Site Collection App Catalog — scoped to a specific site collection; useful for deploying solutions without tenant-admin involvement, giving teams more autonomy
Q12. How does SPFx handle authentication?
SPFx uses the currently logged-in SharePoint user’s credentials automatically — you don’t manage tokens or OAuth flows yourself for accessing SharePoint resources. The SPHttpClient and MSGraphClientV3 classes handle authenticated requests under the hood.
For external APIs, you declare permissions in package-solution.json under webApiPermissions, and a tenant admin approves those requests in the SharePoint Admin Center. Your code then gets tokens via this.context.aadTokenProviderFactory.
Q13. What is SPHttpClient and when do you use it?
SPHttpClient (from @microsoft/sp-http) is the built-in HTTP client for making REST API calls to SharePoint. It automatically includes the necessary authentication headers. You use it when calling SharePoint REST endpoints — for example, reading a list, creating an item, or managing site properties.
For Microsoft Graph calls, you use MSGraphClientV3 instead, which is the current recommended client (the older MSGraphClient is deprecated).
Q14. What is AadHttpClient, and when do you use it?
AadHttpClient is used to call secured external APIs that are protected by Azure Active Directory (AAD). Unlike SPHttpClient, which is used for SharePoint APIs, AadHttpClient automatically handles OAuth token acquisition for external services.
To use it, you must register an Azure AD application, declare the required permissions webApiPermissions inside package-solution.json, and have a tenant admin approve them. Once approved, SPFx handles token management, and your code can securely call the API without manually handling authentication flows.
Q15. What is MSGraphClientV3 and when should you use it?
MSGraphClientV3 is the recommended client in SPFx for calling Microsoft Graph APIs. It provides a fluent interface for interacting with Microsoft 365 services like users, mail, calendars, and groups.
It automatically handles authentication using the current user’s context. Like other secured API calls, required permissions must be declared in webApiPermissions and approved by a tenant admin before use.
It replaces older versions of the Graph client (MSGraphClient v1/v2), which are now deprecated.
Q16. What is PnP JS (PnPjs), and why do developers use it alongside SPFx?
PnPjs (formerly sp-pnp-js) is a community-driven TypeScript library that wraps the SharePoint REST API and Microsoft Graph in a fluent, chainable API. Instead of writing raw REST calls like /_api/web/lists/getbytitle('Tasks')/items, you can write sp.web.lists.getByTitle('Tasks').items().
It dramatically reduces boilerplate, handles batching, supports caching, and is actively maintained by the PnP community with regular updates. Most real-world SPFx projects use it.
Q17. What is the difference between render() being called once vs. re-rendering in SPFx?
In a basic SPFx web part, the render() method is called once when the web part loads. If you’re using vanilla HTML/JavaScript, you’d manually update the DOM for subsequent changes.
However, if you use React (which most teams do), the React component tree handles re-rendering based on state and props changes — your render() method simply mounts the root React component, and React takes care of the rest. This is a very common interview follow-up: knowing why you’d use React over vanilla DOM manipulation in SPFx.
Q18. How do web part properties work in SPFx?
Properties are defined in an interface (e.g., IMyWebPartProps) and stored in the web part manifest. In the property pane, you expose controls using the getPropertyPaneConfiguration() method — things like PropertyPaneTextField, PropertyPaneToggle, PropertyPaneDropdown, and so on.
These values persist to the page’s content database and survive page refreshes. Your render() method can access them via this.properties.yourPropertyName.
Q19. What are the two property pane interaction modes?
- Reactive mode (default) — the web part re-renders immediately every time a property pane value changes, giving users a live preview
- Non-reactive mode — changes are only applied after the user clicks “Apply”; useful when re-rendering is expensive or when you want to avoid partial states
You switch to non-reactive by overriding disableReactivePropertyChanges to return true.
Q20. What is the SPFx context object?
this.context is one of the most important objects in SPFx development. It gives you access to practically everything about the current environment: the current user (this.context.pageContext.user), site and web info (this.context.pageContext.site, this.context.pageContext.web), HTTP clients, the service scope, the Microsoft Teams context (when running in Teams), and more. Think of it as your gateway to the host environment.
Q21. What is a Service Scope in SPFx?
The Service Scope is SPFx’s built-in dependency injection container. Instead of importing services directly, you register them with the service scope and consume them via this.context.serviceScope.consume(MyService.serviceKey).
This makes your code more testable and modular — in unit tests, you can substitute mock implementations. It’s a pattern borrowed from enterprise-grade DI frameworks and it makes large SPFx projects significantly easier to maintain.
Q22. What is an Application Customizer, and what are placeholder regions?
An Application Customizer is an SPFx extension that runs on every page of a site (or tenant, if deployed that way) without the user placing it on a page. It uses placeholder regions — specifically PageHeader and PageFooter — to inject custom HTML into the top or bottom of the page.
A classic use case is a tenant-wide custom header, cookie banner, or notification bar. You access placeholders via this.context.placeholderProvider.
Q23. What is a Field Customizer extension?
A Field Customizer lets you completely replace how a column’s value is rendered in a list view. Instead of showing plain text, you could render a colored badge, a progress bar, a star rating — anything you want.
You implement the onRenderCell() method and use the provided event.domElement to inject your custom HTML. Keep in mind that as of early 2026, Field Customizer compatibility with the new SharePoint lists UI is something Microsoft is actively clarifying, so it’s worth validating behavior in your specific tenant.

Q24. How do you consume Microsoft Graph in an SPFx web part?
You use MSGraphClientV3, which is the current recommended client. First, you declare the necessary Graph permissions in package-solution.json under webApiPermissions. After a tenant admin approves the permission request in the SharePoint Admin Center, you can request the client like this:
this.context.msGraphClientFactory
.getClient('3')
.then((client: MSGraphClientV3) => {
client.api('/me').get((error, response) => {
// handle response
});
});
The '3' refers to the v3 API version of the client factory. The older MSGraphClient (v1) is deprecated and should not be used in new projects.
Q25. How do you call an external (non-SharePoint, non-Graph) REST API from SPFx?
Use HttpClient from @microsoft/sp-http. For unauthenticated external APIs, it works out of the box. For authenticated external APIs (like your own Azure-hosted APIs), you need to register an AAD application, declare the permission in webApiPermissions in package-solution.json, and then use AadHttpClient.
The tenant admin approves it, and your code gets tokens automatically. You never hard-code credentials or manually handle OAuth flows in SPFx — the framework does it for you.
Q26. What is the difference between tenant-scoped and site-scoped deployment?
- Tenant-scoped: Set
skipFeatureDeployment: trueinpackage-solution.json. Once the package is approved in the tenant App Catalog, the solution is immediately available across every site in the tenant without needing to be added to each site. - Site-scoped: The solution must be explicitly added to each site via the site’s “Add an app” page. More controlled, but more manual work.
Tenant-scoped deployment is great for things like Application Customizers that should run everywhere. Site-scoped is better when you only want a web part available in specific sites.
Q27. What is the SPFx solution manifest (package-solution.json) vs. the web part manifest?
They’re easy to mix up. The solution manifest (config/package-solution.json) describes the overall package — name, version, deployment settings, and API permissions. The web part manifest (src/webparts/yourWebPart/YourWebPart.manifest.json) describes the specific component — its GUID, display name, description, icon, and supported hosts (SharePoint, Teams, Viva Connections, etc.). Every web part has its own manifest; the solution can bundle multiple web parts.
Q28. How does SPFx support Microsoft Teams?
When you create a web part and set "TeamsTab" or "TeamsPersonalApp" in the supportedHosts array of the web part manifest, your web part can be added as a Teams tab. You can detect whether you’re running in Teams by checking this.context.sdks.microsoftTeams.
If it’s not undefined, you’re in Teams and can use Teams-specific context like channel name, team ID, and locale. Many organizations use this to build once and deploy to both SharePoint and Teams.
Q29. What is Fluent UI (formerly Office UI Fabric), and why does it matter in SPFx?
Fluent UI is Microsoft’s design system — the same component library used to build Microsoft 365‘s own interfaces. Using it in your SPFx web parts means your custom UI looks and feels native to the Microsoft 365 experience: consistent fonts, colors, spacing, accessibility, and responsive behavior.
Microsoft bundles it with SPFx so you don’t need to pull it in as an extra dependency. In 2026, the recommended approach is Fluent UI React v9 (also called Fluent UI React Components), though v8 is still commonly seen in existing codebases.
Q30. How do you handle localization (multi-language support) in SPFx?
SPFx has built-in localization support using resource files. In your web part’s loc folder, you define a default strings file (e.g., en-us.js) and additional locale files for other languages. Each file exports an object with key-value pairs. In your component, you import the strings via import * as strings from 'YourWebPartStrings' and reference them as strings.SomeLabel. At runtime, SPFx automatically loads the right locale file based on the user’s SharePoint language setting.
Q31. What are Adaptive Card Extensions (ACEs), and where are they used?
ACEs are SPFx components designed specifically for the Viva Connections dashboard. They render as cards using the Adaptive Card format — a JSON-based card schema — and support both a compact card view and a quick view (a richer expanded panel).
They’re particularly useful for surfacing line-of-business data (pending approvals, leave balances, IT tickets) directly in Viva Connections. ACEs follow the same SPFx development model but have their own base class: BaseAdaptiveCardExtension.
Q32. What is the purpose of the feature.xml in a SharePoint solution, and does it still apply to SPFx?
In classic SharePoint solutions, feature.xml was used to define Features that provisioned artifacts on activation. In SPFx, the role of features is greatly diminished. SPFx has its own feature framework wrapper for provisioning elements like App Customizer activations, but you typically don’t write raw feature.xml files.
For any serious provisioning work (creating lists, content types, columns), most teams use PnP PowerShell or the PnP Provisioning engine instead — not SPFx features.
Q33. How does SPFx handle versioning?
There are two separate versions to track. The package version in package-solution.json controls the .sppkg version — increment it when you redeploy to the App Catalog. The component version in the web part manifest controls the client-side assets loaded from the CDN.
These should be bumped together to avoid cache issues. SPFx uses content hashing on bundle output files so browsers don’t serve stale cached assets after a deployment, but you still need to manage the package version to trigger an update in the App Catalog.
Q34. Why is React the most commonly used framework in SPFx?
React is the framework Microsoft itself uses to build SharePoint Online’s UI. The scaffolded SPFx project template optionally includes React out of the box, Fluent UI is built on React, and the SPFx community has standardized on it.
That said, SPFx is framework-agnostic — you can use Vue, Angular, or plain JavaScript. But for most teams, React is the path of least resistance, and the ecosystem of samples, PnP components, and documentation is overwhelmingly React-focused.
Q35. What version of React does SPFx currently support?
As of early 2026, SPFx ships with React 17 and that remains the bundled version. Microsoft has acknowledged this is overdue for an update — moving to React 18 requires coordinating with the internal first-party web parts that are also built on React.
There is active work to enable React 18 support, but no firm release date has been announced as of the February 2026 roadmap update. This means features like use(), Suspense with async data fetching, and concurrent rendering are not yet officially supported in SPFx.
Q36. What is the role of ReactDOM.render() in an SPFx web part?
In the web part’s render() method, you typically call ReactDOM.render() to mount your root React component into this.domElement — the HTML element SPFx gives you as your web part’s container.
In React 18, you’d normally use createRoot().render(), but since SPFx currently targets React 17, ReactDOM.render() is still the standard approach. One important thing: also implement onDispose() to call ReactDOM.unmountComponentAtNode(this.domElement) and clean up properly.
Q37. How do you share state between multiple web parts on the same page?
SPFx doesn’t give you a shared state store across web parts by default — each web part runs in its own isolated context. The common patterns are:
- SPFx Dynamic Data — a built-in publish/subscribe mechanism where one web part exposes data as a “Dynamic Data Source” and other web parts subscribe to it
- localStorage or sessionStorage — works but has security implications and is not SharePoint-aware
- SharePoint list — the cleanest enterprise pattern; treat the list as a shared backend store
For most real-world scenarios, either SPFx Dynamic Data or simply storing state in a SharePoint list is the right answer.
Q38. What is the recommended way to load external libraries in SPFx without bloating the bundle?
There are two common approaches. First, you can externalize libraries in config/config.json under the externals key, pointing to a CDN URL — this tells Webpack not to bundle the library and to load it from the CDN at runtime instead. Second, for libraries that you do bundle, use dynamic import() (code splitting) so heavy modules are only loaded when actually needed. Bundling everything including large libraries like lodash or moment into a single file is one of the most common performance mistakes in SPFx projects.
Q39. How does the SPFx build system work under the hood, and what changed in version 1.22?
Prior to SPFx 1.22, the build system was orchestrated by Gulp with a set of tasks chaining TypeScript compilation, Webpack bundling, SASS compilation, and packaging. Starting from SPFx 1.22 (released December 2025), Microsoft transitioned to a Webpack-based toolchain orchestrated by Heft (a build tool from the Rush Stack ecosystem).
This removes Gulp as a direct dependency, resolves long-standing npm audit vulnerabilities in the build pipeline, and modernizes the foundation. TypeScript also moved to v5.8 as the default in 1.22.
Q40. What is the new SPFx CLI replacing Yeoman?
The new SPFx CLI is an open-source replacement for the Yeoman-based generator (@microsoft/generator-sharepoint). It’s being decoupled from specific SPFx release versions — meaning you can update the CLI independently without waiting for a new SPFx release.
A preview release is expected with SPFx 1.23.1 in April 2026. Crucially, Microsoft is open-sourcing the solution templates on GitHub and allowing community contributions, so organizations will be able to fork and customize scaffolding templates for their own standards.
Q41. What are the best practices for handling errors in SPFx web parts?
- Always wrap async calls in try/catch and handle both network errors and API errors separately
- Render a meaningful error state in the UI rather than showing a blank web part
- Use SPFx logging (
Log.error()from@microsoft/sp-core-library) for diagnostics — it integrates with the browser console and can be captured server-side - Avoid swallowing errors silently
- For production, consider Azure Application Insights integration by adding the App Insights SDK and logging errors there for centralized monitoring
Q42. How do you optimize SPFx web part load time?
- Use
--shipbuilds (production bundles) — debug builds are much larger - Externalize large third-party libraries rather than bundling them
- Lazy-load components that aren’t immediately visible using React’s
lazy()andSuspense(noting the React 17 limitation in SPFx today) - Use the SharePoint CDN for your assets (configured in the tenant admin center) rather than serving from an Azure Storage account manually
- Minimize
webApiPermissions— each permission approval adds overhead at load time - Avoid loading data in
render()unnecessarily; use proper lifecycle hooks
Q43. What is the SharePoint CDN, and how does it affect SPFx deployment?
The SharePoint CDN (Content Delivery Network) can be enabled at the tenant level to automatically cache and serve static files (JavaScript, CSS, images) from Microsoft’s global CDN infrastructure.
When you deploy an SPFx solution and the CDN is enabled, your bundle files are served from the nearest CDN node rather than from your SharePoint tenant. This can significantly improve load times for global organizations. You configure CDN origins in the SharePoint Admin Center or via PowerShell.
Q44. What is the webApiPermissions model in SPFx, and why does it require admin approval?
When your SPFx solution needs to call external APIs (Microsoft Graph, custom Azure APIs, etc.), you declare the required OAuth scopes in package-solution.json under webApiPermissions. This is intentional — Microsoft doesn’t let SPFx code silently acquire tokens for arbitrary APIs without oversight.
A tenant or SharePoint admin must explicitly approve each permission request in the SharePoint Admin Center API Access page. Once approved, the permission is granted to the entire tenant’s SPFx runtime, not just your specific web part. This is both a feature and something to be aware of — it’s a tenant-wide grant.
Q45. How does Content Security Policy (CSP) impact SPFx development?
SharePoint Online enforces a Content Security Policy that restricts what resources can be loaded on SharePoint pages. If you try to load scripts or stylesheets from an external domain not on the allowlist, they’ll be blocked.
The right approach is to either externalize dependencies using the externals mechanism (pointing to CDN URLs that are already permitted) or use the SharePoint CDN for your own assets. Embedding inline scripts using eval() is also blocked by CSP and will cause errors — this catches developers who rely on certain older libraries.
Q46. What is Isolated Web Part mode in SPFx?
Isolated Web Part mode (controlled by the isDomainIsolated flag in the web part manifest) causes your web part to run inside a cross-domain <iframe> served from a separate domain ({tenant}-api.sharepoint.com).
This sandboxes your web part completely — it gets its own separate token and cannot directly access the parent page’s DOM or SharePoint context. It was designed for scenarios where you need to call external APIs with elevated permissions without exposing tokens to the parent page. It has usability trade-offs though, and isn’t suitable for all scenarios.
Q47. What is SPFx Dynamic Data, and how does it work?
Dynamic Data is SPFx’s built-in pub/sub mechanism for sharing data between web parts on the same page. A “source” web part implements the IDynamicDataCallables interface and registers itself with this.context.dynamicDataSourceManager.
A “consumer” web part references the source and subscribes to data changes using this.context.dynamicDataProvider. When the source pushes an update, all registered consumers are notified. It’s commonly used for “map + list” scenarios where selecting a row in one web part filters results in another.
Q48. How do you implement SPFx solutions for Microsoft Viva Connections?
Viva Connections is the employee experience app in Microsoft Teams, and SPFx is the primary extensibility model for it. You can build:
- Adaptive Card Extensions (ACEs) for the Viva Connections dashboard — these are the card-based components users see in the Teams sidebar
- Web parts that render inside the Viva Connections home experience (which is essentially a SharePoint page)
For ACEs, you inherit from BaseAdaptiveCardExtension, define card views and quick views using Adaptive Card templates, and handle actions in onAction(). The card data is fetched the same way as in web parts — via Graph, SharePoint REST, or external APIs.
Q49. What is the Command Set improvements feature coming in SPFx 1.23?
SPFx 1.23 (releasing in March 2026) brings Command Set improvements for lists and libraries, including the ability to group commands. Previously, all custom buttons added via Command Sets appeared flat in the toolbar with no hierarchy.
Grouping allows you to organize related actions under a parent menu, which reduces toolbar clutter in lists where multiple Command Sets are deployed. This is particularly valuable for enterprise deployments where many customizations coexist on the same list.
Q50. What is the Panel Override extension point coming in SPFx 1.23.1?
The new and edit panel override (planned for SPFx 1.23.1, April 2026) allows SPFx components to take over the form panel shown when a user creates or edits a list item in Microsoft Lists or SharePoint lists.
Until now, customizing list forms required building a separate List Form Customizer — this new capability provides a lighter-weight panel-level override specifically for the slide-out panel experience. It gives developers control over the form UI without replacing the entire form.
Q51. How does SPFx integrate with Microsoft Graph and what’s the recommended client in 2026?
The recommended client is MSGraphClientV3, obtained via this.context.msGraphClientFactory.getClient('3'). The older MSGraphClient (v1/v2) is deprecated. MSGraphClientV3 supports the full Microsoft Graph v1.0 and beta endpoints, handles token acquisition automatically, and supports batching requests.
For complex Graph interactions, PnPjs’s Graph module wraps MSGraphClientV3 in a fluent API that’s much easier to work with than raw HTTP calls.
Q52. How do you upgrade an existing SPFx project to a newer version?
The SPFx tooling includes an upgrade command via the @microsoft/office365-cli or the newer Microsoft 365 CLI (m365): m365 spfx project upgrade --output md. This generates a step-by-step markdown report listing every change you need to make — package version bumps, configuration file changes, removed deprecated APIs.
It doesn’t apply the changes automatically, but the report is quite detailed. For major version jumps, plan for testing time — especially when moving from Gulp-based builds to the new Heft toolchain introduced in 1.22.
Q53. What does it mean that SPFx solution templates are being open-sourced?
Starting with SPFx 1.23 and 1.23.1, Microsoft is publishing the project scaffolding templates — the files the generator creates when you run yo @microsoft/sharepoint (or the new CLI equivalent) — to a public GitHub repository. This means the community can contribute improvements, bug fixes, or additional template options.
More practically for enterprise teams, it means you can fork the templates and add your own organizational defaults: pre-configured linting rules, specific folder structures, your company’s base components, or standard API wrappers. This is a meaningful shift toward community-driven tooling.
Q54. What does the future of SPFx look like going into the rest of 2026?
Based on the February 2026 roadmap update, the near-term priorities are:
- SPFx 1.23 (March 2026) — command set improvements, npm audit fixes, and technical platform updates
- SPFx 1.23.1 (April 2026) — new SPFx CLI preview, panel override for lists, open-sourced templates
- SPFx 1.24 (June 2026) — navigation customizers for overriding SharePoint navigation with custom SPFx components
- AI integration — Microsoft is actively working on AI-powered features for both the developer tooling and the solutions you can build (think Copilot extensibility, AI-powered web parts, agents on SharePoint content)
The platform is moving to a quarterly release cadence for more predictability, and the emphasis on developer experience — better debugging tools, open-source tooling, faster TypeScript upgrades — signals that Microsoft is serious about keeping SPFx as a first-class development model for Microsoft 365.
Q55. How do you scaffold a new SPFx web part project using the new toolchain in 2026?
With SPFx 1.22+, the scaffolding command itself hasn’t changed yet — you still use Yeoman for now — but the generated project no longer uses Gulp as the primary build runner. Here’s the scaffold command:
yo @microsoft/sharepoint
When prompted, select WebPart as the component type, choose React as the framework, and make sure you target SharePoint Online only (latest) for new greenfield projects. After scaffolding, notice that package.json now references @microsoft/spfx-heft-plugins and @rushstack/heft rather than the old @microsoft/sp-build-web Gulp setup. The command to start the dev server is now:
heft start
Instead of gulp serve. Under the hood, it’s the same local HTTPS server on port 4321 — the experience is familiar, just without Gulp in the chain.
Q56. How do you properly initialize PnPjs v4 in an SPFx web part?
You set it up in onInit() — not in the constructor, not in render(). The constructor runs too early; this.context isn’t available yet. Here’s a clean pattern using a shared config module:
pnpjsConfig.ts
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { spfi, SPFI, SPFx } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
let _sp: SPFI | null = null;
export const getSP = (context?: WebPartContext): SPFI => {
if (context != null) {
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp!;
};
YourWebPart.ts
import { getSP } from './pnpjsConfig';
public async onInit(): Promise<void> {
await super.onInit();
getSP(this.context); // initialize once; reuse everywhere
}
After this, any component in your project can call getSP() (no argument) to get the pre-configured SPFI instance. You never need to pass the context around as a prop.
Q57. How do you read items from a SharePoint list with PnPjs and display them in a React component?
Here’s a realistic, minimal example. It reads items from a “Tasks” list and renders them:
import * as React from 'react';
import { getSP } from '../pnpjsConfig';
import "@pnp/sp/lists";
import "@pnp/sp/items";
interface ITask {
Id: number;
Title: string;
Status: string;
}
const TaskList: React.FC = () => {
const [tasks, setTasks] = React.useState<ITask[]>([]);
const [error, setError] = React.useState<string>('');
React.useEffect(() => {
const sp = getSP();
sp.web.lists.getByTitle('Tasks').items
.select('Id', 'Title', 'Status')
.top(50)()
.then((items: ITask[]) => setTasks(items))
.catch((err) => setError(`Failed to load: ${err.message}`));
}, []);
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<ul>
{tasks.map(t => (
<li key={t.Id}>{t.Title} — {t.Status}</li>
))}
</ul>
);
};
export default TaskList;
The React.useEffect with an empty dependency array runs once when the component mounts — that’s the right place for your initial data fetch in a functional component. Notice the .top(50) — always paginate or limit your queries. Never fetch all items without a ceiling in a production web part.
Q58. How do you create a new SharePoint List item using PnPjs?
You can use the below SPFx code to create a new SharePoint list item using PnPJS.
import { getSP } from '../pnpjsConfig';
import "@pnp/sp/lists";
import "@pnp/sp/items";
const createTask = async (title: string, priority: string): Promise<void> => {
const sp = getSP();
try {
const result = await sp.web.lists.getByTitle('Tasks').items.add({
Title: title,
Priority: priority
});
console.log(`Created item with ID: ${result.data.Id}`);
} catch (err) {
console.error('Failed to create item:', err);
}
};
The .add() method returns an object that includes data — the fully created item, including its server-generated Id. If you need to do something with the newly created item immediately (like navigating to a detail view), grab the ID from result.data.Id.
Q59. How do you update and delete SharePoint list items using PnPjs?
// Update
const updateTask = async (itemId: number, newTitle: string): Promise<void> => {
const sp = getSP();
await sp.web.lists.getByTitle('Tasks').items.getById(itemId).update({
Title: newTitle
});
};
// Delete
const deleteTask = async (itemId: number): Promise<void> => {
const sp = getSP();
await sp.web.lists.getByTitle('Tasks').items.getById(itemId).delete();
};
Always wrap these in try/catch in real code. For .update(), you only need to pass the fields you want to change — you don’t need to pass the entire item object. For batch operations (updating dozens of items at once), use PnPjs batching (sp.batched()) rather than firing individual requests in a loop.
Q60. How do you implement batched requests in PnPjs to avoid throttling?
When you need to update many items, firing individual requests in a for loop will hit SharePoint’s throttling limits quickly. Use PnPjs’s batched() method:
const batchUpdateItems = async (items: { id: number; title: string }[]): Promise<void> => {
const sp = getSP();
const [batchedSP, execute] = sp.batched();
const results: Promise<any>[] = [];
for (const item of items) {
batchedSP.web.lists.getByTitle('Tasks').items
.getById(item.id)
.update({ Title: item.title })
.then(r => results.push(r));
}
await execute(); // sends all updates as a single $batch request
console.log(`Batched ${results.length} updates`);
};
Under the hood, this uses the SharePoint $batch OData endpoint, which bundles multiple operations into one HTTP request. It’s faster and far more resilient to throttling than individual calls.
Q61. How do you call Microsoft Graph from a web part using MSGraphClientV3?
First, declare the permissions you need in config/package-solution.json:
"webApiPermissions": [
{
"resource": "Microsoft Graph",
"scope": "User.Read"
},
{
"resource": "Microsoft Graph",
"scope": "Mail.Read"
}
]
Then, in your web part:
import { MSGraphClientV3 } from '@microsoft/sp-http';
private _getUserProfile = async (): Promise<void> => {
const client: MSGraphClientV3 = await this.context.msGraphClientFactory.getClient('3');
const response = await client.api('/me').get();
console.log(`Logged in as: ${response.displayName}`);
// Fetch messages
const messages = await client.api('/me/messages').top(5).get();
console.log(messages.value);
};
The '3' in getClient('3') is not an API version — it refers to the v3 client factory interface. Always use this; the old this.context.msGraphClientFactory.getClient() without the argument is the deprecated v1/v2 interface.
Q62. How do you use the onPropertyPaneFieldChanged lifecycle method and when do you need it?
By default, SPFx automatically updates this.properties When a property pane control changes and re-renders the web part. But sometimes you need to react to a specific property change — for instance, when a dropdown selection should dynamically populate another dropdown. You override onPropertyPaneFieldChanged:
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
if (propertyPath === 'selectedList' && newValue !== oldValue) {
// The user picked a different list — reload the columns for that list
this._loadColumnsForList(newValue).then(() => {
this.context.propertyPane.refresh(); // refresh the property pane UI
});
}
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}
Always call super.onPropertyPaneFieldChanged() — Skipping it breaks the built-in property binding. The context.propertyPane.refresh() call tells SPFx to re-render the property pane controls, which is how you make a second dropdown “react” to the first one.
Q63. How do you dynamically populate a dropdown in the property pane — for example, loading list names from SharePoint?
This is one of the most common real-world requirements and one that trips up intermediate developers. The trick is loading the data asynchronously before the property pane opens:
private _listOptions: IPropertyPaneDropdownOption[] = [];
private _listsLoaded: boolean = false;
protected onPropertyPaneOpening(): void {
if (!this._listsLoaded) {
this._loadLists();
}
}
private async _loadLists(): Promise<void> {
const sp = getSP();
const lists = await sp.web.lists
.filter("Hidden eq false and BaseTemplate eq 100")
.select("Title", "Id")();
this._listOptions = lists.map(l => ({ key: l.Id, text: l.Title }));
this._listsLoaded = true;
this.context.propertyPane.refresh();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [{
groups: [{
groupFields: [
PropertyPaneDropdown('selectedList', {
label: 'Select a list',
options: this._listOptions,
disabled: !this._listsLoaded
})
]
}]
}]
};
}
The onPropertyPaneOpening() lifecycle hook fires right before the property pane is shown — that’s your window to fetch data. This disabled: !this._listsLoaded gives users a visual cue that it’s loading.
Q64. How do you make a web part respond to theme changes in SharePoint?
SPFx has built-in theme awareness via the ThemeProvider service. Here’s how to set it up and use it in a React web part:
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme } from '@microsoft/sp-component-base';
private _themeProvider: ThemeProvider;
private _themeVariant: IReadonlyTheme | undefined;
protected onInit(): Promise<void> {
this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);
this._themeVariant = this._themeProvider.tryGetTheme();
this._themeProvider.themeChangedEvent.add(this, this._handleThemeChanged);
return super.onInit();
}
private _handleThemeChanged = (args: ThemeChangedEventArgs): void => {
this._themeVariant = args.theme;
this.render(); // re-render the web part with new theme
};
// In your React component, pass theme as a prop
public render(): void {
const element = React.createElement(MyComponent, {
themeVariant: this._themeVariant
});
ReactDOM.render(element, this.domElement);
}
In your React component, you can then use themeVariant.palette.themePrimary and other palette tokens to apply the theme colors to your UI. This ensures your web part adapts correctly when users switch between light/dark modes or tenant-level themes.
Q65. How do you implement caching in PnPjs to avoid redundant API calls?
PnPjs has a Caching behavior you can apply selectively:
import { Caching } from "@pnp/queryable";
import { spfi } from "@pnp/sp";
// Option 1: Create a cached sp instance for specific queries
const spCache = spfi(getSP()).using(Caching({ store: "session" }));
const lists = await spCache.web.lists.select("Title", "Id")();
// First call hits SharePoint; second call (same session) returns from sessionStorage
You can use "session" (sessionStorage) or "local" (localStorage) as the store. Session cache is cleared when the browser tab closes; local cache persists across sessions. For data that changes rarely (like site metadata or lookup column options), session cache is usually the right choice. Don’t cache user-specific or frequently mutating data — you’ll end up showing stale information.
Q66. How do you build a custom header using an Application Customizer?
You can use the below code to build a custom header using an application customizer in SharePoint Framework.
typescriptimport { BaseApplicationCustomizer, PlaceholderContent, PlaceholderName } from '@microsoft/sp-application-base';
export default class HeaderCustomizer extends BaseApplicationCustomizer<{}> {
private _headerPlaceholder: PlaceholderContent | undefined;
public onInit(): Promise<void> {
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceholders);
this._renderPlaceholders();
return Promise.resolve();
}
private _renderPlaceholders = (): void => {
if (!this._headerPlaceholder) {
this._headerPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this._onDispose }
);
}
if (this._headerPlaceholder && this._headerPlaceholder.domElement) {
this._headerPlaceholder.domElement.innerHTML = `
<div style="background: #0078d4; color: white; padding: 8px 16px; font-size: 14px;">
🔒 Internal use only — Contoso intranet
</div>
`;
}
};
private _onDispose = (): void => {
console.log('Header placeholder disposed');
};
}
A few things to note: always subscribe to placeholderProvider.changedEvent so your placeholder re-renders if SharePoint swaps page content dynamically. And don’t rely on PlaceholderName.Top being available immediately on onInit() — sometimes it’s not ready until the event fires.
Q67. How do you deploy an Application Customizer to a site automatically without users having to add it?
Extensions like Application Customizers are deployed automatically via a Custom Action — not by users adding them from the web part picker. When you include a Custom Action in your SPFx package (defined in the elements.xml under your feature), it gets activated when the app is installed on a site. Here’s the relevant section of elements.xml:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Title="HeaderCustomizer"
Location="ClientSideExtension.ApplicationCustomizer"
ClientSideComponentId="YOUR-COMPONENT-GUID-HERE"
ClientSideComponentProperties="{"message":"Hello from header"}">
</CustomAction>
</Elements>
If you want it tenant-wide (without installing on each site), use skipFeatureDeployment: true in package-solution.json and deploy via the tenant App Catalog. For programmatic deployment across multiple sites, use PnP PowerShell’s Add-PnPCustomAction cmdlet.
Q68. How do you add properties to an Application Customizer and read them at runtime?
First, define a properties interface:
export interface IHeaderCustomizerProperties {
message: string;
backgroundColor: string;
}
export default class HeaderCustomizer
extends BaseApplicationCustomizer<IHeaderCustomizerProperties> {
public onInit(): Promise<void> {
const msg = this.properties.message || 'Default message';
const bgColor = this.properties.backgroundColor || '#0078d4';
// Use the properties when rendering
this._renderHeader(msg, bgColor);
return Promise.resolve();
}
}
The ClientSideComponentProperties JSON from the Custom Action registration is automatically deserialized into this.properties. It’s the same pattern as web part properties, but instead of the property pane, the values are set by the administrator during deployment (or via PnP PowerShell’s -ClientSideComponentProperties parameter).
Q69. How do you build a Command Set extension and add a custom button to a list toolbar?
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetExecuteEventParameters,
ListViewStateChangedEventArgs
} from '@microsoft/sp-listview-extensibility';
export default class MyCommandSet
extends BaseListViewCommandSet<{}> {
public onInit(): Promise<void> {
// Bind state change event to update button visibility
this.context.listView.listViewStateChangedEvent.add(
this, this._onListViewStateChanged
);
return Promise.resolve();
}
private _onListViewStateChanged = (args: ListViewStateChangedEventArgs): void => {
const exportCmd: Command = this.tryGetCommand('EXPORT_ITEM');
if (exportCmd) {
// Only show button when exactly 1 item is selected
exportCmd.visible = this.context.listView.selectedRows?.length === 1;
}
this.raiseOnChange();
};
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'EXPORT_ITEM':
const selectedRow = event.selectedRows[0];
const itemId = selectedRow.getValueByName('ID');
alert(`Exporting item ID: ${itemId}`);
break;
default:
throw new Error('Unknown command');
}
}
}
The tryGetCommand() method uses the command’s id from the manifest. You control visibility and enabled state through the Command object’s visible and isEnabled properties, and always call this.raiseOnChange() to commit the state update to the UI.
Q70. How do you pass data between a Command Set and an external service (like an Azure Function)?
Inside the onExecute handler, you have access to this.context just like in a web part. You can use HttpClient or AadHttpClient:
public async onExecute(event: IListViewCommandSetExecuteEventParameters): Promise<void> {
if (event.itemId === 'SEND_TO_WORKFLOW') {
const selectedRow = event.selectedRows[0];
const itemId = selectedRow.getValueByName('ID');
const listId = this.context.pageContext.list?.id.toString();
const client = await this.context.aadHttpClientFactory
.getClient('https://your-function-app.azurewebsites.net');
const response = await client.post(
'https://your-function-app.azurewebsites.net/api/triggerWorkflow',
AadHttpClient.configurations.v1,
{
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itemId, listId })
}
);
if (response.ok) {
alert('Workflow triggered successfully!');
}
}
}
The AadHttpClient handles the Azure AD OAuth token silently — no pop-ups, no manual token management. The token is scoped to the registered AAD application for your Azure Function, and the permission must have been approved in the SharePoint Admin Center.
Q71. How do you build a Field Customizer to render a color-coded status badge?
import { BaseFieldCustomizer, IFieldCustomizerCellEventParameters } from '@microsoft/sp-listview-extensibility';
export default class StatusBadgeCustomizer extends BaseFieldCustomizer<{}> {
private _getColor(status: string): string {
const colorMap: { [key: string]: string } = {
'Completed': '#107C10',
'In Progress': '#0078D4',
'Not Started': '#797775',
'Blocked': '#D13438'
};
return colorMap[status] || '#797775';
}
public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
const status: string = event.fieldValue || 'Unknown';
const color = this._getColor(status);
event.domElement.innerHTML = `
<span style="
background-color: ${color};
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
">${status}</span>
`;
}
public onDisposeCell(event: IFieldCustomizerCellEventParameters): void {
// Clean up if needed
super.onDisposeCell(event);
}
}
The onRenderCell() method fires for every row in the list view — it needs to be fast. Avoid async calls inside onRenderCell() if you can; if you need additional data per row, batch-fetch it in onInit() and store it in a local map, then use the map during rendering.
Q72. How do you implement the onDisposeCell method correctly in a Field Customizer?
onDisposeCell() is called when a cell is removed from the DOM — for example, when the user sorts, filters, or pages the list. If you’ve added event listeners inside onRenderCell(), you must clean them up here to prevent memory leaks:
public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
const button = document.createElement('button');
button.textContent = 'View Details';
button.addEventListener('click', () => this._handleClick(event.row));
event.domElement.appendChild(button);
// Store reference for cleanup
(event.domElement as any).__clickHandler = () => this._handleClick(event.row);
}
public onDisposeCell(event: IFieldCustomizerCellEventParameters): void {
const btn = event.domElement.querySelector('button');
if (btn && (event.domElement as any).__clickHandler) {
btn.removeEventListener('click', (event.domElement as any).__clickHandler);
}
super.onDisposeCell(event);
}
In practice, the simplest way to avoid this cleanup overhead is to use event delegation — add a single listener on the domElement itself rather than on child elements, since domElement is reused and managed by SPFx.
Q73. What is the full SPFx web part lifecycle, and in what order do the methods fire?
Understanding the full lifecycle prevents a lot of subtle bugs. Here’s the order for a standard web part:
- Constructor — runs first, before
this.contextorthis.propertiesare available. Don’t put initialization logic here. onInit()— your main initialization hook.this.contextandthis.propertiesare ready here. Returns aPromise<void>— use this for async setup.render()— called afteronInit()resolves. Renders the web part UI intothis.domElement. Called again if the web part is configured for reactive property pane changes.onPropertyPaneOpening()— fires when the user opens the property pane.onPropertyPaneFieldChanged()— fires on each property pane control change.onPropertyPaneConfigurationComplete()— fires when the property pane is closed.onThemeChanged()— fires when the page theme changes.onDispose()— fires when the web part is removed from the page. Clean up React mounts, event listeners, and subscriptions here.
The most common mistake is putting async initialization in the constructor instead of onInit(), which silently fails because the context isn’t ready yet.
Q74. How do you properly clean up a React-based SPFx web part in onDispose()?
Here is how to properly clean up a react based SPFx web part using onDispose() method in SPFx.
import * as ReactDOM from 'react-dom';
public onDispose(): void {
// Unmount the React tree to prevent memory leaks
ReactDOM.unmountComponentAtNode(this.domElement);
// Unsubscribe from any events
if (this._themeProvider) {
this._themeProvider.themeChangedEvent.remove(this, this._handleThemeChanged);
}
super.onDispose();
}
Always call super.onDispose() at the end. If you’ve subscribed to placeholderProvider.changedEvent, listViewStateChangedEvent, or any SPFx event hub subscription during onInit(), remove those subscriptions in onDispose().
Leaving them in place means the handlers keep firing on page navigation events, which causes errors and memory leaks in long-running SPA scenarios.
Q75. How do you support multiple web part instances on the same page without conflicts?
Each SPFx web part instance gets its own unique this.instanceId and its own this.domElement. For React-based web parts, since each call to ReactDOM.render() targets a different domElement, they’re naturally isolated. Where things go wrong is with shared global state — if you store data in a module-level variable, all instances on the page share it:
// BAD — all instances share this
let cachedData: IItem[] = [];
// GOOD — instance-specific state inside the class
export default class MyWebPart extends BaseClientSideWebPart<IMyProps> {
private _cachedData: IItem[] = []; // each instance has its own copy
}
For the case where you want instances to share data (like a filter web part driving a results web part), use SPFx Dynamic Data — it’s the framework-native way to communicate between instances without coupling them directly.
Q76. How do you make an SPFx web part full-width on a SharePoint page?
In the web part manifest, set:
{
"supportsFullBleed": true
}
With this flag, when a user places the web part in a full-width column section on a modern SharePoint page, it stretches edge-to-edge, ignoring the standard content gutters. In your web part code, you can detect whether you’re currently rendering in full-bleed mode by checking this.displayMode and this.domElement.parentElement width, and adjust your layout accordingly. Not all page layouts support full-bleed sections — it only works with the specific full-width column layout type.
Q77. How do you access the current user’s information inside an SPFx web part?
Below is the code you can use to access the current user’s information inside a SPFx web part.
// Basic user info — available synchronously via page context
const currentUser = this.context.pageContext.user;
console.log(currentUser.displayName); // "Jane Smith"
console.log(currentUser.email); // "jane@contoso.com"
console.log(currentUser.loginName); // "i:0#.f|membership|jane@contoso.com"
// More detailed user info — requires a REST call
const getUserDetails = async (): Promise<void> => {
const sp = getSP();
const user = await sp.web.currentUser
.select('Title', 'Email', 'JobTitle', 'Department')();
console.log(user.JobTitle, user.Department);
};
// Groups the user belongs to
const getUserGroups = async (): Promise<void> => {
const sp = getSP();
const groups = await sp.web.currentUser.groups();
const isMember = groups.some(g => g.Title === 'HR Managers');
};
pageContext.user is great for display purposes. For permission checks or group membership, you need the REST API call — the page context doesn’t include group information.
Q78. How do you unit test SPFx web parts?
SPFx ships with Jest as the testing framework out of the box. The scaffolded project includes a basic test file. For component-level testing, you use @testing-library/react:
// MyComponent.test.tsx
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from '../components/MyComponent';
describe('MyComponent', () => {
it('renders the title prop', () => {
render(<MyComponent title="Hello SPFx" items={[]} />);
expect(screen.getByText('Hello SPFx')).toBeTruthy();
});
it('shows empty state when no items', () => {
render(<MyComponent title="Tasks" items={[]} />);
expect(screen.getByText('No items found')).toBeTruthy();
});
});
Run tests with:
heft test # run tests once
heft test --watch # watch mode
For testing code that depends on this.context (like SPFx service calls), use the Service Scope pattern — inject a mock service via the service scope rather than making real API calls in tests.
Q79. How do you debug SPFx extensions like Application Customizers and Command Sets locally?
Unlike web parts, you can’t use the local workbench for extensions — they need a live SharePoint page. The serve.json file handles this. Here’s what a properly configured serve configuration looks like for an Application Customizer:
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
"port": 4321,
"https": true,
"serveConfigurations": {
"default": {
"pageUrl": "https://contoso.sharepoint.com/sites/dev/SitePages/Home.aspx",
"customActions": {
"YOUR-COMPONENT-GUID": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"message": "Debug mode active"
}
}
}
}
}
}
Run heft start (or gulp serve in older projects), navigate to the pageUrl in your browser, and when prompted, click Load debug scripts. Your extension runs from your localhost server. The new in-page debugging toolbar that started rolling out in February 2026 is making this flow even smoother — you can toggle debug mode directly on a page without the query parameter dance.
Q80. How do you detect whether your SPFx web part is running inside Microsoft Teams and adjust the UI accordingly?
import { IReadonlyTheme } from '@microsoft/sp-component-base';
// In your web part class
public render(): void {
const isTeams = !!this.context.sdks.microsoftTeams;
if (isTeams) {
// Access Teams-specific context
const teamsContext = this.context.sdks.microsoftTeams.context;
const teamName = teamsContext.team?.displayName;
const channelName = teamsContext.channel?.displayName;
const theme = teamsContext.app?.theme; // 'default' | 'dark' | 'contrast'
console.log(`Running in Teams: ${teamName} > ${channelName}`);
}
const element = React.createElement(MyComponent, {
isTeams,
title: this.properties.title,
context: this.context
});
ReactDOM.render(element, this.domElement);
}
In your React component, use the isTeams prop to adjust layout — for example, hiding SharePoint-specific navigation links, adapting your color scheme to the Teams dark theme, or showing Teams-specific actions like posting to a channel.
Q81. How do you build a basic Adaptive Card Extension (ACE) for Viva Connections?
Here’s the essential structure of a minimal ACE:
import {
BaseAdaptiveCardExtension,
IPropertyPaneConfiguration
} from '@microsoft/sp-adaptive-card-extension-base';
interface IMyACEState {
pendingApprovals: number;
}
export default class ApprovalsACE
extends BaseAdaptiveCardExtension<{}, IMyACEState> {
public async onInit(): Promise<void> {
this.state = { pendingApprovals: 0 };
this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView());
this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView());
// Load data
const count = await this._getPendingApprovals();
this.setState({ pendingApprovals: count });
}
protected getCardViewParameters() {
return {
primaryText: `${this.state.pendingApprovals} pending approvals`,
buttons: [{ title: 'View All', action: { type: 'QuickView', parameters: { view: QUICK_VIEW_REGISTRY_ID } } }]
};
}
}
ACEs follow a strict model: the card view is the compact face of the card visible on the dashboard, and the quick view is the expanded panel that opens when users interact with it. Both are separate classes that you register in onInit(). The state object drives both views — calling this.setState() re-renders both automatically.
Final Thoughts
Eighty-one questions is a lot to work through, and if you’ve made it here, you’re already more prepared than most candidates walking into an SPFx interview.
But here’s what I want to leave you with: the developers who genuinely stand out in interviews aren’t the ones who’ve memorized every API method or can recite the web part lifecycle from memory. They’re the ones who can talk about trade-offs — why they’d choose Dynamic Data over localStorage, why they’d externalize a library instead of bundling it, why they’d reach for PnPjs batching instead of looping through individual requests. That kind of thinking only comes from actually building things, making mistakes, and figuring out why something didn’t work the way you expected.
SPFx is also moving faster now than it has in years. The toolchain overhaul in 1.22, the new CLI coming in 1.23.1, the in-page debugging experience, the open-sourced templates — Microsoft is clearly investing in the developer experience again. Staying current isn’t optional anymore if you want to be taken seriously as an SPFx developer. Follow the Microsoft 365 Dev Blog, keep an eye on the PnP community, and build something new every few months — even if it’s small.
If an interviewer asks you something you genuinely don’t know, say so and then reason through it out loud. Every experienced developer I know respects intellectual honesty far more than a confident wrong answer.
Good luck — you’ve done the work. Now go show them what you know.

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.