How to Filter SharePoint List Items in SharePoint Framework (SPFx)

If you’ve built even one SPFx web part that reads from a SharePoint list, you’ve run into this moment: you fetch the list, get back 500 rows you don’t need, and wonder, “Why can’t I just get the 10 items I actually want?”

That’s exactly what this guide covers. I’ll walk you through every practical way to filter SharePoint list items inside an SPFx web part, from basic REST API OData filters to the modern fluent filter syntax in PnPjs. No fluff, just working code you can copy, understand, and use.

By the end, you’ll know:

  • How OData filtering works in SharePoint REST
  • How to filter using the SPHttpClient (no extra libraries)
  • How to filter using PnPjs (the easy, modern way)
  • How to filter by current user
  • How to handle large lists of over 5,000 items
  • How to use CAML Query as a fallback for metadata/taxonomy fields

Let’s get into it.

Why Filtering Matters in SPFx

When you call SharePoint’s REST API without any filter, it returns up to 100 items by default. In a real project, your list might have thousands of rows, job listings, tickets, project tasks, and announcements. Loading all of them into your web part is slow, wastes bandwidth, and honestly, it just breaks things at scale.

Filtering happens on the server side, so SharePoint does the heavy lifting before anything reaches your browser. That’s the right approach: always filter at the source.

There are three main ways to filter list items in SPFx:

  1. OData $filter via REST API — raw, flexible, works everywhere
  2. PnPjs .filter() method — cleaner syntax wrapping the same REST calls
  3. CAML Query — XML-based, necessary for taxonomy/metadata fields

Setting Up Your SPFx Web Part

  1. I’m assuming you already have an SPFx project set up. If not, run:
yo @microsoft/sharepoint
  1. Choose React or No Framework — the filtering concepts work the same either way.
  1. For PnPjs, install it in your project:
npm install @pnp/sp --save
  1. Then in your web part’s onInit() method, initialize PnPjs:
import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

export default class MyWebPart extends BaseClientSideWebPart<...> {
private sp: ReturnType<typeof spfi>;

protected async onInit(): Promise<void> {
this.sp = spfi().using(SPFx(this.context));
return super.onInit();
}
}

Method 1: OData Filter via SPHttpClient (REST API)

This is the most basic approach — no external libraries, just the built-in SPHttpClient. It’s good to understand this first because everything else is just a wrapper around it.

The pattern looks like this:

/_api/web/lists/getByTitle('ListName')/items?$filter=FieldName eq 'Value'

Basic Text Filter

Let’s say you have a list called Projects and you only want items where Status equals “Active”:

import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";

const siteUrl = this.context.pageContext.web.absoluteUrl;
const endpoint = `${siteUrl}/_api/web/lists/getByTitle('Projects')/items?$filter=Status eq 'Active'&$select=Title,Status,AssignedTo`;

const response: SPHttpClientResponse = await this.context.spHttpClient.get(
endpoint,
SPHttpClient.configurations.v1
);

const data = await response.json();
const items = data.value;
console.log(items);

Number Comparison Filter

Get all projects where Priority is greater than 2:

const endpoint = `${siteUrl}/_api/web/lists/getByTitle('Projects')/items?$filter=Priority gt 2`;

Date Filter

Get items created after January 1, 2026:

const endpoint = `${siteUrl}/_api/web/lists/getByTitle('Projects')/items?$filter=Created ge datetime'2026-01-01T00:00:00Z'`;

OData Filter Operators — Quick Reference

OperatorMeaningExample
eqEqualsStatus eq 'Active'
neNot equalsStatus ne 'Closed'
gtGreater thanPriority gt 2
geGreater than or equalDueDate ge datetime'2024-01-01T00:00:00Z'
ltLess thanBudget lt 10000
leLess than or equalAge le 30
startswithStarts withstartswith(Title,'Project A')
substringofContainssubstringof('keyword',Title)

Combining Filters with and / or

You can chain multiple conditions:

// Status is Active AND Priority is greater than 1
const endpoint = `${siteUrl}/_api/web/lists/getByTitle('Projects')/items?$filter=Status eq 'Active' and Priority gt 1`;

// Status is Active OR Status is On Hold
const endpoint2 = `${siteUrl}/_api/web/lists/getByTitle('Projects')/items?$filter=Status eq 'Active' or Status eq 'On Hold'`;

PnPjs is what most SPFx developers use in real projects because it’s cleaner, handles a lot of edge cases for you, and the code is easier to read.

Basic String Filter

const items = await this.sp.web.lists.getByTitle("Projects")
.items
.filter("Status eq 'Active'")
.select("Title", "Status", "AssignedTo")
();

console.log(items);

Notice the () At the end — that’s how you execute the PnPjs query chain.

Number and Date Filters

// Items where Priority > 2
const highPriorityItems = await this.sp.web.lists.getByTitle("Projects")
.items
.filter("Priority gt 2")
();

