Tuesday, April 28, 2026

SharePoint Framework (SPFx) Complete Guide

 

SharePoint Framework (SPFx) — Complete Guide

Core Concepts · Web Parts · Extensions · API & Graph · Deployment & ALM · Scenarios · Cheat Sheet


Table of Contents

  1. Core Concepts — Basics
  2. Web Parts — Deep Dive
  3. SPFx Extensions
  4. API Calls, Graph & Authentication
  5. Deployment, Packaging & ALM
  6. Performance & Best Practices
  7. Scenario-Based Questions
  8. 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:

  1. Runs entirely in the browser — no server-side code execution required
  2. Uses modern web standards — TypeScript, React, webpack, Node.js toolchain
  3. Works in SharePoint Online, SharePoint 2019/SE on-premises, Teams, Viva Connections, and Outlook
  4. Supports all SharePoint security and permissions natively
  5. 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.location or hardcode site URLs. Always use this.context.pageContext.web.absoluteUrl for 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: true and 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 --ship for 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?

  1. Update version in package-solution.json (e.g., 1.0.0.01.1.0.0)
  2. Update version in the web part manifest (.manifest.json)
  3. Build and package: gulp bundle --ship && gulp package-solution --ship
  4. Upload new .sppkg to App Catalog — SharePoint detects the version change
  5. If skipFeatureDeployment: true: update propagates to all sites automatically
  6. 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):

  1. Upload .sppkg to Tenant App Catalog with skipFeatureDeployment: true
  2. Check "Make this solution available to all sites"
  3. Navigate to App Catalog site → "Tenant Wide Extensions" list
  4. 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 --ship for production — minified and tree-shaken
  • Use tree-shaking-friendly imports (PnPjs v3, Fluent UI v8+)
  • Configure externals in config.json for 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 useCallback for event handlers
  • Use React.lazy for 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:

  1. Property pane: PropertyFieldListPicker for list selection, PropertyPaneDropdown for filter column, PropertyPaneSlider for page size
  2. 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]);
  1. Render: Fluent UI DetailsList for grid, Spinner for loading, MessageBar for errors, custom pagination controls

Scenario: Deploy a company-wide custom header to all SharePoint sites without touching each site.

  1. Build an Application Customizer that injects custom HTML into the Top placeholder
  2. Set skipFeatureDeployment: true in package-solution.json
  3. Build: gulp bundle --ship && gulp package-solution --ship
  4. Upload .sppkg to Tenant App Catalog → check "Make this solution available to all sites"
  5. Add entry to Tenant Wide Extensions list in App Catalog site with the customizer's GUID
  6. 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

  1. Never change the web part GUID after deployment — it breaks all existing page instances. Only the version number changes between releases.
  2. Always use --ship for production — debug bundles are 3–5x larger. Forgetting --ship is the most common deployment mistake.
  3. this.context.pageContext.web.absoluteUrl — never hardcode site URLs. Always use the context for the current site URL.
  4. PnPjs over raw SPHttpClient — more concise, typed, handles errors better, supports batching. Know how to initialise it with SPFx context.
  5. Graph API permissions are tenant-wide — once approved, any SPFx solution in the tenant can use them. Always use least privilege scopes.
  6. skipFeatureDeployment: true + Tenant Wide Extensions — the canonical pattern for org-wide Application Customizer deployment. Know this by heart.
  7. Reactive vs non-reactive property pane — non-reactive is needed when property changes trigger API calls. Reactive fires on every keystroke.
  8. Service Scope for DI — how SPFx enables testability and service reuse. Know how to define a service key and consume it.
  9. CSS Modules — always scope styles with .module.scss. Global CSS bleeds into SharePoint UI and other web parts on the page.
  10. Teams integration — add TeamsTab to supportedHosts, handle this.context.sdks.microsoftTeams, use "Sync to Teams" in App Catalog. Three steps, always the same pattern.


No comments:

Post a Comment

Featured Post

SharePoint Framework (SPFx) Complete Guide

  SharePoint Framework (SPFx) — Complete Guide Core Concepts · Web Parts · Extensions · API & Graph · Deployment & ALM · Scenarios ...

Popular posts