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:
- OData
$filtervia REST API — raw, flexible, works everywhere - PnPjs
.filter()method — cleaner syntax wrapping the same REST calls - CAML Query — XML-based, necessary for taxonomy/metadata fields
Setting Up Your SPFx Web Part
- I’m assuming you already have an SPFx project set up. If not, run:
yo @microsoft/sharepoint
- Choose React or No Framework — the filtering concepts work the same either way.
- For PnPjs, install it in your project:
npm install @pnp/sp --save
- 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
| Operator | Meaning | Example |
|---|---|---|
eq | Equals | Status eq 'Active' |
ne | Not equals | Status ne 'Closed' |
gt | Greater than | Priority gt 2 |
ge | Greater than or equal | DueDate ge datetime'2024-01-01T00:00:00Z' |
lt | Less than | Budget lt 10000 |
le | Less than or equal | Age le 30 |
startswith | Starts with | startswith(Title,'Project A') |
substringof | Contains | substringof('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'`;
Method 2: Filter with PnPjs (Recommended)
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 Type | Available Operators |
|---|---|
| Text / Choice | equals, notEquals, startsWith, contains, in, notIn |
| Number | equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals |
| Date | greaterThan, lessThan, isBetween, isToday, greaterThanOrEquals, lessThanOrEquals |
| Boolean | isTrue, isFalse, isFalseOrNull |
| Lookup | id, 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
expandwhen 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
$skipfor paging — SharePoint REST doesn’t support$skipfor list items. Use$skiptokenor 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.

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:
- Format Dates in SharePoint Framework (SPFx)
- SPFx Web Part Audience Targeting: Show Content to the Right People
- SharePoint Framework (SPFx) Form Validation: A Complete Guide with Examples
- SPFx Environment Variables: A Practical Guide for Every Scenario
- Create a Modal Popup in SPFx (4 Methods with Full Examples)

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.