// Items created after a certain date
const recentItems = await this.sp.web.lists.getByTitle("Projects")
.items
.filter("Created ge datetime'2024-06-01T00:00:00Z'")
();

Using the Fluent Filter (PnPjs v3+)

PnPjs introduced a fluent filter API that’s strongly typed and much easier to write. Instead of hand-crafting OData strings, you get IntelliSense and type safety:

import { IListItem } from "@pnp/sp/items";

interface ProjectItem extends IListItem {
Status: string;
Priority: number;
AssignedToId: number;
}

// Get all Active projects
const activeProjects = await this.sp.web.lists.getByTitle("Projects")
.items
.filter<ProjectItem>(f => f.text("Status").equals("Active"))
();

// Get Active projects with Priority > 2
const urgentProjects = await this.sp.web.lists.getByTitle("Projects")
.items
.filter<ProjectItem>(f =>
f.text("Status").equals("Active")
.and()
.number("Priority").greaterThan(2)
)
();

Complex OR/AND Combinations

Here’s where the fluent filter really shines. Let’s get items assigned to either “John Doe” or “Jane Doe”:

interface EmployeeItem extends IListItem {
FirstName: string;
LastName: string;
}

const doeEmployees = await this.sp.web.lists.getByTitle("Employees")
.items
.filter<EmployeeItem>(f => f.or(
f.and(
f.text("FirstName").equals("John"),
f.text("LastName").equals("Doe")
),
f.and(
f.text("FirstName").equals("Jane"),
f.text("LastName").equals("Doe")
)
))
();

Fluent Filter — Supported Field Types

Field TypeAvailable Operators
Text / ChoiceequalsnotEqualsstartsWithcontainsinnotIn
NumberequalsnotEqualsgreaterThangreaterThanOrEqualslessThanlessThanOrEquals
DategreaterThanlessThanisBetweenisTodaygreaterThanOrEqualslessThanOrEquals
BooleanisTrueisFalseisFalseOrNull
Lookupid, plus Text and Number field types

Method 3: Filter by Current Logged-In User

This one comes up all the time — “show me only the items that belong to me.” Here’s how to do it.

Filter by Current User’s Email

// Get the current user's details
const currentUser = await this.sp.web.currentUser();

// Filter list items where AssignedTo matches current user's email
const myItems = await this.sp.web.lists.getByTitle("Tasks")
.items
.filter(`AssignedTo/EMail eq '${currentUser.Email}'`)
.expand("AssignedTo")
.select("Title", "AssignedTo/EMail", "AssignedTo/Title")
();

console.log(myItems);

Filter by Current User’s ID

This is more reliable since user IDs don’t change when someone’s name changes:

const currentUser = await this.sp.web.currentUser();

const myTasks = await this.sp.web.lists.getByTitle("Tasks")
.items
.filter(`AssignedToId eq ${currentUser.Id}`)
.select("Title", "Status", "DueDate")
();

You can also pull user info from pageContext without an extra API call — useful for simple scenarios:

const userEmail = this.context.pageContext.user.email;
const userLoginName = this.context.pageContext.user.loginName;

const myItems = await this.sp.web.lists.getByTitle("Projects")
.items
.filter(`Author/EMail eq '${userEmail}'`)
.expand("Author")
.select("Title", "Author/EMail")
();

Method 4: CAML Query for Metadata/Taxonomy Fields

There’s one type of field where OData $filter just doesn’t work — taxonomy/metadata fields. If you’re filtering on a Managed Metadata column, you have to use CAML Query.

const camlQuery = {
ViewXml: `
<View>
<Query>
<Where>
<Eq>
<FieldRef Name="ProjectCategory" />
<Value Type="TaxonomyFieldType">Engineering</Value>
</Eq>
</Where>
</Query>
<RowLimit>50</RowLimit>
</View>
`
};

const items = await this.sp.web.lists.getByTitle("Projects")
.getItemsByCAMLQuery(camlQuery);

console.log(items);

CAML Query with Multiple Conditions

const camlQuery = {
ViewXml: `
<View>
<Query>
<Where>
<And>
<Eq>
<FieldRef Name="ProjectCategory" />
<Value Type="TaxonomyFieldType">Engineering</Value>
</Eq>
<Eq>
<FieldRef Name="Status" />
<Value Type="Choice">Active</Value>
</Eq>
</And>
</Where>
</Query>
</View>
`
};

Use CAML only when you have to — it’s verbose and less readable than OData. For everything else, stick with the PnPjs .filter() approach.

Handling Large Lists (Over 5,000 Items)

SharePoint has a List View Threshold of 5,000 items. If your filter hits an unindexed column on a list that large, you’ll get a dreaded threshold exception. Here’s how to avoid it.

Step 1: Index Your Columns

