If you’ve built a SharePoint Framework web part that displays list data, sooner or later someone’s going to ask: “Can I download this as an Excel file?”
The answer is yes — and it’s actually not that hard once you know which libraries to use and how to wire them together. In this tutorial, I’ll walk you through three practical methods to export data to Excel from an SPFx web part, from the simplest approach to more advanced ones. You’ll find real code you can drop into your project and adapt right away.
Let me also tell you which method is best for which scenario, so you’re not overcomplicating things.
SPFx Prerequisites for Exporting Data to Excel
Before jumping into code, make sure you have:
- Node.js (v18.x LTS recommended for SPFx 1.20+)
- SPFx Yeoman generator installed (
npm install -g @microsoft/generator-sharepoint) - A SharePoint Online site and a list to pull data from
- Basic familiarity with React-based SPFx web parts
- Visual Studio Code (or any editor you prefer)
If you are new to SPFx development, check out our complete SharePoint Framework training course.
SPFx Excel Export Methods Covered in This Guide
Here’s a quick overview of the three approaches:
| Method | Library Used | Best For |
|---|---|---|
| Method 1 | xlsx + file-saver | Quick, simple exports |
| Method 2 | ExcelJS + file-saver | Styled Excel files with formatting |
| Method 3 | SPFx ListView Command Set | Exporting selected items directly from a list view |
Let’s go through each one.
Method 1: Exporting Data to Excel in SPFx Using xlsx.js and FileSaver.js
This is the most common approach in the SPFx community, and for good reason — it’s lightweight, well-documented, and gets the job done with minimal setup.
In the image below, you can see that I retrieved the SharePoint list “Employees” data into the SPFx web part, and at the top, I gave an Export to Excel button. Now, when we click it, an Excel file downloads and displays the details shown in the web part.

