---
title: CapacitorJS
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
sidebar_position: 5
sidebar_custom_props:
  summary: JS + Web View
---

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

The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.

The "Complete Example" creates an app that looks like the screenshots below:

<table><thead><tr>
  <th><a href="#demo">iOS</a></th>
  <th><a href="#demo">Android</a></th>
</tr></thead><tbody><tr><td>

![iOS screenshot](pathname:///cap/ios.png)

</td><td>

![Android screenshot](pathname:///cap/and.png)

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

:::note Tested Deployments

This demo was tested in the following environments:

| OS         | Type | Device              | CapacitorJS + FS  | Date       |
|:-----------|:-----|:--------------------|:------------------|:-----------|
| Android 34 | Sim  | Pixel 3a            | `5.5.1` / `5.1.4` | 2023-12-04 |
| iOS 17.0.1 | Sim  | iPhone 15 Pro Max   | `5.5.1` / `5.1.4` | 2023-12-04 |
| Android 29 | Real | NVIDIA Shield       | `5.5.1` / `5.1.4` | 2023-12-04 |
| iOS 15.1   | Real | iPad Pro            | `5.5.1` / `5.1.4` | 2023-12-04 |

:::

:::danger Telemetry

Before starting this demo, manually disable telemetry.  On Linux and MacOS:

```bash
npx @capacitor/cli telemetry off
```

To verify telemetry was disabled:

```bash
npx @capacitor/cli telemetry
```

:::

## Integration Details

This example uses Svelte, but the same principles apply to other frameworks.

#### Reading data

The standard HTML5 File Input element logic works in CapacitorJS:

```html
<script>
import { read, utils } from 'xlsx';

let html = "";

/* show file picker, read file, load table */
async function importFile(evt) {
  // highlight-start
  const f = evt.target.files[0];
  const wb = read(await f.arrayBuffer());
  // highlight-end
  const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
  html = utils.sheet_to_html(ws); // generate HTML and update state
}
</script>

<main>
  <!-- highlight-next-line -->
  <input type="file" on:change={importFile}/>
  <div bind:this={tbl}>{@html html}</div>
</main>
```

#### Writing data

`@capacitor/filesystem` can write Base64 strings:

```html
<script>
import { Filesystem, Directory } from '@capacitor/filesystem';
import { utils, writeXLSX } from 'xlsx';

let html = "";
let tbl;

/* get state data and export to XLSX */
async function exportFile() {
  const elt = tbl.getElementsByTagName("TABLE")[0];
  const wb = utils.table_to_book(elt);
  /* generate Base64 string for Capacitor */
  // highlight-start
  const data = writeXLSX(wb, { type: "base64" });
  await Filesystem.writeFile({
    data,
    path: "SheetJSCap.xlsx",
    directory: Directory.Documents
  }); // write file
  // highlight-end
}

</script>

<main>
  <button on:click={exportFile}>Export XLSX</button>
  <div bind:this={tbl}>{@html html}</div>
</main>
```

## Demo

### Base Project

0) Follow the official "Environment Setup"[^1] instructions to set up Android
and iOS targets

:::caution pass

iOS development is only supported on macOS.

:::

<details>
  <summary><b>Installation Notes</b> (click to show)</summary>

CapacitorJS requires Java 17.

</details>

1) Disable telemetry.

```bash
npx @capacitor/cli telemetry off
```

Verify that telemetry is disabled by running

```bash
npx @capacitor/cli telemetry
```

(it should print `Telemetry is off`)

2) Create a new Svelte project:

```bash
npm create vite@latest sheetjs-cap -- --template svelte
cd sheetjs-cap
```

3) Install dependencies:

<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i --save @capacitor/core @capacitor/cli @capacitor/filesystem`}
</CodeBlock>

4) Create CapacitorJS structure:

```bash
npx cap init sheetjs-cap com.sheetjs.cap --web-dir=dist
npm run build
```

:::note pass

If prompted to create an Ionic account, type `N` and press Enter.

:::

5) Download [`src/App.svelte`](pathname:///cap/App.svelte) and replace:

```bash
curl -o src/App.svelte -L https://docs.sheetjs.com/cap/App.svelte
```

### Android

6) Create Android app

```bash
npm i --save @capacitor/android
npx cap add android
```

7) Enable file reading and writing in the Android app.

Add the highlighted lines to `android/app/src/main/AndroidManifest.xml` after
the `Permissions` comment:

```xml title="android/app/src/main/AndroidManifest.xml (add to file)"
    <!-- Permissions -->

<!-- highlight-start -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- highlight-end -->

    <uses-permission android:name="android.permission.INTERNET" />
```

8) Run the app in the simulator

```bash
npm run build
npx cap sync
npx cap run android
```

9) Test the app

Open the app and observe that presidents are listed in the table.

Touch "Export XLSX" and the emulator will ask for permission:

Tap "Allow" and a popup will be displayed with a path.

To see the generated file, switch to the "Files" app in the simulator, tap the
`≡` icon and tap "Documents". Tap "Documents" folder to find `SheetJSCap.xlsx`.

### iOS

10) Create iOS app

```bash
npm i --save @capacitor/ios
npx cap add ios
```

11) Enable file sharing and make the documents folder visible in the iOS app.
The following lines must be added to `ios/App/App/Info.plist`:

```xml title="ios/App/App/Info.plist (add to file)"
<plist version="1.0">
<dict>
<!-- highlight-start -->
  <key>UIFileSharingEnabled</key>
  <true/>
  <key>LSSupportsOpeningDocumentsInPlace</key>
  <true/>
<!-- highlight-end -->
  <key>CFBundleDevelopmentRegion</key>
```

(The root element of the document is `plist` and it contains one `dict` child)

12) Run the app in the simulator

```bash
npm run build
npx cap sync
npx cap run ios
```

If prompted to select a target device, select "iPhone 15 Pro Max (simulator)".

13) Test the app

Open the app and observe that presidents are listed in the table.

Touch "Export XLSX" and a popup will be displayed.

To see the generated file, switch to the "Files" app in the simulator and look
for `SheetJSCap.xlsx` in "On My iPhone" > "`sheetjs-cap`"

### Android Device

14) Connect an Android device using a USB cable.

If the device asks to allow USB debugging, tap "Allow".

15) Close any Android / iOS emulators.

16) Build APK and run on device:

```bash
npm run build
npx cap sync
npx cap run android
```

If the Android emulators are closed and an Android device is connected, the last
command will build an APK and install on the device.

:::caution pass

For real devices running API level 29 or below, the following line must be added
to the `application` open tag in `android/app/src/main/AndroidManifest.xml`:

```xml title="android/app/src/main/AndroidManifest.xml (add highlighted attribute)"
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        // highlight-next-line
        android:requestLegacyExternalStorage="true"
        android:theme="@style/AppTheme">
```

:::

### iOS Device

17) Connect an iOS device using a USB cable

18) Close any Android / iOS emulators.

19) Enable developer code signing certificates[^2]

19) Run on device:

```bash
npm run build
npx cap sync
npx cap run ios
```

When prompted to select a target device, select the real device in the list.

[^1]: See ["Environment Setup"](https://capacitorjs.com/docs/getting-started/environment-setup) in the CapacitorJS documentation.
[^2]: The [Flutter documentation](https://docs.flutter.dev/get-started/install/macos?tab=ios15#enable-developer-code-signing-certificates) covers the instructions in more detail. The correct workspace is `ios/App/App.xcworkspace`