> 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/get-started/samples/showcase-demo-pdf-layers.md).

# PDF Layers Showcase Demo Code Sample

See how to programmatically add PDF layer separation capabilities (Optional Content Groups). This code relates to our related showcase demo.

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="https://showcase.apryse.com/pdf-layers" class="button primary">Live demo</a>
{% endhint %}

Easily enable or disable layers in PDF construction drawings.

This demo lets you:

* Upload a PDF file of your construction drawings
* Toggle individual layers (e.g., architectural, electrical) using Optional Content Groups (OCGs)
* Edit layout and content directly within the PDF

### **Implementation steps**

To add layer separation capability to a PDF with WebViewer: Step 1: [Get started with WebViewer](/web/get-started/readme.md) in your preferred web stack Step 2: Add the ES6 JavaScript sample code provided in this guide

Once you generate your license key, it will automatically be included in your sample code below.

{% @apryse-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 3.5, 2025-08-03
// File: pdf-layers/index.js

import WebViewer from '@pdftron/webviewer';

const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';

const element = document.getElementById('viewer');
let theInstance = null;
const onLoad = async (instance) => {
  theInstance = instance;
  instance.Core.documentViewer.addEventListener('documentLoaded', () => {
    // Initialize layers when the document is loaded
    initLayers();
  });
};

// sample PDF with multiple layers
const defaultLayersDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/construction_drawing-final.pdf';

WebViewer(
  {
    path: '/lib',
    licenseKey: licenseKey, 
    initialDoc: defaultLayersDoc,
    enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
  },
  element
).then((instance) => {
  onLoad(instance);
});

// Define variable to hold layers information
let docLayers = null;
let flatLayers = null;

// Array to hold labels, checkboxes and line breaks for layers
// This will be used to dynamically create UI elements for each layer and remove them when needed
let layerElements = [];

// Show or hide layers based on checkbox state.
// Set the value of the "visible" property for each layer in the layers array
// and update the document viewer to reflect the changes.
// The sender parameter is the actual checkbox that was changed.
// The checkbox's "value" property contains the nesting level of the layer
function updateLayersDisplay(sender) {
  // check if layer has children
  const layerLevel = +sender.value; // get the level as number
  let startCheck = false;
  let endCheck = false;
  layerElements.forEach(el => {
    if(endCheck)
      return;
    if(el.isSameNode(sender)){
      console.log("sender same as el", sender);
      startCheck = true; // found our checkbox. Start checking from next iteration
      return; // from the forEach
    }
 
    if(el.type === 'checkbox' &#x26;&#x26; startCheck){
      if(+el.value &#x3C;= layerLevel){ // Stop checking when we find first non-child
        endCheck = true;
        return;
      }
      // el is a child of sender. Set all children like sender
      el.checked = sender.checked; 
    }
  });
  // Following code updates the layer's visibility to match the checkboxes
  if (flatLayers &#x26;&#x26; flatLayers.length > 0) {
    let checkboxIndex = 0;
    // loop to find the checkboxes, which are in the same order as their corresponding flat layers
    layerElements.forEach(el => {
      if(el.type === 'checkbox'){
        flatLayers[checkboxIndex].visible = el.checked;
        checkboxIndex++;
      }
    });
    // reflect the layers' visibility on the document
    const documentViewer = theInstance.Core.documentViewer;
    const doc = documentViewer.getDocument();
    doc.setLayersArray(flatLayers);
    documentViewer.refreshAll();
    documentViewer.updateView();
  }
}

// Retrieve the layers from the document and initialize the UI.
// This function will be called when the document is loaded and will populate
// the layers array with the document's layers and create a checkbox for each layer
// If no layers are found, it will suggest loading the default document
async function initLayers() {
  const doc = theInstance.Core.documentViewer.getDocument();
  docLayers = await doc.getLayersArray();

  // If no layers are found, suggest loading the default document
  if(doc.getType() !== 'pdf') {
    labelLayers.textContent = `This sample only supports PDFs. Click "${buttonDefault.textContent}" to load a sample with layers.`;
  } else if(!docLayers || docLayers.length === 0){
    labelLayers.textContent = `This document has no layers. Click "${buttonDefault.textContent}" to load a sample with layers.`;
  }

  // Clear existing labels and checkboxes if any exist from previous document
  layerElements.forEach(element => element.remove());
  layerElements = [];
  // reset the flatLayers array
  flatLayers = [];

  if(!docLayers || docLayers.length === 0) {
    return; // Exit if no layers are found
  }

  let currnetLevel = 0; // will be larger than zero for nested layers
  
  // Function to create UI elements for the layers.
  // Called recursively in case there are child (nested) layers
  function addCheckboxes(layerArray){
    layerArray.forEach((layer) => {
      flatLayers.push(layer); // Add the layer to the flatLayers array
      layer.visible = true; // Set all layers to visible by default
      const checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.value = currnetLevel; // zero if it's not for a child layer
      // indent the child checkboxes based on their nesting level
      checkbox.style.marginLeft = "" + (currnetLevel * 15) + "px";
      checkbox.checked = layer.visible;
      // display the layer's name next to its checkbox
      const label = document.createElement('label');
      label.className = 'label-style';
      label.textContent = layer.name;
      // The container has 2 lines separated by a line break
      // Insert the labels and checkboxes before the line break
      controlsContainer.insertBefore(label, lineBreak);
      controlsContainer.insertBefore(checkbox, label);
      // separate each layer's checkbox and label from the previous layer with a new line break
      const br = document.createElement('br');
      controlsContainer.insertBefore(br, checkbox);
      // handle the change event for the checkboxes
      checkbox.onchange = () => {
        updateLayersDisplay(checkbox);
      };
      // Add the line-break, label and checkbox to the layer elements array for later reference
      layerElements.push(br);
      layerElements.push(label);
      layerElements.push(checkbox);
      if(layer.children &#x26;&#x26; layer.children.length > 0) {
        // Since the layer has children, recursively add checkboxes for these child layers between the brackets
        currnetLevel++;
        addCheckboxes(layer.children);
        currnetLevel--;
      }
    });
  }
  // Add checkboxes for each layer in the layers array and create a flat array of layers
  addCheckboxes(docLayers);

  // Update the label to show the number of layers
  labelLayers.textContent = `Layers found: ${flatLayers.length}`;
}

// UI section

// Create a container for all controls (labels, buttons, checkboxes, etc.)
const controlsContainer = document.createElement('div');

const labelUpload = document.createElement('label');
labelUpload.textContent = 'Use the Open File command in the WebViewer UI menu to upload a PDF file';

// Create a button to open default PDF
const buttonDefault = document.createElement('button');
buttonDefault.textContent = 'Default Document';
buttonDefault.onclick = async () => {
  // load default PDF with layers
  theInstance.UI.loadDocument(defaultLayersDoc);
};

// Label to display layer count or suggest loading default document
const labelLayers = document.createElement('label');
labelLayers.textContent = "";

// Style the container and controls using CSS classes
controlsContainer.className = 'control-container';

buttonDefault.className = 'btn-style';
labelUpload.className = 'label-style';
labelLayers.className = 'label-style';

// Create a break element to separate controls into two lines
const lineBreak = document.createElement('br');

// Append all controls to the container
controlsContainer.appendChild(labelLayers);
controlsContainer.appendChild(lineBreak);
controlsContainer.appendChild(labelUpload);
controlsContainer.appendChild(buttonDefault);
// modify viewer element and its parent div to display controlsContainer with viewer side-by-side
element.parentElement.style.display = "flex";
element.style.display = "inline-block";
element.style.width = "75%";
controlsContainer.style.height = "100%";
controlsContainer.style.display = "inline-block";
controlsContainer.style.width = "25%";
// Add the controls container right before to the viewer
element.parentElement.insertBefore(controlsContainer, element);

</code></pre>


---

# 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/get-started/samples/showcase-demo-pdf-layers.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.