Now, let’s see the steps to achieve this functionality.
- Scaffold the SPFx Project: Open your Node.js command prompt and run:
md ExportToExcel
cd ExportToExcel
yo @microsoft/sharepoint
When the generator prompts you:
- Solution name: ExportToExcel
- Baseline packages: SharePoint Online only (latest)
- Component type: WebPart
- Web part name: ExportToExcel
- Framework: React
- Install the Required Packages
npm install @pnp/sp @pnp/logging --save
npm install xlsx file-saver --save
npm install @types/file-saver --save-dev
Here’s what each package does:
- @pnp/sp — Gives you a clean, fluent API to query SharePoint lists without writing raw REST calls
- xlsx — Parses and writes Excel spreadsheet formats (.xlsx)
- file-saver — Handles the browser-side file download
- Initialize PnPjs in Your Web Part. Open
ExportToExcelWebPart.tsand update theonInitmethod. First, create apnpjsConfig.tsfile in your web part folder:
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { spfi, SPFI, SPFx } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
let _sp: SPFI | undefined = undefined;
export const getSP = (context?: WebPartContext): SPFI => {
if (context !== undefined) {
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp!;
};
Then in your ExportToExcelWebPart.ts:
import { getSP } from './pnpjsConfig';
public async onInit(): Promise<void> {
await super.onInit();
getSP(this.context);
}
- Build the Export Logic. Now open (or create) your main React component
ExportToExcel.tsx. Here’s the full working code:
import * as React from 'react';
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
import { getSP } from '../pnpjsConfig';
import styles from './ExportToExcel.module.scss';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
interface IEmployeeItem {
Title: string;
Department: string;
Location: string;
}
const FILE_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8';
const FILE_EXTENSION = '.xlsx';
const exportToExcel = (data: IEmployeeItem[]): void => {
if (data.length === 0) {
alert("No data to export!");
return;
}
const headers = [["Employee Name", "Department", "Location"]];
// aoa_to_sheet creates a proper worksheet (book_new() creates a workbook, not a worksheet)
const ws = XLSX.utils.aoa_to_sheet(headers);
XLSX.utils.sheet_add_json(ws, data, { origin: 'A2', skipHeader: true });
const wb = { Sheets: { 'Employees': ws }, SheetNames: ['Employees'] };
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], { type: FILE_TYPE });
saveAs(blob, 'EmployeeData' + FILE_EXTENSION);
};
const ExportToExcel: React.FC = () => {
const sp = getSP();
const [items, setItems] = React.useState<IEmployeeItem[]>([]);
const [loading, setLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>('');
React.useEffect(() => {
sp.web.lists
.getByTitle("Employees")
.items
.select("Title", "Department", "Location")()
.then((data: IEmployeeItem[]) => {
const cleanItems = data.map(item => ({
Title: item.Title,
Department: item.Department,
Location: item.Location
}));
setItems(cleanItems);
setLoading(false);
})
.catch((err: Error) => {
setError("Failed to load data: " + err.message);
setLoading(false);
});
}, []);
const handleExport = (): void => {
exportToExcel(items);
};
if (loading) {
return <div className={styles.container}>Loading...</div>;
}
if (error) {
return <div className={styles.container}><p className={styles.error}>{error}</p></div>;
}
return (
<div className={styles.container}>
<div className={styles.header}>
<h2>Employee List</h2>
<button className={styles.exportButton} onClick={handleExport}>
Export to Excel
</button>
</div>
{items.length === 0 ? (
<p>No items found in the Employees list.</p>
) : (
<table className={styles.table}>
<thead>
<tr>
<th>Employee Name</th>
<th>Department</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{items.map((item, index) => (
<tr key={index}>
<td>{item.Title}</td>
<td>{item.Department}</td>
<td>{item.Location}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
};
export default ExportToExcel;
What’s happening here step by step:
- When the user clicks the button,
handleExportfires - PnPjs fetches items from your SharePoint list using a clean, fluent API call
- The data gets mapped to a plain array of objects
XLSX.utils.sheet_add_aoawrites the headers in row 1XLSX.utils.sheet_add_jsonwrites the data starting from row 2saveAsfrom file-saver triggers the browser download dialog
- Deploy your web part
heft build --clean
heft package-solution --production
Then upload the .sppkg file from the sharepoint/solution folder to your App Catalog, install it on your site, and add the web part to a page.
Check out Call an Azure Function from an SPFx Web Part
Method 2: Creating Formatted Excel Files in SPFx with ExcelJS
If you need a styled Excel file — think column widths, bold headers, colored rows, or a logo in the header — ExcelJS is the library you want. It gives you fine-grained control over every cell, as in the image below.

Install ExcelJS
npm install exceljs file-saver --save
npm install @types/file-saver --save-dev
The Export Function with Styling
Here’s a practical example that creates an Excel file with bold headers, auto-sized columns, and a timestamp in the filename:
import * as React from 'react';
import { saveAs } from 'file-saver';
import { getSP } from '../pnpjsConfig';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
interface IProjectItem {
Title: string;
Status: string;
AssignedTo: string;
DueDate: string;
}
const exportWithExcelJS = async (data: IProjectItem[]): Promise<void> => {
// Dynamically import ExcelJS to keep bundle size in check
const ExcelJS = await import('exceljs');
const workbook = new ExcelJS.Workbook();
workbook.creator = 'SharePoint Export';
workbook.created = new Date();
const worksheet = workbook.addWorksheet('Projects', {
pageSetup: { paperSize: 9, orientation: 'landscape' }
});
// Define columns with headers and widths
worksheet.columns = [
{ header: 'Project Name', key: 'Title', width: 30 },
{ header: 'Status', key: 'Status', width: 15 },
{ header: 'Assigned To', key: 'AssignedTo', width: 25 },
{ header: 'Due Date', key: 'DueDate', width: 15 },
];
// Style the header row
const headerRow = worksheet.getRow(1);
headerRow.font = { bold: true, color: { argb: 'FFFFFFFF' }, size: 12 };
headerRow.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF0070C0' } // SharePoint blue
};
headerRow.alignment = { vertical: 'middle', horizontal: 'center' };
headerRow.height = 25;
// Add data rows with alternating row colors
data.forEach((item, index) => {
const row = worksheet.addRow({
Title: item.Title,
Status: item.Status,
AssignedTo: item.AssignedTo,
DueDate: item.DueDate
});
// Alternate row shading
if (index % 2 === 0) {
row.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFF2F2F2' }
};
}
// Add a border to each cell
row.eachCell(cell => {
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
});
// Auto-filter on header row
worksheet.autoFilter = {
from: 'A1',
to: `D1`
};
// Generate the file and trigger download
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const timestamp = new Date().toISOString().split('T')[0];
saveAs(blob, `Projects_Export_${timestamp}.xlsx`);
};
const ProjectExport: React.FC = () => {
const sp = getSP();
const handleExport = async (): Promise<void> => {
const items: IProjectItem[] = await sp.web.lists
.getByTitle("Projects")
.items
.select("Title", "Status", "AssignedTo", "DueDate")();
await exportWithExcelJS(items);
};
return (
<div>
<button onClick={handleExport}>Export Projects to Excel</button>
</div>
);
};
export default ProjectExport;
A few things worth noting here:
- I’m using
await import('exceljs')(dynamic import) instead of a static import. This helps keep your web part’s initial bundle size smaller, because ExcelJS is a fairly large library. - The
autoFiltercall adds Excel’s built-in dropdown filters to your header row — users love this. - The timestamp in the filename (
Projects_Export_2025-03-26.xlsx) helps users know which export they’re looking at when they check their downloads folder.
Read SPFx Drag and Drop File Upload: Single & Multiple Files
Method 3: SPFx ListView Command Set (Export Selected Items)
Sometimes you don’t want a full web part; you just want an “Export” button that appears in the SharePoint list command bar when items are selected. That’s what a ListView Command Set extension does.

This is actually one of the cleanest solutions for end users because it fits right into the native list interface.
Scaffold a Command Set Extension
yo @microsoft/sharepoint
When prompted:
- Component type: Extension
- Extension type: ListView Command Set
- Name: ExportSelectedItems
Updating the SPFx ListView Command Set Code
Open ExportSelectedItemsCommandSet.ts and update it like this:
import { override } from '@microsoft/decorators';
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetExecuteEventParameters,
ListViewStateChangedEventArgs
} from '@microsoft/sp-listview-extensibility';
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
export default class ExportSelectedItemsCommandSet
extends BaseListViewCommandSet<{}> {
@override
public onInit(): Promise<void> {
this.context.listView.listViewStateChangedEvent.add(
this,
this._onListViewStateChanged
);
return Promise.resolve();
}
@override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
if (event.itemId === 'EXPORT_SELECTED') {
this._exportSelectedItems();
}
}
private _exportSelectedItems(): void {
const selectedItems = this.context.listView.selectedRows;
if (!selectedItems || selectedItems.length === 0) {
alert("Please select at least one item to export.");
return;
}
// Pull visible column values from selected items
const data = selectedItems.map(row => {
const obj: { [key: string]: any } = {};
this.context.listView.columns.forEach(col => {
obj[col.displayName] = row.getValueByName(col.internalName);
});
return obj;
});
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Selected Items");
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
saveAs(blob, 'SelectedItems.xlsx');
}
private _onListViewStateChanged = (args: ListViewStateChangedEventArgs): void => {
const exportCommand: Command = this.tryGetCommand('EXPORT_SELECTED');
if (exportCommand) {
// Show button only when items are selected
exportCommand.visible = this.context.listView.selectedRows?.length > 0;
this.raiseOnChange();
}
}
}
Update your ExportSelectedItemsCommandSet.manifest.json to register the command:
{
"items": {
"EXPORT_SELECTED": {
"title": { "default": "Export to Excel" },
"iconImageUrl": "icons/export.png",
"type": "command"
}
}
}
What makes this approach nice:
- The export button only appears when items are selected, keeping the UI clean
- It automatically picks up whatever columns are visible in the current view — no hardcoding
- Users get exactly what they see on screen, not more
Handling Large SharePoint Lists in SPFx (Over 5,000 Items)
SharePoint has a list view threshold of 5,000 items. If your list has more than that, a regular .items() call will throw an error. Here’s how to handle it with PnPjs pagination:
const getAllItems = async (): Promise<any[]> => {
const sp = getSP();
let allItems: any[] = [];
// getAll() handles pagination automatically
const items = await sp.web.lists
.getByTitle("LargeList")
.items
.select("Title", "Status", "Category")
.getAll(5000); // fetches in batches of 5000
allItems = items;
return allItems;
};
PnPjs’s .getAll() method handles the pagination for you behind the scenes — it keeps making requests until it’s pulled in every item. Just be aware that for very large lists (50,000+ items), this can take a while, so consider adding a loading indicator or a progress bar for users.
SPFx Best Practices for Exporting Data to Excel
- Column names matter: When you map your SharePoint list data to Excel, use the internal field names (like
FileLeafRef,AssignedTo) in PnPjs queries, then map them to human-friendly display names for Excel headers. - Date fields: SharePoint returns dates in ISO format. You’ll want to format them before writing to Excel — something like
new Date(item.DueDate).toLocaleDateString(). - Lookup fields: If your list has lookup columns, you’ll need to expand them in PnPjs:
.select("Manager/Title").expand("Manager"). - Bundle size: ExcelJS is ~1.5MB. Use dynamic imports (
await import('exceljs')) to avoid slowing down your web part’s initial load. - xlsx library security note: The
xlsx(SheetJS) free community edition hasn’t had a new npm release in a while. If security scanning flags it, you can use ExcelJS as a full alternative — it’s actively maintained.
Which Method Should You Use?
Here’s my honest take:
- Use Method 1 (xlsx + file-saver) if you just need a clean data dump with no special formatting. It’s fast to set up and works great for most reporting needs.
- Use Method 2 (ExcelJS) if your users expect a polished Excel file — branded colors, bold headers, auto-filters. The extra setup is worth it when presentation matters.
- Use Method 3 (Command Set) if you want the export to live inside the list view itself, and users need to cherry-pick which rows to export.
All three methods work in SharePoint Online (Microsoft 365) as of Aug 2026 with SPFx 1.20+.
Conclusion
I hope you found this article helpful. In this guide, I explained three different ways to export data to Excel in SPFx. Each method has its own advantages, and the best choice depends on your project requirements.
If you need a simple export, xlsx.js and FileSaver.js are usually enough. If you want advanced formatting, multiple worksheets, or better control over the Excel file, ExcelJS is a better option. And if users need to export selected items directly from a SharePoint list, a ListView Command Set can provide a better experience.
Before choosing a method, think about the amount of data you need to export, the formatting requirements, and how users will interact with the solution. Using the right approach will help you build faster, more reliable, and more user-friendly SPFx solutions.
Also, you may like:
- SharePoint Framework (SPFx) Form Validation
- SPFx Environment Variables
- How to Create a Modal Popup in SPFx
- Customize SharePoint List Command Bar Download Button Using SPFx Extension

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.