> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/advanced/offline-loading.md).

# Viewing documents offline with WebViewer

View documents offline with WebViewer by caching resources using service workers and storing documents with localforage. Learn how to load and view files offline on web. The Apryse Web SDK streamlines

There are two parts to loading WebViewer in an offline scenario: loading the web resources (e.g. JS, HTML, CSS, Web Workers, etc) and loading the actual document in the viewer. Loading the resources can be accomplished using [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/), [AppCache](https://developer.mozilla.org/en-US/docs/Web/API/Window/applicationCache/) or using local resources embedded in a native app. Then using [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/) makes it straightforward to save and load the actual document.

In this guide, we will be using the service worker to cache and serve WebViewer files and [localforage](https://github.com/localForage/localForage/) to simplify storing and retrieving documents in web storage. This guide assumes you have some basic knowledge about service workers, so you can read [this guide](https://developers.google.com/web/fundamentals/primers/service-workers/) for an overview.

To see a complete project with the code in this guide, visit [this repo](https://github.com/ApryseSDK/webviewer-offline-sample/).

## Register a service worker

There isn't anything special about registering a service worker for WebViewer. You can follow the exact steps to [register a service worker](https://developers.google.com/web/fundamentals/primers/service-workers/#register_a_service_worker).

## Cache files

After the service worker has been registered, it's time to cache WebViewer files in the service worker. Depending on the type of the documents you are going to load, not every file needs to be cached. In the root folder of the [sample project](https://github.com/ApryseSDK/webviewer-offline-sample/), You can run the following command to generate the list of files to be cached for WebViewer:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// in the root folder
node prepare-serviceworker-list.js
```

{% endcode %}
{% endtab %}
{% endtabs %}

This will generate a list of files in a json file in the `src` folder, which will be used by the service worker. Optionally, you can modify the list of files in WebViewer to be cached by the service worker according to your need.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
// In your service worker file
const CACHE_NAME = 'YOUR_CACHE_NAME';
// This file is cached only because we are using this library in this guide
const localforage = 'path/to/localforage.js';

var assets = [
  '/',
  '/style.css',
  '/index.js',
  '/index.html',
  `/scripts/pwacompat.min.js`,
  `/manifest.json`,
  '/images/ic_launcher-48.png',
  '/images/ic_launcher-72.png',
  '/images/ic_launcher-96.png',
  '/images/ic_launcher-144.png',
  '/images/ic_launcher-192.png',
  '/images/ic_launcher-512.png',
  'https://fonts.googleapis.com/css?family=Roboto',
  'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600',
  'https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2',
  '/public/lib/webviewer.min.js',
];

async function getWorkersList() {
  try {
    const response = await fetch('/service-worker-list.json');
    const jsonData = await response.json();

    return jsonData;
  } catch(err) {
    console.error('Error fetching workers list');
  }
}

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(async function(cache) {
        const { core, ui } = await getWorkersList();
        return cache.addAll([localforage].concat(
          core,
          ui,
          assets,
        ));
      }),
  );
});
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
// In your service worker file
const CACHE_NAME = 'YOUR_CACHE_NAME';
// This file is cached only because we are using this library in this guide
const localforage = 'path/to/localforage.js';

// The following files are required to load WebViewer with the default UI
const externalFiles = [
  'path/to/lib/core/external/decode.min.js',
  'path/to/lib/core/external/rawinflate.js',
  'path/to/lib/core/external/pako_inflate.min.js',
  'path/to/lib/core/external/jquery-3.2.1.min.js',
  'path/to/lib/core/external/html2canvas.min.js',
  'path/to/lib/core/external/Promise.js'
];
const uiFiles = [
  'path/to/lib/ui/build/index.html',
  'path/to/lib/ui/build/style.css',
  'path/to/lib/ui/build/webviewer-ui.min.js',
  'path/to/lib/ui/build/i18n/translation-en.json'
];
const webViewerFiles = [
  'path/to/lib/core/CoreControls.js',
  'path/to/lib/webviewer.min.js',
  'path/to/lib/core/CoreWorker.js'
];

// The following files are optional

// If you want to load a PDF file
const PDFWorkerFiles = [
  'path/to/lib/core/pdf/pdfnet.res',
  'path/to/lib/core/pdf/PDFworker.js',
  'path/to/lib/core/pdf/lean/PDFNetC.gz.js.mem',
  'path/to/lib/core/pdf/lean/PDFNetC.gz.mem',
  'path/to/lib/core/pdf/lean/PDFNetCWasm.br.js.mem',
  'path/to/lib/core/pdf/lean/PDFNetCWasm.br.wasm',
  'path/to/lib/core/pdf/lean/optimized/...' // all files in the optimized folders
];
// If you want to load an Office file
const OfficeWorkerFiles = [
  'path/to/lib/core/office/OfficeWorker.js',
  'path/to/lib/core/office/WebOfficeWorker.gz.js.mem',
  'path/to/lib/core/office/WebOfficeWorker.gz.mem',
  'path/to/lib/core/office/WebOfficeWorkerWasm.br.js.mem',
  'path/to/lib/core/office/WebOfficeWorkerWasm.br.wasm',
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        return cache.addAll([
          localforage,
          ...externalFiles,
          ...uiFiles,
          ...webViewerFiles,
          ...PDFWorkerFiles,
          ...OfficeWorkerFiles
        ]);
      })
  );
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Return cached responses

WebViewer will append a query string when requesting the worker files depending on if you are using [the full API](/web/what-is-webviewer/deployment-options.md#full-pdftron-api). In order for the service worker to return the correct cached files we need to set the [ignoreSearch](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match/).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request, { ignoreSearch: true })
      .then(response => {
        if (response) {
          return response;
        }
        return fetch(event.request);
      })
  );
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Store documents

Fetching the document and storing it as a blob are easy using the [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch/) API and [localforage](https://github.com/localForage/localForage/).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const store = localforage.createInstance({
  name: 'store'
});
const filePath = 'path/to/your/file';
const fileName = 'fileName';

fetch(filePath)
  .then(response => response.blob())
  .then(blob => {
    store.setItem(fileName, blob);
  })
  .catch(error => {
    console.log(error);
  });
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Load documents

The [loadDocument](https://sdk.apryse.com/api/web/UI.html#.loadDocument) API supports [loading a blob](/web/open-save-document/open/blob.md) so all we need to do is to get the blob from the store and call the API with it.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
store
  .getItem(fileName)
  .then((blob) => {
    viewerInstance.UI.loadDocument(blob, {
      filename: fileName
    });
  });
```

{% endcode %}
{% endtab %}
{% endtabs %}

To see a complete project with the code in this guide, visit [this repo](https://github.com/ApryseSDK/webviewer-offline-sample/).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/advanced/offline-loading.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
