SheetJS xlsx Reference
Read and write Excel spreadsheets (XLSX, XLS, CSV) in the browser. Use SheetJS to parse uploaded spreadsheet files into structured data, or build workbooks from app data and export them as .xlsx downloads.
Import
import * as XLSX from "xlsx";
Core Concepts
XLSX.read(data, opts)parses a workbook from an ArrayBuffer, Uint8Array, or binary stringXLSX.write(wb, opts)serializes a workbook to an ArrayBuffer or other formatXLSX.utilscontains all helper functions for converting between worksheets and JavaScript data- Workbook = collection of named worksheets; created with
XLSX.utils.book_new() - Worksheet = a single sheet of data; created from arrays or objects
- Combine with
useDownloadto trigger.xlsxfile downloads - Combine with
useFileUploadto read user-uploaded spreadsheets
Reading Spreadsheet Files
Parse an Uploaded File
Use useFileUpload to get the file, fetch its contents as an ArrayBuffer, then parse with XLSX.read:
const response = await fetch(uploadedFile.downloadUrl);
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: "array" });
Parse an App Asset
For XLSX/XLS files uploaded as app assets, fetch and parse the same way:
const SPREADSHEET_ASSET_URL = "https://...blob-url-from-assets...";
const response = await fetch(SPREADSHEET_ASSET_URL);
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: "array" });
Inspecting a Workbook
const workbook = XLSX.read(arrayBuffer, { type: "array" });
workbook.SheetNames; // ["Sheet1", "Sheet2"] — array of sheet names
workbook.Sheets; // { "Sheet1": worksheet, "Sheet2": worksheet }
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
Extract Data as Array of Objects
sheet_to_json treats the first row as headers and returns typed objects:
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json<{ Name: string; Age: number; City: string }>(worksheet);
// [
// { Name: "Alice", Age: 30, City: "NYC" },
// { Name: "Bob", Age: 25, City: "LA" }
// ]
Extract Data as Array of Arrays
Use header: 1 to get raw arrays (useful when headers aren't clean):
const rows = XLSX.utils.sheet_to_json<(string | number)[]>(worksheet, { header: 1 });
// [
// ["Name", "Age", "City"],
// ["Alice", 30, "NYC"],
// ["Bob", 25, "LA"]
// ]
Get Column Headers
const allRows = XLSX.utils.sheet_to_json<string[]>(worksheet, { header: 1 });
const headers = allRows[0]; // ["Name", "Age", "City"]
sheet_to_json Options
| Option | Type | Default | Description |
|---|---|---|---|
header | number | string[] | — | 1 returns arrays; string array overrides headers; omit for objects keyed by first row |
defval | any | — | Default value for empty cells (e.g., "" or 0) |
raw | boolean | true | If false, formats values as strings using cell formats |
range | number | string | — | Start row (0-indexed number) or cell range string like "A2:D100" |
blankrows | boolean | false | Include blank rows in output |
Creating Workbooks
From Array of Objects
const data = [
{ Name: "Alice", Age: 30, City: "NYC" },
{ Name: "Bob", Age: 25, City: "LA" },
];
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "People");
From Array of Arrays
const data = [
["Name", "Age", "City"],
["Alice", 30, "NYC"],
["Bob", 25, "LA"],
];
const worksheet = XLSX.utils.aoa_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "People");
Custom Headers
Override the default headers generated by json_to_sheet:
const worksheet = XLSX.utils.json_to_sheet(data);
XLSX.utils.sheet_add_aoa(worksheet, [["Full Name", "Age (years)", "City"]], { origin: "A1" });
Column Widths
Set column widths via the !cols property (width in characters):
worksheet["!cols"] = [
{ wch: 20 }, // Column A: 20 characters wide
{ wch: 10 }, // Column B: 10 characters wide
{ wch: 15 }, // Column C: 15 characters wide
];
Auto-fit column widths based on data:
function autoFitColumns(worksheet: XLSX.WorkSheet, data: Record<string, any>[]) {
const headers = Object.keys(data[0] ?? {});
worksheet["!cols"] = headers.map((header) => {
const maxLen = Math.max(
header.length,
...data.map((row) => String(row[header] ?? "").length)
);
return { wch: Math.min(maxLen + 2, 50) };
});
}
Multi-Sheet Workbooks
const workbook = XLSX.utils.book_new();
const summarySheet = XLSX.utils.json_to_sheet(summaryData);
XLSX.utils.book_append_sheet(workbook, summarySheet, "Summary");
const detailSheet = XLSX.utils.json_to_sheet(detailData);
XLSX.utils.book_append_sheet(workbook, detailSheet, "Details");
const rawSheet = XLSX.utils.aoa_to_sheet(rawRows);
XLSX.utils.book_append_sheet(workbook, rawSheet, "Raw Data");
Exporting / Downloading Workbooks
Use XLSX.write to serialize the workbook, then useDownload to trigger the download:
const arrayBuffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
const blob = new Blob([arrayBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
download(blob, "export.xlsx");
XLSX.write Options
| Option | Type | Default | Description |
|---|---|---|---|
bookType | string | "xlsx" | Output format: "xlsx", "xls", "csv" |
type | string | — | Output type: "array" (ArrayBuffer), "base64", "binary" |
compression | boolean | false | Enable ZIP compression (smaller file size) |
Other Utility Functions
Worksheet to CSV
const csvString = XLSX.utils.sheet_to_csv(worksheet);
Worksheet to HTML
const htmlString = XLSX.utils.sheet_to_html(worksheet);
Add Data to Existing Worksheet
XLSX.utils.sheet_add_json(worksheet, moreData, { origin: -1 });
XLSX.utils.sheet_add_aoa(worksheet, [["Total", "", 1500]], { origin: -1 });
The origin: -1 option appends data after the last row.
Example: Upload Excel, Display in Table
import * as React from "react";
import * as XLSX from "xlsx";
type Row = Record<string, string | number | boolean>;
export default function App() {
const { uploadFile, isLoading } = useFileUpload();
const [sheetNames, setSheetNames] = React.useState<string[]>([]);
const [activeSheet, setActiveSheet] = React.useState("");
const [sheets, setSheets] = React.useState<Record<string, Row[]>>({});
const [columns, setColumns] = React.useState<string[]>([]);
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const uploaded = await uploadFile(file);
const response = await fetch(uploaded.downloadUrl);
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: "array" });
const parsed: Record<string, Row[]> = {};
for (const name of workbook.SheetNames) {
parsed[name] = XLSX.utils.sheet_to_json<Row>(workbook.Sheets[name], {
defval: "",
});
}
setSheets(parsed);
setSheetNames(workbook.SheetNames);
setActiveSheet(workbook.SheetNames[0]);
};
React.useEffect(() => {
if (activeSheet && sheets[activeSheet]?.length > 0) {
setColumns(Object.keys(sheets[activeSheet][0]));
} else {
setColumns([]);
}
}, [activeSheet, sheets]);
const rows = sheets[activeSheet] ?? [];
return (
<div>
<input
type="file"
accept=".xlsx,.xls,.csv"
onChange={handleFile}
disabled={isLoading}
/>
{isLoading && <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />}
{sheetNames.length > 1 && (
<select
value={activeSheet}
onChange={(event) => setActiveSheet(event.target.value)}
>
{sheetNames.map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
)}
{rows.length > 0 && (
<div className="border rounded-lg overflow-auto">
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col}>{col}</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
{columns.map((col) => (
<td key={col}>{String(row[col] ?? "")}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
Example: Export Data as Excel
import * as React from "react";
import * as XLSX from "xlsx";
import { toast } from "sonner";
import { Upload } from "lucide-react";
type Contact = { name: string; email: string; phone: string; company: string };
export default function App() {
const [contacts, setContacts] = usePersistentItem<Contact[]>("contacts", []);
const { uploadFile, isLoading } = useFileUpload();
const [importing, setImporting] = React.useState(false);
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setImporting(true);
try {
const uploaded = await uploadFile(file);
const response = await fetch(uploaded.downloadUrl);
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: "array" });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json<Contact>(worksheet, { defval: "" });
setContacts((prev) => [...prev, ...rows]);
toast.success(`Imported ${rows.length} contacts from ${workbook.SheetNames[0]}`);
} catch {
toast.error("Failed to import spreadsheet");
} finally {
setImporting(false);
}
};
return (
<div>
<div className="flex items-center gap-2">
<input
type="file"
accept=".xlsx,.xls,.csv"
onChange={handleFileSelect}
disabled={isLoading || importing}
/>
{(isLoading || importing) && <span className="inline-block w-4 h-4 animate-spin rounded-full border-2 border-current border-t-transparent" />}
</div>
<p className="text-sm text-muted-foreground">{contacts.length} contacts</p>
</div>
);
}
Best Practices
- Use
type: "array"when reading — pass ArrayBuffer fromfetch().arrayBuffer()or FileReader - Use
XLSX.write+useDownloadfor exports — do not useXLSX.writeFile(it won't work in the sandbox) - Use
defval: ""when parsing — preventsundefinedfor empty cells - Set column widths with
!cols— improves readability of exported files - Use
json_to_sheetfor structured data andaoa_to_sheetfor raw grids or custom layouts - Override headers with
sheet_add_aoaatorigin: "A1"— gives user-friendly column names - Handle multi-sheet workbooks — check
workbook.SheetNamesand let users pick a sheet - Accept
.xlsx,.xls,.csvin file inputs — SheetJS handles all three formats transparently - Show loading state during import/export — parsing large files can take a moment
- Sanitize imported data — validate and clean parsed rows before persisting