SharePoint Framework (SPFx) — Complete Guide
Core Concepts · Web Parts · Extensions · API & Graph · Deployment & ALM · Scenarios · Cheat Sheet
Table of Contents
- Core Concepts — Basics
- Web Parts — Deep Dive
- SPFx Extensions
- API Calls, Graph & Authentication
- Deployment, Packaging & ALM
- Performance & Best Practices
- Scenario-Based Questions
- Cheat Sheet — Quick Reference
1. Core Concepts — Basics
What is SharePoint Framework (SPFx) and why was it introduced?
SPFx is the recommended development model for extending SharePoint Online, Microsoft Teams, Microsoft Viva, and Outlook. Introduced in 2016, it replaced older extension models (Farm Solutions, Sandboxed Solutions, Script Editor web parts) with a modern, client-side, TypeScript-based framework.
Why SPFx was introduced:
- Runs entirely in the browser — no server-side code execution required
- Uses modern web standards — TypeScript, React, webpack, Node.js toolchain
- Works in SharePoint Online, SharePoint 2019/SE on-premises, Teams, Viva Connections, and Outlook
- Supports all SharePoint security and permissions natively
- Fully supported by Microsoft — unlike Script Editor/Content Editor web parts
Key positioning: SPFx replaced the "full trust" server-side model with a "least privilege" client-side model. This is what makes it cloud-compatible and Teams/Viva-ready.
What can you build with SPFx?
| Component | Description |
|---|---|
| Client-side web parts | Interactive UI components placed on SharePoint pages and Teams tabs |
| Application Customizer | Inject custom header/footer HTML across all pages in a site |
| Field Customizer | Customise how a column value is rendered in a list view |
| Command Set | Add custom buttons to list/library toolbar and context menu |
| Adaptive Card Extensions (ACE) | Cards for Microsoft Viva Connections dashboard |
| Form customiser | Replace default SharePoint new/edit/display forms with custom React forms |
What is the SPFx development toolchain and what does each component do?
Node.js → JavaScript runtime — required for the build toolchain
npm / yarn → package manager — install dependencies
Yeoman (yo) → project scaffolding generator
@microsoft/generator-sharepoint → SPFx-specific Yeoman generator
gulp → task runner — build, bundle, package, serve
webpack → module bundler — bundles TypeScript/React into JS
TypeScript → typed superset of JavaScript — primary language
React → UI component library (default, optional)
Fluent UI → Microsoft design system — UI components for M365 look/feel
PnPjs → community library for SharePoint/Graph REST API calls
Tip: SPFx has specific Node.js version compatibility requirements per SPFx version. Always check the SPFx compatibility matrix before setting up a new dev environment. Using the wrong Node.js version causes cryptic build errors.
What is the SPFx project structure and what are the key files?
my-spfx-solution/
├── config/
│ ├── config.json ← bundle config, external scripts
│ ├── package-solution.json ← solution metadata, version, permissions
│ ├── serve.json ← local workbench serve config
│ └── write-manifests.json ← CDN URL for asset hosting
├── src/
│ └── webparts/
│ └── myWebPart/
│ ├── MyWebPartWebPart.ts ← main web part class
│ ├── MyWebPartWebPart.manifest.json ← component metadata, GUID
│ └── components/
│ ├── MyWebPart.tsx ← React component
│ └── MyWebPart.module.scss ← CSS modules
├── sharepoint/
│ └── assets/ ← provisioning XML, list schemas
├── .yo-rc.json ← Yeoman config, SPFx version
├── package.json ← npm dependencies
├── tsconfig.json ← TypeScript config
└── gulpfile.js ← gulp tasks
What are the key SPFx CLI commands every developer must know?
# Project setup
yo @microsoft/sharepoint # scaffold new project
npm install # install dependencies
# Development
gulp serve # local workbench
gulp serve --nobrowser # serve without opening browser
# Building
gulp build # compile TypeScript, validate
gulp bundle # webpack bundle (debug)
gulp bundle --ship # webpack bundle (production/minified)
gulp package-solution # create .sppkg file (debug)
gulp package-solution --ship # create .sppkg file (production)
# Upgrade Node/SPFx compatibility check
spfx doctor # check environment compatibility
What is the difference between SPFx and the older SharePoint extension models?
| Model | Runs | Trust | Cloud-compatible | Status |
|---|---|---|---|---|
| Farm Solutions | Server-side | Full trust | No | Deprecated |
| Sandboxed Solutions | Server-side | Partial trust | No | Deprecated |
| Script Editor web part | Client-side | No governance | Technically yes | Discouraged |
| SPFx | Client-side | Least privilege | Yes | Recommended |
2. Web Parts — Deep Dive
What is the anatomy of an SPFx web part class?
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IPropertyPaneConfiguration,
PropertyPaneTextField } from '@microsoft/sp-property-pane';
import * as React from 'react';
import * as ReactDom from 'react-dom';
import MyComponent from './components/MyComponent';
export interface IMyWebPartProps {
description: string;
listName: string;
}
export default class MyWebPart
extends BaseClientSideWebPart<IMyWebPartProps> {
public render(): void {
// Mount React component
const element = React.createElement(MyComponent, {
description: this.properties.description,
listName: this.properties.listName,
context: this.context // ← pass SP context to React
});
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
// Lifecycle hooks
protected async onInit(): Promise<void> {
// initialise PnPjs, services, etc.
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [{
header: { description: 'Web Part Settings' },
groups: [{
groupName: 'Configuration',
groupFields: [
PropertyPaneTextField('description', { label: 'Description' }),
PropertyPaneTextField('listName', { label: 'List Name' })
]
}]
}]
};
}
}
What is the Web Part context and what does it expose?
The web part context (this.context) is the gateway to SharePoint and Microsoft 365 platform services:
this.context.pageContext
.web.absoluteUrl // current site URL
.web.title // site title
.user.displayName // current user's display name
.user.email // current user's email
.user.loginName // current user's login name (i:0#.f|membership|...)
.site.id // site collection GUID
.list.id // current list GUID (if on list page)
.list.title // current list title (if on list page)
this.context.spHttpClient // make SharePoint REST API calls
this.context.msGraphClientFactory // get MS Graph v3 client
this.context.aadHttpClientFactory // call Azure AD-secured APIs
this.context.httpClient // call anonymous/public APIs
this.context.serviceScope // DI container
this.context.propertyPane // open/refresh the property pane
this.context.sdks.microsoftTeams // Teams context (if running in Teams)
Tip: Never use
window.locationor hardcode site URLs. Always usethis.context.pageContext.web.absoluteUrlfor the current site URL.
What is the Property Pane and what control types are available?
The Property Pane is the configuration panel that opens when an editor clicks "Edit" on a web part.
Built-in property pane controls:
| Control | Description |
|---|---|
PropertyPaneTextField |
Single or multi-line text input |
PropertyPaneCheckbox |
Boolean toggle |
PropertyPaneDropdown |
Select from a list of options |
PropertyPaneToggle |
On/off switch |
PropertyPaneSlider |
Numeric range slider |
PropertyPaneChoiceGroup |
Radio button group |
PropertyPaneLink |
Clickable link |
PropertyPaneLabel |
Descriptive read-only label |
PropertyPaneHorizontalRule |
Visual separator |
PnP property pane controls (@pnp/spfx-property-controls):
| Control | Description |
|---|---|
PropertyFieldListPicker |
Pick a SharePoint list |
PropertyFieldPeoplePicker |
Pick SharePoint users/groups |
PropertyFieldTermPicker |
Pick managed metadata terms |
PropertyFieldColorPicker |
Colour selector |
PropertyFieldDateTimePicker |
Date/time input |
What is the difference between reactive and non-reactive property panes?
Reactive (default): web part re-renders immediately every time a property pane value changes — even before the user confirms.
Non-reactive: web part only re-renders when the user clicks "Apply". Better for properties that trigger expensive API calls on change.
protected get disableReactivePropertyChanges(): boolean {
return true; // web part only updates on Apply button click
}
Tip: Use non-reactive when property changes trigger API calls (e.g., selecting a list name loads its columns). Reactive mode fires an API call on every keystroke — expensive and poor UX.
What is the SPFx web part manifest and what does it contain?
{
"$schema": "https://developer.microsoft.com/json-schemas/...",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"alias": "MyWebPart",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,
"requiresCustomScript": false,
"supportedHosts": [
"SharePointWebPart",
"TeamsPersonalApp",
"TeamsTab",
"SharePointFullPage"
],
"supportsThemeVariants": true,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "My Web Part" },
"description": { "default": "My web part description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "My Web Part"
}
}]
}
Critical: Never change the web part GUID (
id) after it has been deployed to production. The GUID identifies the component on pages — changing it breaks all existing page instances.
3. SPFx Extensions
What is an Application Customizer and how does it work?
An Application Customizer injects custom HTML into placeholder regions (Top = header, Bottom = footer) on every SharePoint page where it is activated.
import { BaseApplicationCustomizer,
PlaceholderContent, PlaceholderName }
from '@microsoft/sp-application-base';
export default class HeaderFooterCustomizer
extends BaseApplicationCustomizer<IHeaderFooterProps> {
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?.domElement) {
this._headerPlaceholder.domElement.innerHTML =
`<div class="custom-header">Company Banner</div>`;
// Or mount a React component for richer UI
}
}
}
Tip: Application Customizers deployed to the Tenant App Catalog with
skipFeatureDeployment: trueand activated via the "Tenant Wide Extensions" list apply to ALL SharePoint sites in the tenant in one operation.
What is a Field Customizer and when would you use it?
A Field Customizer replaces the default rendering of a column value in a list view with custom HTML/React.
import { BaseFieldCustomizer,
IFieldCustomizerCellEventParameters }
from '@microsoft/sp-listview-extensibility';
export default class StatusFieldCustomizer
extends BaseFieldCustomizer<IStatusProps> {
public onRenderCell(
event: IFieldCustomizerCellEventParameters): void {
const status: string = event.fieldValue;
const color: string =
status === 'Approved' ? 'green' :
status === 'Rejected' ? 'red' : 'orange';
event.domElement.innerHTML =
`<span style="color:${color};font-weight:bold;
padding:4px 8px;border-radius:4px;
background:${color}20">${status}</span>`;
}
public onDisposeCell(
event: IFieldCustomizerCellEventParameters): void {
ReactDom.unmountComponentAtNode(event.domElement);
}
}
Use cases: colour-coded status badges, progress bars, rating stars, clickable action buttons, formatted currency/date display.
What is a Command Set extension and how do you add custom buttons to a list?
A Command Set adds custom buttons to the SharePoint list/library toolbar and context menu.
import { BaseListViewCommandSet, Command,
IListViewCommandSetExecuteEventParameters,
IListViewCommandSetListViewUpdatedParameters }
from '@microsoft/sp-listview-extensibility';
export default class ApprovalCommandSet
extends BaseListViewCommandSet<ICommandProps> {
public onListViewUpdated(
event: IListViewCommandSetListViewUpdatedParameters): void {
const approveCmd: Command = this.tryGetCommand('APPROVE_ITEM');
if (approveCmd) {
// Show button only when exactly one item selected
approveCmd.visible = event.selectedRows.length === 1;
}
}
public onExecute(
event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'APPROVE_ITEM':
const itemId = event.selectedRows[0].getValueByName('ID');
this._approveItem(Number(itemId));
break;
}
}
private async _approveItem(id: number): Promise<void> {
const sp = spfi().using(SPFx(this.context));
await sp.web.lists.getByTitle('Requests').items.getById(id)
.update({ Status: 'Approved' });
this.context.listView.refresh();
}
}
What are Adaptive Card Extensions (ACE) and what are they used for?
Adaptive Card Extensions are SPFx components that render as cards on the Microsoft Viva Connections dashboard.
Two views:
- Card view: compact card on the dashboard — title, description, icon, primary/secondary button
- Quick view: richer panel that opens on card click — detailed data, forms, actions using Adaptive Card JSON
Common use cases:
- Pending approvals count with approve/reject actions
- Today's lunch menu or announcements
- IT ticket submission form
- Time-off balance with request button
- Key KPI metrics tile
Tip: ACEs are the bridge between SPFx development and Viva Connections. If asked about Viva extensibility — the answer is ACE + SPFx.
What is a Form Customiser in SPFx?
A Form Customiser replaces the default SharePoint list new/edit/display forms with a completely custom React-based form. Instead of the standard SharePoint form UI, users see your custom UI.
Use cases:
- Multi-step wizard forms
- Custom validation with complex business rules
- Branded forms matching company design system
- Forms with dynamic fields based on previous selections
- Integration with external systems at form submission
4. API Calls, Graph & Authentication
What are the different ways to call APIs from SPFx?
| Client | Use For | Auth Method |
|---|---|---|
SPHttpClient |
SharePoint REST API | SharePoint session cookie (automatic) |
MSGraphClientV3 |
Microsoft Graph API | MSAL OAuth (automatic via SPFx) |
AadHttpClient |
Azure AD-secured custom APIs | OAuth token (automatic via SPFx) |
HttpClient |
Anonymous/public APIs | None |
| PnPjs | SharePoint REST + Graph (wrapper) | Inherits from SPFx context |
How do you call the SharePoint REST API from an SPFx web part?
import { SPHttpClient, SPHttpClientResponse }
from '@microsoft/sp-http';
// GET — read list items with OData
const response: SPHttpClientResponse =
await this.context.spHttpClient.get(
`${this.context.pageContext.web.absoluteUrl}` +
`/_api/web/lists/getbytitle('Tasks')/items` +
`?$select=Title,Status,AssignedTo/Title` +
`&$expand=AssignedTo` +
`&$filter=Status eq 'In Progress'` +
`&$orderby=Created desc` +
`&$top=10`,
SPHttpClient.configurations.v1
);
const data = await response.json();
const items = data.value;
// POST — create a new list item
await this.context.spHttpClient.post(
`${this.context.pageContext.web.absoluteUrl}` +
`/_api/web/lists/getbytitle('Tasks')/items`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-Type': 'application/json;odata=verbose'
},
body: JSON.stringify({
'__metadata': { 'type': 'SP.Data.TasksListItem' },
'Title': 'New Task',
'Status': 'Not Started'
})
}
);
How do you call Microsoft Graph API from SPFx?
// Step 1 — declare permissions in package-solution.json:
"webApiPermissionRequests": [
{ "resource": "Microsoft Graph", "scope": "User.Read" },
{ "resource": "Microsoft Graph", "scope": "Sites.Read.All" },
{ "resource": "Microsoft Graph", "scope": "Mail.Read" }
]
Step 2 — admin approves:
SharePoint Admin Center → Advanced → API Access → approve pending requests
// Step 3 — use MSGraphClientV3 in web part:
import { MSGraphClientV3 } from '@microsoft/sp-http';
const client: MSGraphClientV3 =
await this.context.msGraphClientFactory.getClient('3');
// Get current user's profile
const me = await client.api('/me').get();
// Get SharePoint sites
const sites = await client.api('/sites')
.filter("siteCollection/root ne null")
.select('displayName,webUrl')
.top(50)
.get();
// Get current user's manager
const manager = await client.api('/me/manager').get();
// Send email on behalf of user
await client.api('/me/sendMail').post({
message: {
subject: 'Notification from SPFx',
body: { contentType: 'Text', content: 'Hello from SPFx' },
toRecipients: [
{ emailAddress: { address: 'recipient@company.com' } }
]
}
});
Warning: Graph API permissions in SPFx are tenant-wide — any SPFx solution can use approved permissions. Never approve overly broad scopes (User.ReadWrite.All, Sites.FullControl.All). Use least privilege.
What is PnPjs and why is it preferred over raw SPHttpClient?
PnPjs (@pnp/sp, @pnp/graph) wraps SharePoint REST and Microsoft Graph APIs with a fluent, strongly-typed, chainable interface.
// Setup in onInit():
import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/site-users";
const sp = spfi().using(SPFx(this.context));
// Read list items (concise vs verbose raw HTTP):
const items = await sp.web
.lists.getByTitle('Tasks')
.items
.select('Title', 'Status', 'Author/Title')
.expand('Author')
.filter("Status eq 'Active'")
.orderBy('Created', false)
.top(50)();
// Create item:
await sp.web.lists.getByTitle('Tasks').items.add({
Title: 'New Task',
Status: 'Not Started'
});
// Update item:
await sp.web.lists.getByTitle('Tasks').items.getById(itemId).update({
Status: 'Completed'
});
// Parallel requests (no extra code):
const [user, listItems, siteTitle] = await Promise.all([
sp.web.currentUser(),
sp.web.lists.getByTitle('Tasks').items.top(10)(),
sp.web.select('Title')()
]);
Tip: PnPjs v3+ uses tree-shaking-friendly imports — only import what you use to keep bundle size minimal. Always initialise PnPjs with SPFx context in the web part's
onInit().
How do you call an Azure AD-secured internal API from SPFx?
// Declare in package-solution.json:
"webApiPermissionRequests": [{
"resource": "MyInternalAPI", // App registration display name
"scope": "Data.Read"
}]
// After admin approval:
import { AadHttpClient } from '@microsoft/sp-http';
const client: AadHttpClient =
await this.context.aadHttpClientFactory
.getClient('api://myinternalapi-client-id');
const response = await client.get(
'https://myapi.company.com/api/data',
AadHttpClient.configurations.v1
);
const data = await response.json();
Tip: SPFx handles OAuth token acquisition transparently — no MSAL code needed. The framework gets and caches the token automatically.
5. Deployment, Packaging & ALM
What is the App Catalog and what are the two types?
Tenant App Catalog: one per SharePoint tenant. SPFx solutions deployed here can be made available to all sites. Managed by SharePoint/Global Admins. Supports tenant-wide deployment.
Site Collection App Catalog: one per site collection, enabled by admin. Allows site collection owners to deploy SPFx solutions scoped to their site only — without requiring tenant admin involvement.
// Enable tenant-wide deployment in package-solution.json:
{
"solution": {
"name": "my-solution",
"id": "...",
"version": "1.0.0.0",
"skipFeatureDeployment": true // ← enables tenant-wide deployment
}
}
Tip:
skipFeatureDeployment: true+ Application Customizer + Tenant Wide Extensions list = zero-touch org-wide deployment pattern.
What is the difference between debug and ship bundles?
# Debug bundle (for development):
gulp bundle # non-minified, source maps, large file
gulp package-solution # creates debug .sppkg
# Production bundle (for deployment):
gulp bundle --ship # minified, tree-shaken, small file
gulp package-solution --ship # creates production .sppkg
Critical: Always use
--shipfor production deployments. A debug bundle can be 3–5x larger than the ship bundle, causing slow page loads for all users.
What is CDN hosting for SPFx assets and why is it important?
SPFx JavaScript bundles and assets need to be hosted at a publicly accessible URL.
| Option | Description | Cost |
|---|---|---|
| SharePoint CDN | Assets served from SharePoint document library via Azure CDN | Free (included) |
| Azure Blob + CDN | Assets in Azure Blob Storage behind Azure CDN | Azure costs |
| Embedded in .sppkg | Assets bundled inside the .sppkg file | Free but slower |
# Enable Office 365 CDN:
Set-SPOTenantCdnEnabled -CdnType Public -Enable $true
# Configure CDN URL in write-manifests.json:
{
"cdnBasePath": "https://yourtenant.sharepoint.com/sites/appcatalog/CDNFiles"
}
How do you version and upgrade an SPFx solution in production?
- Update version in
package-solution.json(e.g.,1.0.0.0→1.1.0.0) - Update version in the web part manifest (
.manifest.json) - Build and package:
gulp bundle --ship && gulp package-solution --ship - Upload new
.sppkgto App Catalog — SharePoint detects the version change - If
skipFeatureDeployment: true: update propagates to all sites automatically - If
skipFeatureDeployment: false: site admins must go to Site Contents → find app → click Upgrade
Warning: The web part GUID must stay the same across all versions. Only the version number changes — changing the GUID creates a new web part and breaks all existing page instances.
How do you deploy extensions (Application Customizer) tenant-wide?
Approach 1 — Tenant Wide Extensions list (recommended):
- Upload .sppkg to Tenant App Catalog with
skipFeatureDeployment: true - Check "Make this solution available to all sites"
- Navigate to App Catalog site → "Tenant Wide Extensions" list
- Add new item:
- Title: "Company Header"
- Component ID: (Application Customizer GUID from manifest)
- Component Properties:
{"headerMessage":"Welcome"} - Location:
ClientSideExtension.ApplicationCustomizer
Approach 2 — PnP PowerShell (for automation):
Add-PnPCustomAction -Name "CompanyHeader" -Title "Company Header" `
-Location "ClientSideExtension.ApplicationCustomizer" `
-ClientSideComponentId "a1b2c3d4-..." `
-ClientSideComponentProperties '{"message":"Welcome"}' `
-Scope Site
6. Performance & Best Practices
What are the key SPFx performance best practices?
Bundle size:
- Always use
--shipfor production — minified and tree-shaken - Use tree-shaking-friendly imports (PnPjs v3, Fluent UI v8+)
- Configure externals in
config.jsonfor libraries already loaded by SharePoint (React, ReactDOM, Fluent UI) - Avoid importing entire libraries — import only what you use
Data loading:
- Use
onInit()for one-time initialisation, not render-blocking data loads - Cache data in component state — avoid re-fetching on every render
- Use
Promise.all/ PnPjs batching for parallel requests - Implement loading states with Fluent UI Spinner
CSS:
- Use CSS Modules (
.module.scss) to scope styles — prevents class name conflicts with other web parts on the page - Never use global CSS — it bleeds into SharePoint UI
- Use Fluent UI theme variables instead of hardcoded colours — supports SharePoint theme changes
React:
- Use functional components with hooks (not class components)
- Memoize expensive calculations with
useMemo - Avoid re-renders with
useCallbackfor event handlers - Use
React.lazyfor code splitting large components
What are the SPFx externals and why are they important?
Externals in config/config.json tell webpack not to bundle specific libraries — instead loading them from the SharePoint page's global scope (which already has React, ReactDOM, and Fluent UI loaded).
// config/config.json:
"externals": {
"react": "React",
"react-dom": "ReactDom",
"@fluentui/react": {
"path": "https://res.cdn.office.net/files/fabric-react-8.29.0/office-ui-fabric-react.js",
"globalName": "Fabric",
"globalDependencies": ["react", "react-dom"]
}
}
Why important: Without externals, React and Fluent UI are bundled into your web part JS file — adding ~200-500KB to the bundle. With externals, your bundle only contains your code — dramatically smaller and faster to load.
7. Scenario-Based Questions
Scenario: Build an SPFx web part that displays list items with filtering and paging.
Architecture:
- Property pane:
PropertyFieldListPickerfor list selection,PropertyPaneDropdownfor filter column,PropertyPaneSliderfor page size - React component with useState/useEffect hooks:
const [items, setItems] = useState<IListItem[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [currentPage, setCurrentPage] = useState<number>(1);
const [error, setError] = useState<string>('');
useEffect(() => {
const loadItems = async () => {
setLoading(true);
try {
const sp = spfi().using(SPFx(props.context));
const result = await sp.web
.lists.getByTitle(props.listName)
.items
.select('Title', 'Status', 'AssignedTo/Title')
.expand('AssignedTo')
.filter(props.filterValue ? `Status eq '${props.filterValue}'` : '')
.orderBy('Created', false)
.top(props.pageSize)
.skip((currentPage - 1) * props.pageSize)();
setItems(result);
} catch (err) {
setError(`Failed to load items: ${err.message}`);
} finally {
setLoading(false);
}
};
loadItems();
}, [currentPage, props.filterValue, props.listName]);
- Render: Fluent UI
DetailsListfor grid,Spinnerfor loading,MessageBarfor errors, custom pagination controls
Scenario: Deploy a company-wide custom header to all SharePoint sites without touching each site.
- Build an Application Customizer that injects custom HTML into the Top placeholder
- Set
skipFeatureDeployment: trueinpackage-solution.json - Build:
gulp bundle --ship && gulp package-solution --ship - Upload
.sppkgto Tenant App Catalog → check "Make this solution available to all sites" - Add entry to Tenant Wide Extensions list in App Catalog site with the customizer's GUID
- Result: all SharePoint sites in the tenant show the custom header — zero per-site action needed
Scenario: An SPFx web part needs to call an internal Azure API secured with Azure AD.
// 1. Register API in Azure AD — expose scope: api://myapi/Data.Read
// 2. package-solution.json:
"webApiPermissionRequests": [{
"resource": "MyInternalAPI",
"scope": "Data.Read"
}]
// 3. Admin approves in SharePoint Admin Center → Advanced → API Access
// 4. Call the API in the web part:
const client: AadHttpClient =
await this.context.aadHttpClientFactory
.getClient('api://myapi-app-registration-client-id');
const response = await client.get(
'https://myapi.company.com/api/employees',
AadHttpClient.configurations.v1
);
const employees = await response.json();
Scenario: How do you make an SPFx web part available as a Microsoft Teams tab?
// 1. Add Teams to supportedHosts in manifest:
"supportedHosts": [
"SharePointWebPart",
"TeamsTab",
"TeamsPersonalApp"
]
// 2. Handle Teams context in web part:
public async onInit(): Promise<void> {
if (this.context.sdks.microsoftTeams) {
const teamsContext = this.context.sdks.microsoftTeams.context;
console.log('Team ID:', teamsContext.team?.internalId);
console.log('Channel ID:', teamsContext.channel?.id);
console.log('User:', teamsContext.user?.userPrincipalName);
}
}
3. Deploy to App Catalog with tenant-wide deployment
4. In App Catalog → find app → click "Sync to Teams"
(auto-creates Teams app manifest from SPFx solution manifest)
5. In Teams → channel → + Add tab → find your app → configure
Scenario: How do you implement dark/light theme support in an SPFx web part?
// Use Fluent UI theme tokens in CSS Modules:
// MyWebPart.module.scss
.container {
background: "[theme:bodyBackground, default:#ffffff]";
color: "[theme:bodyText, default:#000000]";
border: 1px solid "[theme:neutralLight, default:#edebe9]";
}
// In manifest — enable theme variants:
"supportsThemeVariants": true
// In web part class:
import { ThemeProvider, ThemeChangedEventArgs,
IReadonlyTheme } from '@microsoft/sp-component-base';
protected onInit(): Promise<void> {
const themeProvider = this.context.serviceScope
.consume(ThemeProvider.serviceKey);
this._themeVariant = themeProvider.tryGetTheme();
themeProvider.themeChangedEvent.add(this,
this._handleThemeChanged);
return super.onInit();
}
private _handleThemeChanged(args: ThemeChangedEventArgs): void {
this._themeVariant = args.theme;
this.render();
}
8. Cheat Sheet — Quick Reference
Key Commands
# Scaffold
yo @microsoft/sharepoint
# Development
gulp serve --nobrowser
# Production build
gulp bundle --ship
gulp package-solution --ship
# Deploy
Upload .sppkg to App Catalog
Approve API permissions if needed
# Check environment
spfx doctor
API Client Quick Reference
// SharePoint REST:
await this.context.spHttpClient.get(url, SPHttpClient.configurations.v1)
// Microsoft Graph:
const client = await this.context.msGraphClientFactory.getClient('3');
await client.api('/me').get();
// Azure AD-secured API:
const client = await this.context.aadHttpClientFactory
.getClient('api://your-app-id');
await client.get(url, AadHttpClient.configurations.v1);
// PnPjs (recommended wrapper):
const sp = spfi().using(SPFx(this.context));
await sp.web.lists.getByTitle('Name').items.top(10)();
Extension Types Reference
Application Customizer
→ Injects HTML into Top/Bottom placeholders
→ Runs on every page in activated site/tenant
→ Activated via site CustomActions or Tenant Wide Extensions list
→ Use for: global header, footer, notification banners, chat widgets
Field Customizer
→ Replaces column value rendering in list views
→ Bound to a specific column in a specific list
→ Use for: status badges, progress bars, icon columns, linked text
Command Set
→ Adds buttons to list toolbar and context menu
→ Can show/hide buttons based on selection count or item values
→ Use for: approve/reject actions, export, send for review
Adaptive Card Extension (ACE)
→ Cards on Viva Connections dashboard
→ Card view (compact) + Quick view (detail panel)
→ Use for: approvals, metrics, announcements, forms
Form Customiser
→ Replaces SharePoint default new/edit/display forms
→ Full React component — complete UI control
→ Use for: multi-step forms, complex validation, branded forms
Package-solution.json Key Settings
{
"solution": {
"name": "my-solution-client-side-solution",
"id": "unique-guid-never-change",
"version": "1.0.0.0",
"includeClientSideAssets": true, // embed assets in .sppkg
"skipFeatureDeployment": true, // enable tenant-wide deployment
"isDomainIsolated": false, // set true for isolated web parts
"developer": {
"name": "Your Company",
"websiteUrl": "https://yourcompany.com"
},
"metadata": {
"shortDescription": { "default": "Solution description" },
"longDescription": { "default": "Long description" },
"screenshotPaths": [],
"videoUrl": "",
"categories": []
}
},
"webApiPermissionRequests": [
{ "resource": "Microsoft Graph", "scope": "User.Read" },
{ "resource": "Microsoft Graph", "scope": "Sites.Read.All" }
],
"paths": {
"zippedPackage": "solution/my-solution.sppkg"
}
}
Deployment Checklist
Pre-deployment:
☐ Use --ship flag for bundle and package
☐ Verify bundle size is acceptable (<500KB ideal)
☐ Test in SharePoint workbench against real site
☐ Test in Teams if supportedHosts includes Teams
☐ Verify property pane controls work correctly
☐ Check theme support (light/dark/high contrast)
☐ Review Graph/API permissions — least privilege
Deployment:
☐ Upload .sppkg to correct App Catalog (tenant or site)
☐ Check "Make available to all sites" if tenant-wide
☐ Approve API permissions in Admin Center if needed
☐ Test in staging/UAT before production
Post-deployment:
☐ Verify web part appears in web part picker
☐ Test all property pane settings
☐ Verify API calls return correct data
☐ Check browser console for errors
☐ Test on mobile (SharePoint mobile app)
Top 10 Tips
- Never change the web part GUID after deployment — it breaks all existing page instances. Only the version number changes between releases.
- Always use
--shipfor production — debug bundles are 3–5x larger. Forgetting--shipis the most common deployment mistake. this.context.pageContext.web.absoluteUrl— never hardcode site URLs. Always use the context for the current site URL.- PnPjs over raw SPHttpClient — more concise, typed, handles errors better, supports batching. Know how to initialise it with SPFx context.
- Graph API permissions are tenant-wide — once approved, any SPFx solution in the tenant can use them. Always use least privilege scopes.
skipFeatureDeployment: true+ Tenant Wide Extensions — the canonical pattern for org-wide Application Customizer deployment. Know this by heart.- Reactive vs non-reactive property pane — non-reactive is needed when property changes trigger API calls. Reactive fires on every keystroke.
- Service Scope for DI — how SPFx enables testability and service reuse. Know how to define a service key and consume it.
- CSS Modules — always scope styles with
.module.scss. Global CSS bleeds into SharePoint UI and other web parts on the page. - Teams integration — add
TeamsTabtosupportedHosts, handlethis.context.sdks.microsoftTeams, use "Sync to Teams" in App Catalog. Three steps, always the same pattern.
No comments:
Post a Comment