> ## Documentation Index
> Fetch the complete documentation index at: https://setup.despia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# File Viewer

> Open a remote file in a native previewer with consistent render fidelity across iOS and Android.

`fileviewer://` opens any remote file in a native previewer without forcing the user out of your app. iOS renders the file through QuickLook; Android renders it through Despia's in-app file viewer, built to match QuickLook's fidelity so output stays consistent regardless of what apps the user has installed.

Useful for invoices, statements, contracts, exported reports, generated PDFs, and any other case where you would otherwise navigate the user to a hosted file URL and lose your app's context.

***

## Installation

<Tabs>
  <Tab title="Bundle">
    <CodeGroup>
      ```bash npm theme={null}
      npm install despia-native
      ```

      ```bash pnpm theme={null}
      pnpm add despia-native
      ```

      ```bash yarn theme={null}
      yarn add despia-native
      ```
    </CodeGroup>

    ```javascript theme={null}
    import despia from 'despia-native';
    ```
  </Tab>

  <Tab title="CDN">
    <CodeGroup>
      ```html UMD theme={null}
      <script src="https://cdn.jsdelivr.net/npm/despia-native/index.min.js"></script>
      ```

      ```html ESM theme={null}
      <script type="module">
          import despia from 'https://cdn.jsdelivr.net/npm/despia-native/+esm'
      </script>
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## How it works

Pass an absolute HTTPS URL as the `src` parameter. The native side downloads the file off the main thread, resolves its type from the URL extension (falling back to the response `Content-Type`), and hands it to the previewer. The call is fire-and-forget; there is no JS callback.

```javascript theme={null}
import despia from 'despia-native'

const isDespia = navigator.userAgent.toLowerCase().includes('despia')
const src = encodeURIComponent('https://example.com/report.pdf')

if (isDespia) {
    despia(`fileviewer://?src=${src}`)
}
```

Always pass `src` through `encodeURIComponent` so the scheme separator, slashes, and any query characters survive transit.

| Parameter | Required | Description                                                                                                        |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `src`     | Yes      | Absolute HTTPS URL of the file. Must be percent-encoded.                                                           |
| `theme`   | No       | `light` or `dark`. Overrides the system appearance for the previewer UI. Omit to follow the device's system theme. |

The previewer renders PDFs, images (PNG, JPG, HEIC), plain text, RTF, CSV, and Office documents (DOCX, XLSX, PPTX).

***

## Matching the previewer to your app's theme

Pass `theme=light` or `theme=dark` when your app overrides the OS appearance and you want the previewer to follow your app's setting instead of the device default. Omit `theme` entirely to fall back to the system appearance.

```javascript theme={null}
const src = encodeURIComponent('https://example.com/report.pdf')

if (isDespia) {
    despia(`fileviewer://?src=${src}&theme=dark`)
}
```

`theme` is a plain string and does not need encoding; only `src` needs `encodeURIComponent`.

***

## Authenticating the file URL

The native fetch is a plain HTTPS request made from outside your web session, so it does not carry the user's cookies. Any file behind authentication has to be reachable through the URL itself. The standard pattern is a short-lived signed URL generated by your backend.

```javascript theme={null}
const res = await fetch('/api/invoices/123/signed-url')
const { url } = await res.json()

if (isDespia) {
    despia(`fileviewer://?src=${encodeURIComponent(url)}`)
}
```

Most object stores generate these natively: S3 presigned URLs, Cloudflare R2 signed URLs, Google Cloud Storage signed URLs, Azure Blob SAS tokens. Issue them with the shortest expiry that fits your flow, typically a few minutes, so a leaked URL stops working quickly.

***

## Generated or canvas-exported files

<Warning>
  `src` must be a publicly reachable HTTPS URL. `data:`, `blob:`, and `file://` URLs are rejected, since the native side performs a real network fetch. Upload generated content to a CDN or signed endpoint first and pass the resulting URL.
</Warning>

If your app produces a file on the client, a canvas export, a generated PDF, a CSV built in memory, upload it first and then pass the returned URL to the scheme.

```javascript theme={null}
const form = new FormData()
form.append('file', blob, 'report.pdf')

const res = await fetch('/api/uploads', { method: 'POST', body: form })
const { url } = await res.json()

if (isDespia) {
    despia(`fileviewer://?src=${encodeURIComponent(url)}`)
}
```

If the upload endpoint returns a signed URL, the file is also protected after the upload — the same pattern from the previous section applies.

***

## Resources

<CardGroup cols={2}>
  <Card title="NPM Package" icon="npm" href="https://www.npmjs.com/package/despia-native">
    despia-native
  </Card>

  <Card title="Support" icon="envelope" href="mailto:support@despia.com">
    [support@despia.com](mailto:support@despia.com)
  </Card>
</CardGroup>
