---
title: Spreadsheet-Powered Wails Apps
sidebar_label: Wails
description: Build data-intensive desktop apps using Wails. Seamlessly integrate spreadsheets into your app using SheetJS. Modernize Excel-powered business processes with confidence.
pagination_prev: demos/mobile/index
pagination_next: demos/data/index
sidebar_position: 3
sidebar_custom_props:
  summary: Webview + Go Backend
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[Wails](https://wails.io/) is a modern toolkit for building desktop apps. Wails
apps pair a Go-powered backend with a JavaScript-powered frontend[^1].
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Wails and SheetJS to pull data from a spreadsheet and display the
data in the app. We'll explore how to load SheetJS in a Wails app and exchange
file data between the JavaScript frontend and Go backend.
The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
  | Windows | macOS | Linux | 
|  |  |  | 
:::tip pass
This demo assumes familiarity with the Go programming language.
For a pure JavaScript solution, the [Electron](/docs/demos/desktop/electron)
platform provides many native features out of the box.
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
installed in the `frontend` folder and imported in frontend scripts.
:::caution pass
Wails currently does not provide the equivalent of NodeJS `fs` module.
**Reading and writing raw file data must be implemented in native Go code.**
:::
This demo includes native Go code for showing dialogs and reading and writing
files. When sending data between Go and JavaScript code, the raw files are
encoded as Base64 strings.
### Reading Files
When the user clicks the "Import File" button, the frontend tells the Go backend
to read data. The user will be presented with a file picker to select a file to
read. The Go backend will read the data, encode as a Base64 string, and send the
result to the frontend.
The frontend will parse the data using the SheetJS `read` method[^2], generate
HTML tables with `sheet_to_html`[^3], and display the tables on the frontend.
The following diagram summarizes the steps:
```mermaid
sequenceDiagram
  actor User
  participant JS as Frontend (JS)
  participant Go as Backend (Go)
  User->>JS: click button
  JS->>Go: ask for data
  Note over Go: Show Open Dialog
  Note over Go: Read File Bytes
  Note over Go: Generate Base64
  Go->>JS: return data
  Note over JS: Parse Data
`read`
  Note over JS: Display Table
`sheet_to_html`
  JS->>User: app shows data
```
#### Go
The Wails runtime provides the cross-platform `OpenFileDialog` function[^4] to
show a file picker. The Go standard library provides methods for reading data
from the selected file[^5] and encoding in a Base64 string[^6]
```go
import (
  "context"
// highlight-start
  "encoding/base64"
  "os"
  "github.com/wailsapp/wails/v2/pkg/runtime"
// highlight-end
)
type App struct {
  ctx context.Context
}
// ReadFile shows an open file dialog and returns the data as Base64 string
func (a *App) ReadFile() string {
  // highlight-next-line
  selection, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
    Title: "Select File",
    Filters: []runtime.FileFilter{
      { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", },
      // ... more filters for more file types
    },
  })
  if err != nil { return "" } // The demo app shows an error message
  // highlight-next-line
  data, err := os.ReadFile(selection)
  if err != nil { return "" } // The demo app shows an error message
  // highlight-next-line
  return base64.StdEncoding.EncodeToString(data)
}
```
#### JS
Wails will automatically create bindings for use in JS. The `App` binding module
will export the function `ReadFile`.
The following example uses the [SvelteJS](/docs/demos/frontend/svelte) framework:
```js title="frontend/src/App.svelte"
import { read, utils } from 'xlsx';
import { ReadFile } from '../wailsjs/go/main/App';
async function importFile(evt) {
// highlight-start
  /* call the native Go function and receive a base64 string */
  const b64 = await ReadFile();
  /* parse the base64 string with SheetJS */
  const wb = read(b64, { type: "base64" });
// highlight-end
  const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
  return utils.sheet_to_html(ws); // generate HTML table
}
```
### Writing Files
:::info pass
The SheetJS `write` method[^7] can write spreadsheets in a number of formats[^8]
including XLSX, XLSB, XLS, and NUMBERS. It expects a `bookType` option. This
means the frontend needs to know the output file name before creating the file.
:::
When the user clicks the "Export File" button, the frontend asks the Go backend
for the output filename and path. The user will be presented with a file picker
to select the output folder and workbook type. The backend will send the name
to the frontend.
The frontend will generate a workbook object from the table using the SheetJS
`table_to_book` method[^9]. The SheetJS `write` method[^10] will generate a
Base64 string from the data.
The frontend will send the Base64 string to the backend. The backend will write
the data to a file in the selected folder.
```mermaid
sequenceDiagram
  actor User
  participant JS as Frontend (JS)
  participant Go as Backend (Go)
  User->>JS: click button
  JS->>Go: ask for path
  Note over Go: Show Save Dialog
  Go->>JS: path to save file
  Note over JS: Read from Table
`table_to_book`
  Note over JS: Write Workbook
`write`
  JS->>Go: base64-encoded bytes
  Note over Go: Decode Data
  Note over Go: Write to File
  Go->>JS: write finished
  JS->>User: alert
```
##### Go
Two Go functions will be exposed.
- `SaveFile` will show the file picker and return the path. It will use the
  cross-platform `SaveFileDialog` function[^11].
```go
import (
  "context"
// highlight-next-line
  "github.com/wailsapp/wails/v2/pkg/runtime"
)
type App struct {
  ctx context.Context
}
func (a *App) SaveFile() string {
// highlight-next-line
  selection, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
    Title: "Select File",
    DefaultFilename: "SheetJSWails.xlsx",
    Filters: []runtime.FileFilter{
      { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", },
      // ... more filters for more file types
    },
  })
  if err != nil { return "" } // The demo app shows an error message
  return selection
}
```
- `WriteFile` performs the file write given a Base64 string and file path. The
  Go standard library provides methods for decoding Base64 strings[^12] and
  writing data to the filesystem[^13]
```go
import (
  "context"
// highlight-start
  "encoding/base64"
  "os"
// highlight-end
)
type App struct {
  ctx context.Context
}
func (a *App) WriteFile(b64 string, path string) {
  // highlight-start
  buf, _ := base64.StdEncoding.DecodeString(b64);
  _ = os.WriteFile(path, buf, 0644);
  // highlight-end
}
```
#### JS
Wails will automatically create bindings for use in JS. The `App` binding module
will export the functions `SaveFile` and `WriteFile`.
The following example uses the [SvelteJS](/docs/demos/frontend/svelte) framework:
```js title="frontend/src/App.svelte"
import { utils, write } from 'xlsx';
import { SaveFile, WriteFile } from '../wailsjs/go/main/App';
async function exportFile(table_element) {
  /* generate workbook */
  const wb = utils.table_to_book(table_element);
  /* show save picker and get path */
  const path = await SaveFile();
  /* get the file extension -> bookType */
  const bookType = path.slice(path.lastIndexOf(".")+1);
  /* generate base64 string */
  const b64 = write(wb, { bookType: bookType, type: "base64" });
  /* write to file */
  await WriteFile(b64, path);
}
```
## Complete Example
:::note
This demo was tested in the following environments:
| OS and Version | Arch | Wails    | Date       |
|:---------------|:-----|:---------|:-----------|
| macOS 12.6.3   | x64  | `v2.5.1` | 2023-08-24 |
| macOS 13.5.1   | ARM  | `v2.5.1` | 2023-08-24 |
| Windows 10     | x64  | `v2.5.1` | 2023-08-25 |
| Windows 11     | ARM  | `v2.6.0` | 2023-09-25 |
| Linux (HoloOS) | x64  | `v2.6.0` | 2023-10-11 |
| Linux (Debian) | ARM  | `v2.6.0` | 2023-09-25 |
:::
0) Read the Wails "Getting Started" guide[^14] and install dependencies.
Installation Notes (click to show)
Wails will require:
- A recent version of [Go](https://go.dev/doc/install).
- The "LTS" version of [NodeJS](https://nodejs.org/en/download).
After installing both, run the following command to install Wails:
```bash
go install github.com/wailsapp/wails/v2/cmd/wails@latest
```
Once that finishes, run the following command in a new terminal window:
```bash
wails doctor
```
The output will include a `# Diagnosis` section. It should display:
```
# Diagnosis
Your system is ready for Wails development!
```
If a required dependency is missing, it will be displayed.
:::note pass
None of the optional packages are required for building and running this demo.
:::
:::info pass
On the Steam Deck (HoloOS), some dependencies must be reinstalled:
```bash
sudo pacman -Syu base-devel gtk3 glib2 pango harfbuzz cairo gdk-pixbuf2 atk libsoup
```
:::