---
title: NeutralinoJS
sidebar_label: NeutralinoJS
description: Build data-intensive desktop apps using NeutralinoJS. Seamlessly integrate spreadsheets into your app using SheetJS. Quickly modernize Excel-powered business processes.
pagination_prev: demos/mobile/index
pagination_next: demos/data/index
sidebar_position: 5
sidebar_custom_props:
  summary: Webview + Lightweight Extensions
---

# Data Munging in NeutralinoJS

import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';

[NeutralinoJS](https://neutralino.js.org/) is a modern desktop app framework.
NeutralinoJS apps pair platform-native browser tools with a static web server.

[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.

This demo uses NeutralinoJS and SheetJS to pull data from a spreadsheet and
display the data in the app. We'll explore how to load SheetJS in a NeutralinoJS
app and use native features to read and write files.

The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:

<table><thead><tr>
  <th><a href="#complete-example">Win10</a></th>
  <th><a href="#complete-example">macOS</a></th>
  <th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>

![Win10 screenshot](pathname:///neu/win10.png)

</td><td>

![macOS screenshot](pathname:///neu/macos.png)

</td><td>

![Linux screenshot](pathname:///neu/linux.png)

</td></tr></tbody></table>

## Integration Details

The [SheetJS Standalone build](/docs/getting-started/installation/standalone)
can be added to the entry `index.html`

For code running in the window, native methods must be explicitly enabled in the
NeutralinoJS `neutralino.conf.json` settings file[^1].

- `os.*` enables the open and save dialog methods.
- `filesystem.*` enables reading and writing file data.

The starter app enables `os.*` so typically one line must be added:

```json title="neutralino.config.json"
  "nativeAllowList": [
    "app.*",
    "os.*",
// highlight-next-line
    "filesystem.*",
    "debug.log"
  ],
```

### Reading Files

There are three steps to reading files:

1) Show an open file dialog with `Neutralino.os.showOpenDialog`[^2]. This method
   resolves to the selected path.

2) Read raw data from the file with `Neutralino.filesystem.readBinaryFile`[^3].
   This method resolves to a standard `ArrayBuffer`.

3) Parse the data with the SheetJS `read` method[^4]. This method returns a
   SheetJS workbook object.

The following code example defines a single function `openFile` that performs
all three steps and returns a SheetJS workbook object:

```js
const filters = [
  {name: "Excel Binary Workbook", extensions: ["xls", "xlsb"]},
  {name: "Excel Workbook", extensions: ["xls", "xlsx"]},
]

async function openFile() {
  /* show open file dialog */
  const [filename] = await Neutralino.os.showOpenDialog(
    'Open a spreadsheet',
    { filters, multiSelections: false }
  );

  /* read data into an ArrayBuffer */
  const ab = await Neutralino.filesystem.readBinaryFile(filename);

  /* parse with SheetJS */
  const wb = XLSX.read(ab);
  return wb;
}
```

At this point, standard SheetJS utility functions[^5] can extract data from the
workbook object. The demo includes a button that calls `sheet_to_html`[^6] to
generate an HTML TABLE and add to the DOM:

```js
const open_button_callback = async() => {
  const wb = await openFile();

  /* get the first worksheet */
  // highlight-start
  const ws = wb.Sheets[wb.SheetNames[0]];
  // highlight-end

  /* get data from the first worksheet */
  // highlight-start
  const html = XLSX.utils.sheet_to_html(ws);
  // highlight-end

  /* display table */
  document.getElementById('info').innerHTML = html;
};
```

### Writing Files

There are three steps to reading files:

1) Show a file dialog with `Neutralino.os.showSaveDialog`[^7]. This method
   resolves to the selected path.

2) Write the data with the SheetJS `write` method[^8]. The output book type can
   be inferred from the selected file path. Using the `buffer` output type[^9],
   the method returns a `Uint8Array` object that plays nice with NeutralinoJS.

2) Write to file with `Neutralino.filesystem.writeBinaryFile`[^10].

The following code example defines a single function `saveFile` that performs
all three steps starting from a SheetJS workbook object:

```js
const filters = [
  {name: "Excel Binary Workbook", extensions: ["xls", "xlsb"]},
  {name: "Excel Workbook", extensions: ["xls", "xlsx"]},
]

async function saveFile(wb) {
  /* show save file dialog */
  const filename = await Neutralino.os.showSaveDialog(
    'Save to file',
    { filters }
  );

  /* Generate workbook */
  const bookType = filename.slice(filename.lastIndexOf(".") + 1);
  const data = XLSX.write(wb, { bookType, type: "buffer" });

  /* save data to file */
  await Neutralino.filesystem.writeBinaryFile(filename, data);
}
```

The demo includes a button that calls `table_to_book`[^11] to generate a
workbook object from the HTML table:

```js
const save_button_callback = async() => {
  /* get the table */
  const tbl = document.getElementById('info').querySelector('table');

  /* generate workbook from the table */
  // highlight-start
  const wb = XLSX.utils.table_to_book(tbl);
  // highlight-end

  await saveFile(wb);
}
```

## Complete Example

:::note

This demo was tested in the following environments:

| OS and Version | Arch | Server    | Client    | Date       |
|:---------------|:-----|:----------|:----------|:-----------|
| macOS 13.5.1   | x64  | `v4.13.0` | `v3.11.0` | 2023-08-26 |
| macOS 13.4.1   | ARM  | `v4.10.0` | `v3.8.2`  | 2023-06-28 |
| Windows 10     | x64  | `v4.13.0` | `v3.11.0` | 2023-08-26 |
| Linux (HoloOS) | x64  | `v4.13.0` | `v3.11.0` | 2023-08-26 |

