Getting Started with PnPjs in SPFx: Reading SharePoint Lists

When building solutions with Microsoft 365 and SharePoint Framework (SPFx), one of the most important skills is learning how to communicate with SharePoint data efficiently.

Before using advanced controls like PnP React Controls, it is essential to understand the foundation: PnPjs.

PnPjs is the data layer.

It simplifies communication with SharePoint REST APIs and provides a fluent, modern, and strongly typed way to query data.

This article introduces the initial setup and shows how to retrieve items from a SharePoint list.


Why PnPjs?

Without PnPjs, you would often write raw REST calls:

this.context.spHttpClient.get(...)

This works, but quickly becomes repetitive and harder to maintain.

PnPjs improves this by providing:

  • Cleaner syntax
  • Better readability
  • Stronger maintainability
  • Query chaining
  • Easier filtering, sorting, and expanding
  • Better scalability

Example:

Raw REST:

_api/web/lists/getbytitle('Bike Sales')/items

PnPjs:

sp.web.lists.getByTitle("Bike Sales").items()

Much cleaner.


Installing PnPjs

Install the required packages:

npm install @pnp/sp

Creating a central configuration

A common professional approach is to initialize PnPjs once and reuse it everywhere.

Create:

src/webparts/spPnPbasico/components/pnpjsConfig.ts

Code:

import { spfi, SPFI } from "@pnp/sp";
import { SPFx } from "@pnp/sp/behaviors/spfx";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
let _sp: SPFI | undefined;
export const getSP = (context?: any): SPFI => {
if (!_sp && context) {
_sp = spfi().using(SPFx(context));
}
if (!_sp) {
throw new Error("PnPjs has not been initialized.");
}
return _sp;
};

This creates a singleton instance.

Benefits:

  • One initialization
  • Better performance
  • Cleaner architecture
  • Easier reuse

Passing SPFx Context

Inside your WebPart:

const element: React.ReactElement<ISpPnPbasicoProps> = React.createElement(
SpPnPbasico,
{
context: this.context
}
);

This is critical because PnPjs needs the SPFx context.

Without it, authentication and requests will fail.


Creating the React component

Inside:

SpPnPbasico.tsx

Code:

import * as React from 'react';
import { useEffect } from "react";
import { ISpPnPbasicoProps } from './ISpPnPbasicoProps';
import { getSP } from "./pnpjsConfig";
const SpPnPbasico: React.FC<ISpPnPbasicoProps> = (props) => {
useEffect(() => {
loadItems();
}, []);
const loadItems = async (): Promise<void> => {
try {
const sp = getSP(props.context);
const items = await sp.web.lists
.getByTitle("Bike Sales")
.items
.select("Id", "Title")();
console.log("Bike Sales items:", items);
} catch (error) {
console.error("Error loading items:", error);
}
};
return (
<div>
<h1>Welcome to SPFx with PnPjs!</h1>
<p>Check the browser console to see the list items.</p>
</div>
);
};
export default SpPnPbasico;

Understanding the code

useEffect()

useEffect(() => {
loadItems();
}, []);

Runs once when the component loads.


Getting the PnP instance

const sp = getSP(props.context);

Retrieves the initialized singleton.


Getting list data

const items = await sp.web.lists
.getByTitle("Bike Sales")
.items
.select("Id", "Title")();

Breakdown:

  • getByTitle() → targets the list
  • items → accesses list items
  • select() → improves performance by limiting columns
  • () → executes the query

Why select() matters

Bad:

.items()

Returns everything.

Good:

.items.select("Id", "Title")()

Returns only what you need.

This improves:

  • performance
  • payload size
  • rendering speed

Always use select whenever possible.


What comes next?

This is the foundation.

In the next article, we will use this same PnPjs setup with PnP React Controls.

Specifically:

ListView

Why?

Because ListView is one of the most useful controls for rendering SharePoint data professionally.

Instead of:

console.log(items)

We will render:

  • columns
  • sorting
  • selection
  • custom rendering
  • Fluent UI integration

Flow:

PnPjs → Data source
ListView → UI layer

This separation is one of the best architectural patterns in SPFx.


Final thoughts

PnPjs is not optional for serious SPFx development.

It is the foundation of:

  • data retrieval
  • filtering
  • sorting
  • paging
  • CRUD operations
  • integration with advanced controls

Mastering this first makes everything else easier.

In the next article:

PnP React Controls ListView with SharePoint List Data

Edvaldo Guimrães Filho Avatar

Published by