> 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/config-files.md).

# Config files

Enhance WebViewer functionality with config files. Learn how to leverage JavaScript files executed within iframes for easy access to core objects and advanced customizations. The Apryse Web SDK stream

[WebViewer fundamentals](/web/get-started/guides/learn-more-overview.md) explains how WebViewer creates the UI app inside an iframe, so that [Core namespaces and classes](/web/ui-customization/core.md) are encapsulated. The iframe window and document object are still accessible through [contentWindow](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentWindow/) and [contentDocument](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentDocument/), but it can still be tricky to run scripts or listen to events happening inside the iframe.

WebViewer provides an option referred to as a "config file". It's just an ordinary JavaScript file, but it will be executed in the context of the iframe. This gives you easy access to the [Document](https://sdk.apryse.com/api/web/Core.Document.html), [DocumentViewer](https://sdk.apryse.com/api/web/Core.DocumentViewer.html) and [AnnotationManager](https://sdk.apryse.com/api/web/Core.AnnotationManager.html) objects (among others) which can allow you to make more complicated customizations.

To instantiate WebViewer with a config file you just need to set the `config` option in the WebViewer constructor. For example:

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

```js
WebViewer({
  initialDoc: "mydoc.pdf",
  config: "path/to/my/config/file.js" // relative to your HTML file
}, viewerElement);
```

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

## Useful events

The config file is executed even before the core objects are instantiated, so you won't be able to call core functions immediately. You can listen for events on the HTML document object that will notify you at key points.

The first important one is the `viewerLoaded` event. viewerLoaded will be fired after app has been rendered, and at this point you'll be able to access the `documentViewer` variable before the document has loaded. For example:

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

```js
instance.UI.addEventListener(instance.UI.Events.VIEWER_LOADED, () => {
  documentViewer.setMargin(20);
  documentViewer.addEventListener('fitModeUpdated', fitMode => {
    console.log('fit mode changed');
  });
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('documentLoaded', () => {
  const doc = docViewer.getDocument();
  doc.loadThumbnailAsync(1, thumb => {
    const myThumbnailDiv = document.getElementById('myThumbnailDiv');
    myThumbnailDiv.appendChild(thumb)
  });
  const annotManager = docViewer.getAnnotationManager();
  const rectangle = new Annotations.RectangleAnnotation();
  rectangle.PageNumber = 2;
  rectangle.X = 100;
  rectangle.Y = 100;
  rectangle.Width = 250;
  rectangle.Height = 250;
  rectangle.Author = annotManager.getCurrentUser();
  annotManager.addAnnotation(rectangle);
  docViewer.displayLastPage();
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('viewerLoaded', () => {
  docViewer.setMargin(20);
  docViewer.on('fitModeUpdated', fitMode => {
    console.log('fit mode changed');
  });
});
```

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

Another important event is [`documentLoaded`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:documentLoaded__anchor). Once documentLoaded has fired you can access Document as well as functions related to the page number on ReaderControl, DocumentViewer and AnnotationManager. For example:

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

```js
instance.UI.addEventListener(instance.UI.Events.DOCUMENT_LOADED, () => {
  const doc = documentViewer.getDocument();
  doc.loadThumbnailAsync(1, thumb => {
    const myThumbnailDiv = document.getElementById('myThumbnailDiv');
    myThumbnailDiv.appendChild(thumb)
  });
  const annotManager = documentViewer.getAnnotationManager();
  const rectangle = new Annotations.RectangleAnnotation();
  rectangle.PageNumber = 2;
  rectangle.X = 100;
  rectangle.Y = 100;
  rectangle.Width = 250;
  rectangle.Height = 250;
  rectangle.Author = annotManager.getCurrentUser();
  annotManager.addAnnotation(rectangle);
  documentViewer.displayLastPage();
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('documentLoaded', () => {
  const doc = docViewer.getDocument();
  doc.loadThumbnailAsync(1, thumb => {
    const myThumbnailDiv = document.getElementById('myThumbnailDiv');
    myThumbnailDiv.appendChild(thumb)
  });
  const annotManager = docViewer.getAnnotationManager();
  const rectangle = new Annotations.RectangleAnnotation();
  rectangle.PageNumber = 2;
  rectangle.X = 100;
  rectangle.Y = 100;
  rectangle.Width = 250;
  rectangle.Height = 250;
  rectangle.Author = annotManager.getCurrentUser();
  annotManager.addAnnotation(rectangle);
  docViewer.displayLastPage();
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('documentLoaded', () => {
  const doc = docViewer.getDocument();
  doc.loadThumbnailAsync(0, thumb => {
    const myThumbnailDiv = document.getElementById('myThumbnailDiv');
    myThumbnailDiv.appendChild(thumb)
  });
  const annotManager = docViewer.getAnnotationManager();
  const rectangle = new Annotations.RectangleAnnotation();
  rectangle.PageNumber = 2;
  rectangle.X = 100;
  rectangle.Y = 100;
  rectangle.Width = 250;
  rectangle.Height = 250;
  rectangle.Author = annotManager.getCurrentUser();
  annotManager.addAnnotation(rectangle);
  docViewer.displayLastPage();
});
```

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

