> 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/ui-customization/modular-ui/ui-import-and-export.md).

# UI Import and Export

Easily build a WebViewer Modular UI using a configuration JSON object. Create a UI with Components, Headers, Panels, Popups, and Flyouts in one place which can be imported and exported to make customi

With the WebViewer Modular UI, it is possible to import and export an entire UI configuration using JSON.

This provides a simple and efficient way to customize the Webviewer UI whether starting from the out-of-the-box UI or assembling something new using modular components. A configuration can be saved as a separate file or worked on as an object to make customization and editing easier with all the different components available in one place.

A configuration can contain:

* Modular Components (ie., [Items](/web/ui-customization/modular-ui/items.md), [Grouped Items](/web/ui-customization/modular-ui/containers.md#grouped-items), and [Ribbon Groups](/web/ui-customization/modular-ui/containers.md#ribbon-group))
* [Modular Headers](/web/ui-customization/modular-ui/containers.md#modular-header)
* [Flyouts](/web/ui-customization/modular-ui/flyouts.md)
* [Panels](/web/ui-customization/modular-ui/panels.md)
* [Popups](/web/ui-customization/customizing-popup.md)

## Import a Modular UI

To use a Modular UI configuration, it needs to be imported into WebViewer. The configuration can be imported using the `UI.importModularComponents` method. The method takes a configuration JSON object as a parameter along with an optional function map.

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

```js
instance.UI.importModularComponents(configUI);
```

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

[UI.importModularComponents](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor)

The import method will validate the configuration to ensure that it is correctly formatted and that all Modular Components, Modular Headers, Flyouts, Panels, and Popups have their necessary properties. If any errors are found, an error message will display in the console and the import operation will be aborted.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-05c5bbbae0ba9ee9c27430e336644c1c6ecc52bb%2F33be077293ebe1f7865419b2aa2478b8a0060397-992x508.png?alt=media)

In the example above, there is an error thrown which shows that a component `menu-toggle-button` has an invalid key `type` and explains which `type` properties are valid.

### Add a Function Map

Functions cannot be imported and exported via JSON, but a function map can be used to define functions like `onClick` for components such as Custom Buttons or `render` for Custom Elements or Custom Panels. The function map is kept separately from the Modular UI configuration. It needs to contain all functions that are used by custom components as they cannot be exported along with the other components in a JSON format.

The function map should contain one object with keys to denote the name of the function and the value as the function itself as shown below:

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

```js
const functionMap = {
  'alertClick': () => alert('Alert triggered!'),
  'flyoutSecondButtonOnClick': () => {
    console.log('Second Item clicked!');
  },
  'customElementRender': (args) => {
    const element = document.createElement('div');
    element.style.backgroundColor = 'darkgrey';
    element.style.color = 'white';
    element.style.textWrap = 'nowrap';
    element.style.textAlign = 'center';
    element.innerText = args;
    return element;
  },
  'customPanelRender': () => {
    const div = document.createElement('div');
    div.innerHTML = 'My custom panel';
    return div;
  },
};
```

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

You can then refer to functions in the function map by their key in `modularComponents`. For example, the `alertClick` function key can be added to a Custom Button and the `render` functions can be added to a Custom Element and Custom Panel as shown below:

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

```js
{
  "modularComponents": {
    "myButton": {
      "type": "customButton",
      "label": "My Button",
      "onClick": "alertClick"
    },
    "flyoutFirstButton": {
      "type": "customButton",
      "label": "Flyout First Button",
      "children": ["flyoutSecondButton"]
    },
    "flyoutSecondButton": {
      "type": "customButton",
      "label": "Flyout Second Button",
      "onClick": "flyoutSecondButtonOnClick"
    },
    "customElement": {
      "title": "Custom Element",
      "disabled": false,
      "type": "customElement",
      "render": "customElementRender",
      "renderArguments": ["Custom Args"]
    },
    "customPanelToggleButton": {
      "type": "toggleButton",
      "img": "icon-header-sidebar-line",
      "toggleElement": "customPanel"
    }
  },
  "panels": {
    "customPanel": {
      "render": "customPanelRender",
      "title": "Custom Panel",
      "location": "left"
    }
  }
}
```

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

The function map can be added by importing it along with the UI configuration:

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

```js
instance.UI.importModularComponents(configUI, functionMap);
```

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

[UI.importModularComponents](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor)

{% hint style="info" %}
**Importing with a Function Map**

The function map should always be reimported along with its associated configuration.
{% endhint %}

### Initialize with a UI Config File

If you have a configuration JSON file that you want your WebViewer UI to initialize with, you can add the `uiConfig` option to your WebViewer constructor with the path to your file:

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

```js
WebViewerConstructor({
  initialDoc: hashFile,
  path: '/lib',
  ui: uiOption,
  uiConfig: 'path/to/your/config.json',
}, document.getElementById('viewer')).then(instance => {
  window.instance = instance;
});
```

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

{% hint style="warning" %}
**Important: Whitelisting Required for Custom UI Configurations**

To enhance security, any remote or custom `uiConfig` file must originate from a whitelisted domain. You must add the origin (e.g., `https://yourdomain.com`) of your custom `uiConfig` to the `configorigin.txt` file in your deployment.

If the origin is not listed in `configorigin.txt`, WebViewer will block loading the configuration and the default UI will be initialized instead.

For more information, see our [config file article](/web/advanced/config-files.md#using-a-config-file-when-the-path-is-on-another-domain).
{% endhint %}

## Export a Modular UI

A Modular UI configuration can be exported from WebViewer using the `UI.exportModularComponents` method. The method will return a JSON object containing all the Modular Components, Headers, Flyouts, Panels, and Popups that have been added to the UI.

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

```js
instance.UI.exportModularComponents();
```

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

[UI.exportModularComponents](https://sdk.apryse.com/api/web/UI.html#.exportModularComponents__anchor)

{% hint style="info" %}
**Exporting Functions**

Since functions can't be stored in JSON, only the function key will be exported. Read more about creating and maintaining your function map [here](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor).
{% endhint %}

When exporting a Modular UI configuration, a JSON object will be returned. The JSON object can then be copied or saved to a file for viewing and editing.

Components are validated upon export and must include an object key to be exported successfully. Any validation warnings will be printed to the console.

If there are any functions that are referred to by components in the Modular UI and they are not included in a function map, they will be printed in a console warning:

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-3dbd7e20655d5b72cdfb8641cc6eb733cf1e604e%2F1c049c18c6c7a1fb95570ffa442814ac66c8254f-1008x530.png?alt=media)

The function can then be copied and added to the function map when importing the configuration.

{% hint style="info" %}
**Import/Export Differences**

Sometimes, an exported config will look different from an imported configuration. Usually this is because the exported configuration arranges all the object properties alphabetically instead of in the order that they were in the imported configuration. You might also notice that additional sections or elements are added, such as `popups`, even if you didn't include them in your imported configuration. This happens because those elements are included by default in the UI. You do not need to add them in your own import configs as they get added by default when the Modular UI is processed, but you can edit them in your import config.
{% endhint %}

## Modular UI Configuration Structure

A Modular UI configuration is a JSON object that contains all the components that are used to build a UI. The configuration can contain the following properties:

![a diagram of each of the parts in a Modular UI config file](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-c6c2e509d85a4a9fbce0849e0dfb5ef963def99a%2Ffbc9cda3071184c1afb115579b461ded34236c1e-1008x831.png?alt=media)

The above properties can be reordered in any way that is convenient for you. Each property can contain multiple objects with each object representing a component that is used in the UI.

To define each component and its properties within the top-level objects of the configuration structure, they should be given an object key which will be used as the `dataElement` of the component. The `dataElement` is a unique identifier used to refer to the component in the UI.

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

```js
{
  "modularComponents": {
    "myButton": {
      "type": "customButton",
      "label": "My Button",
      "onClick": "alertClick"
    }
  }
}
```

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

### Modular Components

Modular Components can be added to a UI configuration by adding a `modularComponents` property with all components as child objects. Modular Components refer to any items or containers that can be added to a Modular Header or a Flyout. A valid Modular Component in the configuration needs to include an object key unique to that component which will be used as its `dataElement`.

Modular components also need to contain a `type` property so that WebViewer knows which kind of component it needs to create. The `type` property can be any of the item types normally added to a Modular Header or Flyout. More detail in the [list of item types - api definitions](https://sdk.apryse.com/api/web/global.html#ItemType__anchor).

To add components to the UI, their object keys (`dataElement`) need to be included in the `items` property of a Modular Header or Flyout. Since Flyouts support a nested structure, `items` can also be included in the `children` property to create a nested Flyout.

Modular Components should be added to the configuration as shown below. In this example, a Button component is added to the configuration with a `dataElement` of `myButton` and a `label` of `My Button`. It also calls an `onClick` function of `alertClick` when clicked. The logic for `alertClick` is stored in the [function map](#add-a-function-map) and only referenced in the configuration JSON.

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

```js
{
  "modularComponents": {
    "myButton": {
      "type": "customButton",
      "label": "My Button",
      "onClick": "alertClick"
    }
  }
}
```

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

[Item Types](https://sdk.apryse.com/api/web/global.html#ItemType__anchor)

For more information on items which can be used in Modular Components, refer to the [Items](/web/ui-customization/modular-ui/items.md) and [Containers](/web/ui-customization/modular-ui/containers.md) documentation.

### Modular Headers

[Modular Headers](/web/ui-customization/modular-ui/containers.md#modular-header) can be easily created by adding a `modularHeaders` property with each header listed as a child object to your UI configuration.

Each header can have an `items` property which should contain an array of `dataElements` of the components to be added to the header. The items contained in a header all need to be valid and included in the `modularComponents` section of the modular UI configuration in order to be used.

There also needs to be a `placement` property that specifies where the header should be placed in the UI. The `placement` property can be any of the following values: `top`, `left`, `right`, or `bottom`.

Modular headers can also include any of the [container properties](https://sdk.apryse.com/api/web/global.html#ContainerProperties__anchor) used to customize how they appear in your UI.

Modular Headers should be added to the configuration as shown below. In this example, a Modular Header is added to the configuration JSON with a `dataElement` of `myHeader` and a `placement` of `top`. The `items` property contains an array with the `dataElement` of the Button component that was added earlier.

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

```js
{
  "modularHeaders": {
    "myHeader": {
      "placement": "top",
      "items": ["flyoutToggle", "myButton", "searchPanelToggle"]
    }
  }
}
```

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

[Container Properties](https://sdk.apryse.com/api/web/global.html#ContainerProperties__anchor)

For more information on Modular Headers, refer to the [Modular Headers](/web/ui-customization/modular-ui/containers.md) documentation.

### Flyouts

[Flyouts](/web/ui-customization/modular-ui/flyouts.md) are menus which are toggled by a component (ie. a [Toggle Element Button](/web/ui-customization/modular-ui/items.md#toggle-element-buttons)) and can be added to the JSON by adding a `flyouts` property.

Flyouts can contain items similarly to headers but in the case of a Flyout, the items can also be used to create submenus when they have a `children` property which contains other items. The `items` property should contain an array of `dataElements` of the components to be added to the first level of the Flyout.

The example below shows how to create a simple Modular UI configuration which includes a header that contains a Toggle Element Button to open a Flyout. The Flyout contains a Custom Button which has a `children` property that contains another Custom Button which is opened as a submenu.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-df7d6830ffbf3d3651f696e5681d3fb133717c47%2F3686c8773667f435080f2d0320991e9cc5698ddc-600x488.gif?alt=media)

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

```js
{
  "modularComponents": {
    "flyoutToggle": {
      "type": "toggleButton",
      "img": "ic-hamburger-menu",
      "toggleElement": "myFlyout"
    },
    "flyoutFirstButton": {
      "type": "customButton",
      "label": "Flyout First Button",
      "children": ["flyoutSecondButton"]
    },
    "flyoutSecondButton": {
      "type": "customButton",
      "label": "Flyout Second Button",
      "onClick": "flyoutSecondButtonOnClick"
    },
  },
  "modularHeaders": {
    "myHeader": {
      "placement": "top",
      "items": ["flyoutToggle"]
    }
  },
  "flyouts": {
    "myFlyout": {
      "items": ["flyoutFirstButton"]
    }
  }
}
```

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

[UI.Components.Flyout](https://sdk.apryse.com/api/web/UI.Components.Flyout.html)

{% hint style="info" %}
**Default Navigation**

Chevrons to denote that an item has children are added automatically. Back buttons are also added automatically to help with Flyout navigation.
{% endhint %}

#### ViewControls

Starting in version 11.11, the `ViewControlsFlyout` is automatically included in Modular UI configurations by default, similar to how popups are handled.

The `ViewControlsFlyout` is the menu that opens when clicking the view controls button in the header, providing options for changing page display modes and layouts.

If the `ViewControlsFlyout`is not explicitly defined in the configuration, it will still be applied to the UI using its default settings.

When the configuration is exported, the `ViewControlsFlyout`will appear in the exported configuration even if it was not explicitly defined in the original configuration.

### Panels

[Panels](/web/ui-customization/modular-ui/panels.md) can be included by adding a `panels` property with each panel listed as a child object in the configuration.

Panels need to include a `location` property that specifies where the panel should be placed in the UI. The `location` property can be either `left` or `right`.

They must also include a `render` property that specifies which Panel to render. The `render` property can include a [prebuilt panel](/web/ui-customization/modular-ui/panels.md#list-of-prebuilt-panels) name, a [custom panel](/web/ui-customization/modular-ui/panels.md#adding-custom-panels) with a function name that is referred to in a [function map](#add-a-function-map) or it can be `tabPanel` which allows you to create a [Tabbed Panel](/web/ui-customization/modular-ui/panels.md#tabbed-panels).

The following example shows how to add a panel that is rendered as a Search Panel to a Modular UI configuration.

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

```js
{
  "panels": {
    "myPanel": {
      "location": "left",
      "render": "searchPanel"
    }
  }
}
```

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

[UI.Components.Panel](https://sdk.apryse.com/api/web/UI.Components.Panel.html)

The next example shows how to create a Modular UI configuration that includes a Tabbed Panel similar to the one in the [Default UI](/web/ui-customization/modular-ui/getting-started.md#the-default-ui).

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-b71ee8f1660a18af7cf85062173d6b557f1f33fc%2Ffaf4b23f4095856d5fa7ffee46e9100a8daf1f04-600x396.gif?alt=media)

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

```js
Webviewer.WebComponent(
  {
    path: '/path/to/your/webviewer',
    initialDoc: '/path/to/your/document.pdf',
  },
  viewerElement
).then((instance) => {
  const configUI = {
    "modularComponents": {
      "left-panel-toggle": {
        "title": "Left Panel",
        "type": "toggleButton",
        "img": "icon-header-sidebar-line",
        "toggleElement": "customLeftPanel"
      }
    },
    "modularHeaders": {
      "default-top-header": {
        "placement": "top",
        "items": [
          "left-panel-toggle"
        ]
      }
    },
    "panels": {
      "customLeftPanel": {
        "render": "tabPanel",
        "panelsList": [
          {
            "render": "thumbnailPanel"
          },
          {
            "render": "outlinesPanel"
          },
          {
            "render": "bookmarkPanel"
          },
          {
            "render": "layersPanel"
          },
          {
            "render": "signaturePanel"
          },
          {
            "render": "fileAttachmentPanel"
          },
          {
            "render": "portfolioPanel"
          }
        ],
        "location": "left"
      },
      "thumbnailPanel": {
        "render": "thumbnailsPanel",
        "location": "left"
      },
      "outlinesPanel": {
        "render": "outlinesPanel",
        "location": "left"
      },
      "bookmarkPanel": {
        "render": "bookmarksPanel",
        "location": "left"
      },
      "layersPanel": {
        "render": "layersPanel",
        "location": "left"
      },
      "fileAttachmentPanel": {
        "render": "fileAttachmentPanel",
        "location": "left"
      },
      "signaturePanel": {
        "render": "signaturePanel",
        "location": "left"
      },
      "portfolioPanel": {
        "render": "portfolioPanel",
        "location": "left"
      }
    }
  };

  instance.UI.importModularComponents(configUI);
});
```

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

[UI.Components.Panel](https://sdk.apryse.com/api/web/UI.Components.Panel.html) [UI.importModularComponents](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor)

### Popups

Starting in version `11.8`, Popups are also included in Modular UI configurations. There are three popups that are included in the Modular UI by default:

* The `annotationPopup` which opens when annotations are selected.
* The `textPopup` which opens when text in a document is selected.
* The `contextMenuPopup` which opens when right-clicking in the document viewer.

The following example shows how the default popups appear in a config file:

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

```javascript
    "popups": {
      "annotationPopup": [
        { "dataElement": "viewFileButton" },
        { "dataElement": "annotationCommentButton" },
        { "dataElement": "annotationStyleEditButton" },
        { "dataElement": "annotationDateEditButton" },
        { "dataElement": "annotationRedactButton" },
        { "dataElement": "annotationCropButton" },
        { "dataElement": "annotationContentEditButton" },
        { "dataElement": "annotationClearSignatureButton" },
        { "dataElement": "annotationGroupButton" },
        { "dataElement": "annotationUngroupButton" },
        { "dataElement": "formFieldEditButton" },
        { "dataElement": "calibratePopupButton" },
        { "dataElement": "linkButton" },
        { "dataElement": "fileAttachmentDownload" },
        { "dataElement": "annotationDeleteButton" },
        { "dataElement": "shortCutKeysFor3D" },
        { "dataElement": "playSoundButton" },
        { "dataElement": "openAlignmentButton" }
      ],
      "textPopup": [
        { "dataElement": "copyTextButton" },
        { "dataElement": "textHighlightToolButton" },
        { "dataElement": "textUnderlineToolButton" },
        { "dataElement": "textSquigglyToolButton" },
        { "dataElement": "textStrikeoutToolButton" },
        { "dataElement": "textRedactToolButton" },
        { "dataElement": "linkButton" }
      ],
      "contextMenuPopup": [
        { "dataElement": "panToolButton" },
        { "dataElement": "stickyToolButton" },
        { "dataElement": "highlightToolButton" },
        { "dataElement": "freeHandToolButton" },
        { "dataElement": "freeHandHighlightToolButton" },
        { "dataElement": "freeTextToolButton" },
        { "dataElement": "markInsertTextToolButton" },
        { "dataElement": "markReplaceTextToolButton" }
      ]
    }
```

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

The Popup section is optional and can be omitted from a configuration.

If a default popup is not explicitly defined in the configuration, it will still be applied to the UI using its default settings. When the configuration is exported, these default popups will appear in the exported configuration even if they were not explicitly defined in the original configuration.

Each popup can be customized by adding, removing, or reordering items in its array.

Since popup items are defined as array entries rather than keyed objects, each item must explicitly define a dataElement.

If a Popup's array is empty, it will not appear in the UI.

In this example:

* The `annotationPopup` is customized by removing specific items.
* The `textPopup` is disabled by providing an empty array.
* The `contextMenuPopup` is omitted from the configuration, so it remains unchanged and will be exported with its default configuration.

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

```javascript
    "popups": {
      "annotationPopup": [
        // Items can be removed from default popups.
        { "dataElement": "annotationCommentButton" },
        { "dataElement": "annotationStyleEditButton" },
        { "dataElement": "linkButton" },
        { "dataElement": "annotationDeleteButton" },
      ],
      // To remove a default popup altoger, leave it empty.
      "textPopup": [],
      // To use a default popup as is, you can remove it from the config.
      // In this case, "contextMenuPopup is removed".
    }
```

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

Refer to [this guide](/web/ui-customization/customizing-popup.md#customizing-popups-with-the-modular-ui) for more information on customizing Popups.

## Examples

### Basic Modular UI Example

The following example shows how to create a basic Modular UI configuration. It includes a Custom Button, a Custom Header, a Panel, and a Flyout with a submenu. The configuration JSON object is then imported into WebViewer along with a function map that contains functions for the Custom Buttons. Here the popups are left in their default state and can be edited by adding or removing items from them.

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

```js
WebViewer(
  {
    path: '/path/to/your/webviewer',
    initialDoc: '/path/to/your/document.pdf',
  },
  // your viewer element
  document.getElementById('viewer')
).then((instance) => {
  const configUI = {
    "modularComponents": {
      "myButton": {
        "type": "customButton",
        "label": "My Button",
        "onClick": "alertClick"
      },
      "flyoutToggle": {
        "type": "toggleButton",
        "img": "ic-hamburger-menu",
        "toggleElement": "myFlyout"
      },
      "flyoutFirstButton": {
        "type": "customButton",
        "label": "Flyout First Button",
        "children": [
          "flyoutSecondButton"
        ]
      },
      "flyoutSecondButton": {
        "type": "customButton",
        "label": "Flyout Second Button",
        "onClick": "flyoutSecondButtonOnClick"
      },
      "searchPanelToggle": {
        "type": "toggleButton",
        "img": "icon-header-search",
        "toggleElement": "myPanel"
      }
    },
    "modularHeaders": {
      "myHeader": {
        "placement": "top",
        "items": [
          "flyoutToggle",
          "myButton",
          "searchPanelToggle"
        ]
      }
    },
    "panels": {
      "myPanel": {
        "location": "left",
        "render": "searchPanel"
      }
    },
    "flyouts": {
      "myFlyout": {
        "items": [
          "flyoutFirstButton"
        ]
      }
    },
    "popups": {
      "annotationPopup": [
        { "dataElement": "viewFileButton" },
        { "dataElement": "annotationCommentButton" },
        { "dataElement": "annotationStyleEditButton" },
        { "dataElement": "annotationDateEditButton" },
        { "dataElement": "annotationRedactButton" },
        { "dataElement": "annotationCropButton" },
        { "dataElement": "annotationContentEditButton" },
        { "dataElement": "annotationClearSignatureButton" },
        { "dataElement": "annotationGroupButton" },
        { "dataElement": "annotationUngroupButton" },
        { "dataElement": "formFieldEditButton" },
        { "dataElement": "calibratePopupButton" },
        { "dataElement": "linkButton" },
        { "dataElement": "fileAttachmentDownload" },
        { "dataElement": "annotationDeleteButton" },
        { "dataElement": "shortCutKeysFor3D" },
        { "dataElement": "playSoundButton" },
        { "dataElement": "openAlignmentButton" }
      ],
      "textPopup": [
        { "dataElement": "copyTextButton" },
        { "dataElement": "textHighlightToolButton" },
        { "dataElement": "textUnderlineToolButton" },
        { "dataElement": "textSquigglyToolButton" },
        { "dataElement": "textStrikeoutToolButton" },
        { "dataElement": "textRedactToolButton" },
        { "dataElement": "linkButton" }
      ],
      "contextMenuPopup": [
        { "dataElement": "panToolButton" },
        { "dataElement": "stickyToolButton" },
        { "dataElement": "highlightToolButton" },
        { "dataElement": "freeHandToolButton" },
        { "dataElement": "freeHandHighlightToolButton" },
        { "dataElement": "freeTextToolButton" },
        { "dataElement": "markInsertTextToolButton" },
        { "dataElement": "markReplaceTextToolButton" }
      ]
    }
  };

  const functionMap = {
    'alertClick': () => alert('Alert triggered!'),
    'flyoutSecondButtonOnClick': () => {
      console.log('Second Item clicked!');
    },
  };

  instance.UI.importModularComponents(configUI, functionMap);
});
```

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

[UI.importModularComponents](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor)

### Working with the Default UI

The following example was build using an export of the [Default UI](/web/ui-customization/modular-ui/getting-started.md#the-default-ui) as a base.

It has been customized by editing the default ribbon to only contain the View and Annotate ribbon items, and adding the page navigation buttons to the top header.

It also has some reordered tools in the Annotate ribbon group which will now open on the left side of the UI instead of at the top underneath the default header. The Style Panel’s location has changed to open on the right instead of the left as well.

The main menu flyout was also customized to include only the Open File and Save As buttons.

Finally, the Popups were simplified to only include the Annotation Popup which opens when you click on an annotation. It has the Comment, Style, Link, and Delete buttons. The Text Popup was left empty so it will not appear. The Context Menu Popup was left out of the config. This means that it will be used as is and still work when right clicking in the document viewer.

*Note: although the Context Menu Popup is not in the import, it will appear when the config is exported.*

There is no need to include a function map in this example as no custom functions are used in the configuration.

See the comments in the code snippet below to understand how the configuration was built.

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

```js
WebViewer(
  {
    path: '/path/to/your/webviewer',
    initialDoc: '/path/to/your/document.pdf',
    // add Open File button to the Main Menu
    enableFilePicker: true,
  },
  // your viewer element
  document.getElementById('viewer')
).then((instance) => {
  const configUI = {
    "modularComponents": {
      // only include the buttons, grouped items, dividers,
      // ribbon items, and ribbon groups that are needed here
      "page-controls-container": {
        "type": "pageControls"
      },
      "filePickerButton": {
        "type": "presetButton"
      },
      "saveAsButton": {
        "type": "presetButton"
      },
      "menu-toggle-button": {
        "img": "ic-hamburger-menu",
        "title": "component.menuOverlay",
        "toggleElement": "MainMenuFlyout",
        "type": "toggleButton"
      },
      "zoom-container": {
        "type": "zoom"
      },
      "highlightToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateTextHighlight"
      },
      "underlineToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateTextUnderline"
      },
      "strikeoutToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateTextStrikeout"
      },
      "squigglyToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateTextSquiggly"
      },
      "freeTextToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateFreeText"
      },
      "rectangleToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateRectangle"
      },
      "markInsertTextToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateMarkInsertText"
      },
      "markReplaceTextToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateMarkReplaceText"
      },
      "freeHandToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateFreeHand"
      },
      "freeHandHighlightToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateFreeHandHighlight"
      },
      "stickyToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationCreateSticky"
      },
      "divider-0.4011225832731946": {
        "type": "divider"
      },
      "stylePanelToggle": {
        "title": "action.style",
        "type": "toggleButton",
        "img": "icon-style-panel-toggle",
        "toggleElement": "stylePanel"
      },
      "divider-0.5730925860609144": {
        "type": "divider"
      },
      "undoButton": {
        "type": "presetButton",
        "buttonType": "undoButton"
      },
      "redoButton": {
        "type": "presetButton",
        "buttonType": "redoButton"
      },
      "eraserToolButton": {
        "type": "toolButton",
        "toolName": "AnnotationEraserTool"
      },
      "defaultAnnotationUtilities": {
        // items here are defined above
        "items": [
          "divider-0.4011225832731946",
          "stylePanelToggle",
          "divider-0.5730925860609144",
          "undoButton",
          "redoButton",
          "eraserToolButton"
        ],
        "type": "groupedItems",
        "grow": 0,
        "gap": 12,
        "alwaysVisible": false,
        "style": {}
      },
      "annotateGroupedItems": {
        // items here are defined above
        "items": [
          "underlineToolButton",
          "highlightToolButton",
          "rectangleToolButton",
          "freeTextToolButton",
          "freeHandToolButton",
          "freeHandHighlightToolButton",
          "stickyToolButton",
          "squigglyToolButton",
          "strikeoutToolButton",
          "markInsertTextToolButton",
          "markReplaceTextToolButton",
          "defaultAnnotationUtilities"
        ],
        "type": "groupedItems",
        "justifyContent": "center",
        "grow": 0,
        "gap": 12,
        "alwaysVisible": false,
        "style": {}
      },
      "toolbarGroup-View": {
        "title": "View",
        "type": "ribbonItem",
        "label": "View",
        "groupedItems": [],
        "toolbarGroup": "toolbarGroup-View"
      },
      "toolbarGroup-Annotate": {
        "title": "Annotate",
        "type": "ribbonItem",
        "label": "Annotate",
        "groupedItems": [
          "annotateGroupedItems"
        ],
        "toolbarGroup": "toolbarGroup-Annotate"
      },
      "default-ribbon-group": {
        // include all the tool groups that you want in the ribbon here
        "items": [
          "toolbarGroup-View",
          "toolbarGroup-Annotate"
        ],
        "type": "ribbonGroup",
        "justifyContent": "center",
        "grow": 2,
        "gap": 12,
        "alwaysVisible": false,
        "style": {}
      },
      "searchPanelToggle": {
        "title": "component.searchPanel",
        "type": "toggleButton",
        "img": "icon-header-search",
        "toggleElement": "searchPanel"
      },
      "notesPanelToggle": {
        "title": "component.notesPanel",
        "type": "toggleButton",
        "img": "icon-header-chat-line",
        "toggleElement": "notesPanel"
      }
    },
    "modularHeaders": {
      "default-top-header": {
        "placement": "top",
        "grow": 0,
        "gap": 12,
        "position": "start",
        "float": false,
        "stroke": true,
        "dimension": {
          "paddingTop": 8,
          "paddingBottom": 8,
          "borderWidth": 1
        },
        "style": {},
        // include all items that you want in the top header here
        "items": [
          "menu-toggle-button",
          "zoom-container",
          "default-ribbon-group",
          "searchPanelToggle",
          "notesPanelToggle",
          "page-controls-container"
        ]
      },
      "tools-header": {
        // choose where you want to place the secondary header here
        "placement": "left",
        "justifyContent": "start",
        "grow": 0,
        "gap": 12,
        "position": "end",
        "float": false,
        "stroke": true,
        "dimension": {
          "paddingTop": 8,
          "paddingBottom": 8,
          "borderWidth": 1
        },
        "style": {},
        "items": [
          "annotateGroupedItems"
        ]
      }
    },
    "panels": {
      // placing all the panels on the right side
      // since the left side is already occupied by the tools header
      "stylePanel": {
        "render": "stylePanel",
        "location": "right"
      },
      "notesPanel": {
        "render": "notesPanel",
        "location": "right"
      },
      "searchPanel": {
        "render": "searchPanel",
        "location": "right"
      }
    },
    "flyouts": {
      "MainMenuFlyout": {
        "items": [
          // include the buttons that are needed in the Main Menu here
          "filePickerButton",
          "divider",
          "saveAsButton"
        ]
      }
    },
    "popups": {
      "annotationPopup": [
        { "dataElement": "annotationCommentButton" },
        { "dataElement": "annotationStyleEditButton" },
        { "dataElement": "linkButton" },
        { "dataElement": "annotationDeleteButton" },
      ],
      // to remove a default popup, leave it empty
      "textPopup": [],
      // to use a default popup as is, you can remove it from the config
    }
  };

  instance.UI.importModularComponents(configUI);
});
```

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

[UI.importModularComponents](https://sdk.apryse.com/api/web/UI.html#.importModularComponents__anchor)


---

# 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/ui-customization/modular-ui/ui-import-and-export.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.