:::

The app core state will be the HTML table.  Reading files will add the table to
the window.  Writing files will parse the table into a spreadsheet.

1) Create a new NeutralinoJS app:

```bash
npx @neutralinojs/neu create sheetjs-neu
cd sheetjs-neu
```

2) Download [Standalone build](/docs/getting-started/installation/standalone)
and place in the `resources/js/` folder:

<CodeBlock language="bash">{`\
curl -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
</CodeBlock>

3) Add the highlighted line to `neutralino.config.json` in `nativeAllowList`:

```json title="neutralino.config.json"
  "nativeAllowList": [
    "app.*",
    "os.*",
// highlight-start
    "filesystem.*",
// highlight-end
    "debug.log"
  ],
```

:::note pass

There may be multiple `nativeAllowList` blocks in the configuration file. The
line must be added to the first block.

:::

4) Set up skeleton app and print version info:

- Replace the contents of `resources/index.html` with the following code:

```html title="resources/index.html"
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>SheetJS + NeutralinoJS</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <div id="neutralinoapp">
      <h1>SheetJS × NeutralinoJS</h1>
      <button onclick="importData()">Import Data</button>
      <button onclick="exportData()">Export Data</button>
      <div id="info"></div>
    </div>
    <script src="js/neutralino.js"></script>
    <!-- Load the browser build and make XLSX available to main.js -->
    <script src="js/xlsx.full.min.js"></script>
    <script src="js/main.js"></script>
  </body>
</html>
```

- Append the following code to `resources/styles.css` to center the table:

```css title="resources/styles.css"
#info {
    width:100%;
    text-align: unset;
}
table {
    margin: 0 auto;
}
```

- Print the version number in the `showInfo` method of `resources/js/main.js`:

```js title="resources/js/main.js"
function showInfo() {
    document.getElementById('info').innerHTML = `
        ${NL_APPID} is running on port ${NL_PORT}  inside ${NL_OS}
        <br/><br/>
        <span>server: v${NL_VERSION} . client: v${NL_CVERSION}</span>
// highlight-start
        <br/><br/>
        <span>SheetJS version ${XLSX.version}</span>
// highlight-end
        `;
}
```

5) Run the app:

```bash
npx @neutralinojs/neu run
```

<p>The app should print <code>SheetJS Version {current}</code></p>

6) Add the following code to the bottom of `resources/js/main.js`:

```js title="resources/js/main.js"
(async() => {
  const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
  const wb = XLSX.read(ab);
  const ws = wb.Sheets[wb.SheetNames[0]];
  document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws);
})();
```

Save the source file, close the app and re-run with `npx @neutralinojs/neu run`

When the app loads, a table should show in the main screen.

7) Add `importFile` and `exportFile` to the bottom of `resources/js/main.js`:

```js title="resources/js/main.js"
async function importData() {
  /* show open dialog */
  const [filename] = await Neutralino.os.showOpenDialog('Open a spreadsheet');

  /* read data */
  const ab = await Neutralino.filesystem.readBinaryFile(filename);
  const wb = XLSX.read(ab);

  /* make table */
  const ws = wb.Sheets[wb.SheetNames[0]];
  document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws);
}

async function exportData() {
  /* show save dialog */
  const filename = await Neutralino.os.showSaveDialog('Save to file');

  /* make workbook */
  const tbl = document.getElementById('info').querySelector("table");
  const wb = XLSX.utils.table_to_book(tbl);

  /* make file */
  const bookType = filename.slice(filename.lastIndexOf(".") + 1);
  const data = XLSX.write(wb, { bookType, type: "buffer" });
  await Neutralino.filesystem.writeBinaryFile(filename, data);
}
```

Save the source file, close the app and re-run with `npx @neutralinojs/neu run`

When the app loads, click the "Import File" button and select a spreadsheet to
see the contents.

Click "Export File" and enter `SheetJSNeu.xlsx` to write a new file.

:::caution pass

When saving the file, the actual file extension must be included. Attempting to
save as `SheetJSNeu` will not automatically add the `.xlsx` extension!

:::

8) Build production apps:

```bash
npx @neutralinojs/neu build
```

Platform-specific programs will be created in the `dist` folder.

[^1]: See [`nativeAllowList`](https://neutralino.js.org/docs/configuration/neutralino.config.json#nativeallowlist-string) in the NeutralinoJS documentation
[^2]: See [`os.showOpenDialog`](https://neutralino.js.org/docs/api/os#osshowopendialogtitle-options) in the NeutralinoJS documentation
[^3]: See [`filesystem.readBinaryFile`](https://neutralino.js.org/docs/api/filesystem/#filesystemreadbinaryfilefilename) in the NeutralinoJS documentation
[^4]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^5]: See ["Utility Functions"](/docs/api/utilities/)
[^6]: See ["HTML Table Output" in "Utility Functions"](/docs/api/utilities/html#html-table-output)
[^7]: See [`os.showSaveDialog`](https://neutralino.js.org/docs/api/os#osshowsavedialogtitle-options) in the NeutralinoJS documentation
[^8]: See [`write` in "Writing Files"](/docs/api/write-options)
[^9]: See ["Supported Output Formats"](/docs/api/write-options#supported-output-formats)
[^10]: See [`filesystem.writeBinaryFile`](https://neutralino.js.org/docs/api/filesystem/#filesystemwritebinaryfilefilename-data) in the NeutralinoJS documentation
[^11]: See ["HTML Table Input" in "Utility Functions"](/docs/api/utilities/html#html-table-input)