PnP (Patterns and Practices) is the umbrella for several separate tools that speed up SharePoint Framework development — PnPjs for calling SharePoint from code, PnP PowerShell for admin/automation tasks, and a library of reusable React controls for SPFx web parts. This post covers what each actually does, where the real limitations are, and two places the original examples had drifted from current syntax.
In this post: PnPjs: calling SharePoint from code · PnP PowerShell: automation · Reusable React controls · The real limitations · Use cases · Best practices worth taking seriously · Related reading
PnPjs: calling SharePoint from code
PnPjs wraps SharePoint’s REST API behind a fluent, promise-based interface — reading, creating, and updating list items without hand-building request URLs. Worth using the current v4 initialization pattern, not the `sp` singleton import from v2, which is a genuinely different (and outdated) setup style:
import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
const sp = spfi().using(SPFx(this.context));
async function fetchTasks() {
try {
const items = await sp.web.lists.getByTitle("Tasks").items.select("Title", "Status")();
console.log(items);
} catch (error) {
console.error("Error fetching items:", error);
}
}
Note the trailing `()` at the end of the query chain — that invokes the request in the current PnPjs syntax, replacing the old `.get()` method call from the v2-era `sp` singleton pattern. For lists large enough to need pagination, the current approach is the async-iterator pattern covered in depth in the related post below, not a `.getAll()` call, which was removed entirely in v4.
PnP PowerShell: automation
Site provisioning, permission management, and bulk operations, scripted rather than clicked through the UI. Worth a direct correction on site creation specifically: `New-PnPTenantSite` with a classic template ID like `STS#3` creates a classic-style site collection, not the modern site most people actually mean when they say “create a team site” today. For the modern equivalent:
Connect-PnPOnline -Url "https://contoso-admin.sharepoint.com" -Interactive
# Modern, Microsoft 365 Group-connected team site -- the current default choice
New-PnPSite -Type TeamSite -Title "Team Site" -Alias "teamsite" -Owners "admin@contoso.com"
`New-PnPTenantSite` still has a real, narrower use: a classic site collection deliberately *not* connected to a Microsoft 365 Group. Worth picking deliberately between the two based on whether that’s actually the requirement, rather than defaulting to whichever example happened to be copied from an older guide.
PnP’s provisioning engine (`Apply-PnPProvisioningTemplate` against an XML template) still works and is genuinely useful for replicating a complex site structure across many sites — but it’s worth knowing it’s the heavier, older option now. For simpler, repeatable setup steps, Site Scripts and Site Designs are the lighter modern alternative most new provisioning work reaches for first, applied directly from the site-creation flow rather than as a separate post-creation script run.
New-PnPTenantSite with a classic template creates a classic-style site collection, not a modern one — New-PnPSite -Type TeamSite is the current default for what most people actually mean by “create a team site.”
Reusable React controls
`@pnp/spfx-controls-react` ships pre-built, tested components — taxonomy pickers, file pickers, data grids — for common SPFx UI needs, avoiding rebuilding them from scratch in every project:
import { TaxonomyPicker } from "@pnp/spfx-controls-react/lib/TaxonomyPicker";
console.log(terms)}
/>;
The import path matters here — importing from the package root rather than the specific control’s subpath pulls in more of the library’s bundle than a single component needs, worth avoiding on a production web part where bundle size affects page load.
The real limitations
- Breaking changes across major versions. PnPjs v4 removed `.getAll()` entirely rather than deprecating it with a warning — a real, concrete example of why “stay updated” isn’t risk-free, covered in more depth below.
- On-premises gaps. Some PnP functionality targets SharePoint Online specifically and doesn’t have a Server equivalent — worth checking against the specific SharePoint version in play before assuming parity.
- A genuinely large surface area. PnPjs, PnP PowerShell, the React controls library, and the (largely legacy at this point) provisioning engine are separate tools with separate release cadences and separate documentation — there’s a real learning curve in knowing which one covers which problem, not just in any single tool’s API.
Use cases
- Custom web parts — data-driven dashboards and intranet components built on PnPjs and the React controls together.
- Automation scripts — bulk site creation, permission audits, content migration via PnP PowerShell.
- List and library management — CRUD operations and bulk updates via PnPjs, including the batching pattern covered in the related post below for anything touching hundreds of items at once.
- Integrating external data — combining SharePoint content with other APIs inside an SPFx web part.
Best practices worth taking seriously
“Stay updated” is generic advice on its own — worth being specific about what that actually means given PnPjs’s own history: `npm install @pnp/sp@latest` without reading the release notes first is a real risk, not a formality, since a major-version bump can remove methods outright (as `.getAll()` demonstrates) rather than just adding features. Checking the changelog before bumping a major version, and pinning to a known-working minor/patch version in between deliberate upgrades, is worth doing on any project that can’t tolerate an unannounced breaking change reaching production.
Beyond that: wrap PnPjs calls in try/catch consistently (shown throughout the examples above), keep SPFx components modular and reusable rather than monolithic, and the PnP community’s GitHub discussions and weekly community calls are a genuinely active, current resource — worth checking there directly for anything version-specific rather than trusting an older blog post’s syntax without verifying it against the current docs first.
Related reading
- PnPjs .getAll() Explained — the removed method and its current async-iterator replacement, in depth.
- SharePoint Data Handling with PnP Batching Queries — bulk writes via PnPjs, including the real 100-request batch cap.
- Start Your SharePoint with SPFx — current SPFx toolchain setup, since PnP’s tools build on top of it.
PnP genuinely reduces how much SharePoint boilerplate a project has to write from scratch — the real skill is knowing which of its several separate tools actually fits a given problem, and keeping current with each one’s own release notes rather than assuming an older example still matches today’s syntax.
App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning


