How to Export Data to Excel in SharePoint Framework (SPFx)

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)
  • 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:

MethodLibrary UsedBest For
Method 1xlsx + file-saverQuick, simple exports
Method 2ExcelJS + file-saverStyled Excel files with formatting
Method 3SPFx ListView Command SetExporting 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.

export sharepoint list data into excel using spfx

Now, let’s see the steps to achieve this functionality.

  1. 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
  1. 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
  1. Initialize PnPjs in Your Web Part. Open ExportToExcelWebPart.ts and update the onInit method. First, create a pnpjsConfig.ts file 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);
}
  1. 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:

  1. When the user clicks the button, handleExport fires
  2. PnPjs fetches items from your SharePoint list using a clean, fluent API call
  3. The data gets mapped to a plain array of objects
  4. XLSX.utils.sheet_add_aoa writes the headers in row 1
  5. XLSX.utils.sheet_add_json writes the data starting from row 2
  6. saveAs from file-saver triggers the browser download dialog
  1. 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.

Creating Formatted Excel Files in SPFx with ExcelJS

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 autoFilter call 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.

sharepoint list view command set extensions for exporting data

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 FileLeafRefAssignedTo) 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:

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