Before anything else, go to List Settings → Indexed Columns and add an index on the column you’re filtering on most. This is a SharePoint admin task, not a code task — but it’s the most important one.

Step 2: Use $top to Limit Results

Always limit how many items you pull at once:

const items = await this.sp.web.lists.getByTitle("BigList")
.items
.filter("Status eq 'Active'")
.top(100)
();

Step 3: Use Async Paging for Large Datasets

When you need to loop through a big list in chunks without blowing the threshold, PnPjs’s async iterator is your friend:

const allItems: any[] = [];

for await (const batch of this.sp.web.lists.getByTitle("BigList").items.top(500)) {
allItems.push(...batch);

// Optional: stop after a certain number
if (allItems.length >= 2000) break;
}

console.log("Total items retrieved:", allItems.length);

Each loop iteration fetches the next page automatically — no skip tokens to manage manually.

Step 4: Filter on Indexed Columns First

If you have a compound filter, put the indexed column first in your query. SharePoint evaluates filters left to right, so leading with an indexed column means it narrows down the result set quickly before checking any non-indexed columns.

// GOOD: Indexed column (Status) first, then non-indexed (Description contains check)
.filter("Status eq 'Active' and substringof('urgent', Description)")

// RISKY: Non-indexed check runs first on the full list
.filter("substringof('urgent', Description) and Status eq 'Active'")

Putting It All Together — Real-World Example

Here’s a complete, realistic example: a task dashboard web part that shows only the current user’s active tasks, sorted by due date.

import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import { IListItem } from "@pnp/sp/items";

interface TaskItem extends IListItem {
Title: string;
Status: string;
DueDate: string;
AssignedToId: number;
Priority: number;
}

public async getMyActiveTasks(): Promise<TaskItem[]> {
const sp = spfi().using(SPFx(this.context));

// Get current user ID from page context (no extra API call needed)
const currentUserId = this.context.pageContext.legacyPageContext.userId;

const tasks = await sp.web.lists.getByTitle("Tasks")
.items
.filter<TaskItem>(f =>
f.number("AssignedToId").equals(currentUserId)
.and()
.text("Status").notEquals("Completed")
)
.select("Title", "Status", "DueDate", "AssignedToId", "Priority")
.orderBy("DueDate", true)
.top(50)
();

return tasks;
}

Clean, readable, and server-filtered — exactly how it should work.

Common Mistakes to Avoid

Here are the things that trip up most people the first time:

  • Using the display name instead of internal name — SharePoint columns have an internal name (like AssignedTo) that might differ from the display name (“Assigned To”). Always check the internal name in List Settings.
  • Forgetting to expand when filtering on People/Lookup fields — If you filter on AssignedTo/EMail, you must also add .expand(“AssignedTo”), or you’ll get empty results.
  • Filtering on a non-indexed column in a large list — This will throw a threshold error. Index the column first.
  • Using $skip for paging — SharePoint REST doesn’t support $skip for list items. Use $skiptoken or PnPjs’s async iterator instead.
  • Forgetting the datetime format — Date filters need this exact format: datetime'2024-01-01T00:00:00Z'. A plain '2024-01-01' won’t work.
Filter SharePoint List Items in SharePoint Framework

Quick Cheat Sheet

// Simple text filter
.filter("Status eq 'Active'")

// Number comparison
.filter("Priority gt 2")

// Date comparison
.filter("Created ge datetime'2024-01-01T00:00:00Z'")

// Contains text
.filter("substringof('keyword', Title)")

// Starts with
.filter("startswith(Title, 'Project A')")

// Combined AND
.filter("Status eq 'Active' and Priority gt 1")

// Fluent filter (PnPjs v3)
.filter<MyItem>(f => f.text("Status").equals("Active"))

// Filter by current user
.filter(`AssignedToId eq ${currentUserId}`)

// Taxonomy field — use CAML instead
getItemsByCAMLQuery({ ViewXml: `<View>...<Value Type="TaxonomyFieldType">Tag</Value>...</View>` })

Conclusion

I hope you found this article helpful! Here, I explained three methods for filtering SharePoint list items in the SPFx web part, with examples. If you are also looking to get the data from the SharePoint list that matches your requirements, then follow this article.

Based on your requirements, choose suitable methods and apply filters in the SPFx web part. If you have any doubts about this, feel free to comment below.

Also, you may like:

Power Apps functions free pdf

30 Power Apps Functions

This free guide walks you through the 30 most-used Power Apps functions with real business examples, exact syntax, and results you can see.

Live Webinar

SharePoint Integration Power Apps Form With Repeating Table [Invoice Management System]

Learn how to build an invoice management system using SharePoint integration and a repeating table.

📅 2nd September 2026 – 10:00 AM EST | 7:30 PM IST

Download User registration canvas app

DOWNLOAD USER REGISTRATION POWER APPS CANVAS APP

Download a fully functional Power Apps Canvas App (with Power Automate): User Registration App