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 string
  • XLSX.write(wb, opts) serializes a workbook to an ArrayBuffer or other format
  • XLSX.utils contains 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 useDownload to trigger .xlsx file downloads
  • Combine with useFileUpload to 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

OptionTypeDefaultDescription
headernumber | string[]1 returns arrays; string array overrides headers; omit for objects keyed by first row
defvalanyDefault value for empty cells (e.g., "" or 0)
rawbooleantrueIf false, formats values as strings using cell formats
rangenumber | stringStart row (0-indexed number) or cell range string like "A2:D100"
blankrowsbooleanfalseInclude 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

OptionTypeDefaultDescription
bookTypestring"xlsx"Output format: "xlsx", "xls", "csv"
typestringOutput type: "array" (ArrayBuffer), "base64", "binary"
compressionbooleanfalseEnable 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

  1. Use type: "array" when reading — pass ArrayBuffer from fetch().arrayBuffer() or FileReader
  2. Use XLSX.write + useDownload for exports — do not use XLSX.writeFile (it won't work in the sandbox)
  3. Use defval: "" when parsing — prevents undefined for empty cells
  4. Set column widths with !cols — improves readability of exported files
  5. Use json_to_sheet for structured data and aoa_to_sheet for raw grids or custom layouts
  6. Override headers with sheet_add_aoa at origin: "A1" — gives user-friendly column names
  7. Handle multi-sheet workbooks — check workbook.SheetNames and let users pick a sheet
  8. Accept .xlsx,.xls,.csv in file inputs — SheetJS handles all three formats transparently
  9. Show loading state during import/export — parsing large files can take a moment
  10. Sanitize imported data — validate and clean parsed rows before persisting