## Passing custom data

Sometimes you might want to send custom data from the "outer" page (with the PDFTron.WebViewer constructor) to the "inner" page (your config file). To do this you can use the `custom` option in the WebViewer constructor. The property expects a string value. So for example to pass an object:

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

```js
const myObj = {
  startPage: 10
};

WebViewer({
  custom: JSON.stringify(myObj)
}, viewerElement);
```

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

Then inside the config file you access this data as follows:

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

```js
let custom;
instance.UI.addEventListener(instance.UI.Events.VIEWER_LOADED, () => {
  const custom = JSON.parse(instance.UI.getCustomData());
  console.log(custom.startPage); // outputs 10
});
instance.Core.documentViewer.addEventListener(instance.Core.DocumentViewer.Events.DOCUMENT_LOADED, () => {
  const documentViewer = instance.Core.documentViewer;
  documentViewer.setCurrentPage(custom.startPage);
});
```

{% endcode %}
{% endtab %}

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

```js
let custom;
window.addEventListener('viewerLoaded', () => {
  custom = JSON.parse(readerControl.getCustomData());
  console.log(custom.startPage); // outputs 10
});
window.addEventListener('documentLoaded', () => {
  const docViewer = readerControl.docViewer;
  docViewer.setCurrentPage(custom.startPage);
});
```

{% endcode %}
{% endtab %}

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

```js
let custom;
window.addEventListener('viewerLoaded', () => {
  custom = JSON.parse(readerControl.getCustomData());
  console.log(custom.startPage); // outputs 10
});
window.addEventListener('documentLoaded', () => {
  const docViewer = readerControl.docViewer;
  docViewer.setCurrentPage(custom.startPage);
});
```

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

## Using a config file when the path is on another domain

If you have the WebViewer path on a different domain than your app, then to protect against XSS attacks you will need to edit the `lib/ui/configorigin.txt` file to whitelist your app's domain(s). A wildcard character can also be used to match part of a domain. Add each domain on a separate line, for example:

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

```js
http://localhost:3000
https://example.com
https://*.pdftron.com
```

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

Domains in this list will be allowed to have WebViewer specify config files that can be loaded.

## Accessing outer page inside the iframe

If you want to access the outer page from inside the iframe, for example from code in your config file, you can access the parent window using `window.parent`. So if you defined an API that's loaded on your HTML page, you could access it from inside the iframe like `window.parent.myApi.myFunction()`.

## Accessing WebViewer instance from the config file

If you want to access the WebViewer instance from the config file, depending on the version you can use `instance` in place of the instance argument normally returned when instantiating WebViewer.

When using WebViewer with WebComponent, get the instance by using:

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

```js
const instance  = WebViewer.getInstance()
```

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

[WebComponent vs Iframe](/web/ui-customization/web-component-vs-iframe.md)

Here is an example of the [Viewing](/web/get-started/samples/viewing.md) sample converted to a config file:

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

```js
instance.UI.addEventListener(instance.UI.Events.VIEWER_LOADED, function() {
  window.parent.document.getElementById('select').onchange = function(e) {
    instance.UI.loadDocument(e.target.value);
  };
  window.parent.document.getElementById('file-picker').onchange = function(e) {
    var file = e.target.files[0];
    if (file) {
      instance.UI.loadDocument(file);
    }
  };
  window.parent.document.getElementById('url-form').onsubmit = function(e) {
    e.preventDefault();
    instance.UI.loadDocument(window.parent.document.getElementById('url').value);
  };
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('viewerLoaded', function() {
  window.parent.document.getElementById('select').onchange = function(e) {
    readerControl.loadDocument(e.target.value);
  };
  window.parent.document.getElementById('file-picker').onchange = function(e) {
    var file = e.target.files[0];
    if (file) {
      readerControl.loadDocument(file);
    }
  };
  window.parent.document.getElementById('url-form').onsubmit = function(e) {
    e.preventDefault();
    readerControl.loadDocument(window.parent.document.getElementById('url').value);
  };
});
```

{% endcode %}
{% endtab %}

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

```js
window.addEventListener('viewerLoaded', function() {
  window.parent.document.getElementById('select').onchange = function(e) {
    readerControl.loadDocument(e.target.value);
  };
  window.parent.document.getElementById('file-picker').onchange = function(e) {
    var file = e.target.files[0];
    if (file) {
      readerControl.loadDocument(file);
    }
  };
  window.parent.document.getElementById('url-form').onsubmit = function(e) {
    e.preventDefault();
    readerControl.loadDocument(window.parent.document.getElementById('url').value);
  };
});
```

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


---

# 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/config-files.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.
