---
pagination_prev: getting-started/index
sidebar_position: 2
---

# Tutorial

SheetJS presents a simple JS interface that works with "Array of Arrays" and
"Array of JS Objects".  The API functions are building blocks that should be
combined with other JS APIs to solve problems.

The discussion focuses on the problem solving mindset.  API details are covered
in other parts of the documentation.

The goal of this example is to generate a XLSX workbook of US President names
and birthdays.  [Click here](#live-demo) to jump to the live demo. The sequence
diagram below shows the process:

```mermaid
sequenceDiagram
  actor U as User
  participant P as Page
  participant A as API
  U->>P: click button
  P->>A: fetch data
  A->>P: raw data
  Note over P: process data
  Note over P: make workbook
  Note over P: setup download
  P->>U: download workbook
```


## Acquire Data

### Raw Data

[The raw data is available in JSON form](https://theunitedstates.io/congress-legislators/executive.json).
For convenience, it has been [mirrored here](https://sheetjs.com/data/executive.json)

Acquiring the data is straightforward with `fetch`:

```js
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
```

The raw data is an Array of objects. This is the data for John Adams:

```js
{
  "id": { /* (data omitted) */ },
  "name": {
    "first": "John",          // <-- first name
    "last": "Adams"           // <-- last name
  },
  "bio": {
    "birthday": "1735-10-19", // <-- birthday
    "gender": "M"
  },
  "terms": [
    { "type": "viceprez", /* (other fields omitted) */ },
    { "type": "viceprez", /* (other fields omitted) */ },
    { "type": "prez", /* (other fields omitted) */ }
  ]
}
```

### Filtering for Presidents

The dataset includes Aaron Burr, a Vice President who was never President!

`Array#filter` creates a new array with the desired rows.  A President served
at least one term with `type` set to `"prez"`.  To test if a particular row has
at least one `"prez"` term, `Array#some` is another native JS function.  The
complete filter would be:

```js
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
```

### Reshaping the Array

For this example, the name will be the first name combined with the last name
(`row.name.first + " " + row.name.last`) and the birthday will be available at
`row.bio.birthday`.  Using `Array#map`, the dataset can be massaged in one call:

```js
const rows = prez.map(row => ({
  name: row.name.first + " " + row.name.last,
  birthday: row.bio.birthday
}));
```

The result is an array of "simple" objects with no nesting:

```js
[
  { name: "George Washington", birthday: "1732-02-22" },
  { name: "John Adams", birthday: "1735-10-19" },
  // ... one row per President
]
```

## Create a Workbook

With the cleaned dataset, `XLSX.utils.json_to_sheet` generates a worksheet:

```js
const worksheet = XLSX.utils.json_to_sheet(rows);
```

`XLSX.utils.book_new` creates a new workbook and `XLSX.utils.book_append_sheet`
appends a worksheet to the workbook. The new worksheet will be called "Dates":

```js
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
```

## Clean up Workbook

The data is in the workbook and can be exported.

![Rough export](pathname:///example/rough.png)

There are multiple opportunities for improvement: the headers can be renamed and
the column widths can be adjusted.  [SheetJS Pro](https://sheetjs.com/pro) offers
additional styling options like cell styling and frozen rows.

<details><summary><b>Changing Header Names</b> (click to show)</summary>

By default, `json_to_sheet` creates a worksheet with a header row. In this case,
the headers come from the JS object keys: "name" and "birthday".

The headers are in cells `A1` and `B1`.  `XLSX.utils.sheet_add_aoa` can write
text values to the existing worksheet starting at cell `A1`:

```js
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
```

</details>

<details><summary><b>Changing Column Widths</b> (click to show)</summary>

Some of the names are longer than the default column width.  Column widths are
set by setting the `"!cols"` worksheet property.

The following line sets the width of column A to approximately 10 characters:

```js
worksheet["!cols"] = [ { wch: 10 } ]; // set column A width to 10 characters
```

One `Array#reduce` call over `rows` can calculate the maximum width:

```js
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
```

</details>

## Export a File

`XLSX.writeFile` creates a spreadsheet file and tries to write it to the system.
In the browser, it will try to prompt the user to download the file.  In NodeJS,
it will write to the local directory.

:::note

`XLSX.writeFileXLSX` only writes XLSX files and is recommended when the export
will always be in the `.xlsx` format. `writeFileXLSX` is more amenable to tree
shaking.  This example uses `XLSX.writeFile` since `writeFileXLSX` does not
support other common export formats like `.xls` or `.xlsb` or `.csv`.

`compression: true` enables ZIP compression for XLSX and other formats.

:::

```js
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
```

![Final export](pathname:///example/final.png)


## Live Demo

This demo runs in the web browser!  Click "Click to Generate File!" and the
browser should generate a XLSX file.

```jsx live
function Presidents() { return ( <button onClick={async () => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  const raw_data = await (await fetch(url)).json();

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = XLSX.utils.json_to_sheet(rows);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* create an XLSX file and try to save to Presidents.xlsx */
  XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
}}><b>Click to Generate file!</b></button> ); }
```

## Run the Demo Locally

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs>
  <TabItem value="browser" label="Web Browser">

Save the following script to `snippet.html` and open the page.  The page must be
hosted (no `file:///` access).

<https://sheetjs.com/pres.html> is a hosted version of the page.

```html
<body>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script>
(async() => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  const raw_data = await (await fetch(url)).json();

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = XLSX.utils.json_to_sheet(rows);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* create an XLSX file and try to save to Presidents.xlsx */
  XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
</script>
<body>
```
  </TabItem>
  <TabItem value="nodejs" label="Command-Line (NodeJS)">

Install the dependencies:

```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
```

Save the following script to `snippet.js` and run `node snippet.js`:

```js title="snippet.js"
const XLSX = require("xlsx");

(async() => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  const raw_data = await (await fetch(url)).json();

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = XLSX.utils.json_to_sheet(rows);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* create an XLSX file and try to save to Presidents.xlsx */
  XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
```

:::caution

Native `fetch` support was added in NodeJS 18.  For older versions of NodeJS,
the script will throw an error `fetch is not defined`.  A third-party library
like `axios` presents a similar API for fetching data:

<details><summary><b>Example using axios</b> (click to show)</summary>

Install the dependencies:

```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz axios
```

The differences in the script are highlighted below.

```js title="snippet.js"
const XLSX = require("xlsx");
// highlight-next-line
const axios = require("axios");

(async() => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  // highlight-next-line
  const raw_data = (await axios(url, {responseType: "json"})).data;

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = XLSX.utils.json_to_sheet(rows);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* create an XLSX file and try to save to Presidents.xlsx */
  XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
```

</details>

:::

<details><summary><b>Other Server-Side Platforms</b> (click to show)</summary>

<Tabs>
  <TabItem value="deno" label="Deno">

Save the following script to `snippet.ts` and run with
`deno run --allow-net --allow-write snippet.ts`:

```js title="snippet.ts"
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';

/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();

/* filter for the Presidents */
const prez = raw_data.filter((row: any) => row.terms.some((term: any) => term.type === "prez"));

/* flatten objects */
const rows = prez.map((row: any) => ({
  name: row.name.first + " " + row.name.last,
  birthday: row.bio.birthday
}));

/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

/* calculate column width */
const max_width = rows.reduce((w: number, r: any) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];

/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
```

  </TabItem>
  <TabItem value="bun" label="Bun">

Download <https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs> to `xlsx.mjs`

Save the following script to `snippet.js` and run with `bun snippet.js`:

```js title="snippet.js"
import * as XLSX from './xlsx.mjs';
import * as fs from 'fs';
XLSX.set_fs(fs);

/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();

/* filter for the Presidents */
const prez = raw_data.filter((row) => row.terms.some((term) => term.type === "prez"));

/* flatten objects */
const rows = prez.map((row) => ({
  name: row.name.first + " " + row.name.last,
  birthday: row.bio.birthday
}));

/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];

/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
```

  </TabItem>
</Tabs>

</details>

  </TabItem>

  <TabItem value="desktop" label="Desktop App">

Save the following script to `snippet.html`:

```html title="snippet.html"
<body>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script>
(async() => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  const raw_data = await (await fetch(url)).json();

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = XLSX.utils.json_to_sheet(rows);
  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* create an XLSX file and try to save to Presidents.xlsx */
  XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
</script>
<body>
```

Save the following to `package.json`:

```json title="package.json"
{
  "name": "sheetjs-nwjs",
  "author": "sheetjs",
  "version": "0.0.0",
  "main": "snippet.html",
  "dependencies": {
    "nw": "~0.66.0",
    "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
  }
}
```

Install dependencies and build the app:

```bash
npm i
npx -p nw-builder nwbuild --mode=build .
```

Run the generated app in the `build\sheetjs-nwjs` folder.  It will show a save
dialog box. After selecting a path, the app will write the file.

  </TabItem>
  <TabItem value="mobile" label="Mobile App">

:::caution

This demo runs in iOS and requires a Macintosh computer with Xcode installed.

:::

:::note Initial Setup

Follow the [Environment Setup](https://reactnative.dev/docs/environment-setup)
of the React Native documentation before testing the demo.

:::

Create a new project by running the following commands in the Terminal:

```bash
npx react-native init SheetJSPres --version="0.70.6"
cd SheetJSPres

npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-native-blob-util@0.17.1

mkdir -p patches
curl -L -o patches/react-native+0.70.6.patch https://github.com/facebook/react-native/files/10356761/react-native%2B0.70.6.patch
npx patch-package

cd ios
pod install
cd ..
```

Save the following to `App.js` in the project:

```js title="App.js"
import React from 'react';
import { Alert, Button, SafeAreaView, Text, View } from 'react-native';
import { utils, version, write } from 'xlsx';
import RNFetchBlob from 'react-native-blob-util';

const make_workbook = async() => {
  /* fetch JSON data and parse */
  const url = "https://sheetjs.com/data/executive.json";
  const raw_data = await (await fetch(url)).json();

  /* filter for the Presidents */
  const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));

  /* flatten objects */
  const rows = prez.map(row => ({
    name: row.name.first + " " + row.name.last,
    birthday: row.bio.birthday
  }));

  /* generate worksheet and workbook */
  const worksheet = utils.json_to_sheet(rows);
  const workbook = utils.book_new();
  utils.book_append_sheet(workbook, worksheet, "Dates");

  /* fix headers */
  utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

  /* calculate column width */
  const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
  worksheet["!cols"] = [ { wch: max_width } ];

  /* React Native does not support `writeFile`. This is a low-level write ! */

  /* write workbook to buffer */
  const buf = write(workbook, {type:'buffer', bookType:"xlsx"});

  /* write buffer to file */
  const filename = RNFetchBlob.fs.dirs.DocumentDir + "/Presidents.xlsx";
  await RNFetchBlob.fs.writeFile(filename, Array.from(buf), 'ascii');

  return filename;
};

const App = () => ( <SafeAreaView><View style={{ marginTop: 32, padding: 24 }}>
  <Text style={{ fontSize: 24, fontWeight: 'bold' }}>SheetJS {version} Export Demo</Text>
  <Button title='Press to Export' onPress={async() => {
    try {
      const filename = await make_workbook();
      Alert.alert("Export Finished", `Exported to ${filename}`);
    } catch(err) {
      Alert.alert("Export Error", `Error ${err.message||err}`);
    }
  }}/>
</View></SafeAreaView> );

export default App;
```

Test the app in the iOS simulator:

```bash
npm run ios
```

After clicking "Press to Export", the app will show an alert with the location
to the generated file.

  </TabItem>
</Tabs>