> 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-add-personal-bookmark-pdf.md).

# Add Bookmarks to PDFs Showcase Demo Code Sample

Add bookmarks to quickly return to specific pages within your PDF document.

{% 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://apryse.com/capabilities#PageManipulation" class="button primary">Package: Page Manipulation</a><a href="https://showcase.apryse.com/add-personal-bookmark-pdf" class="button primary">Live demo</a>
{% endhint %}

Add bookmarks to quickly return to specific pages within your PDF document.

This demo allows you to:

* Upload your own PDF document
* Add bookmarks to quickly return to pages within the PDF document

**Implementation steps** To add user bookmark capability with WebViewer:

Step 1: Get started with your preferred [web stack for WebViewer](/web/get-started/readme.md) 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" %}

{% tabs %}
{% tab title="index.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 4 (Preview), 2025-09-17
// File: showcase-demos/add-personal-bookmark-pdf/index.js


import WebViewer from '@pdftron/webviewer';

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

// Function to initialize and load the WebViewer with user bookmarks feature
function addUserBookmarks() {
  const element = document.getElementById('viewer');
  if (!element) {
    console.error('Viewer div not found.');
    return;
  }

  WebViewer({
    path: '/lib',
    initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/Report_2011.pdf',
    licenseKey: licenseKey,
    enableFilePicker: true,
  }, element).then(instance => {

    instance.UI.enableFeatures([instance.UI.Feature.Initials]);

    // Reset state on document change
    instance.Core.documentViewer.addEventListener('documentLoaded', () => {
      instance.UI.openElements(['tabPanel']);
      instance.UI.setActiveTabInPanel({ tabPanel: 'tabPanel', tabName: 'bookmarksPanel' });
      instance.UI.enableBookmarkIconShortcutVisibility();

      setBookmarks({});
      setBookmarkName('My Bookmark');
      setPageNumber(1);
      createBookmarkControls();
    });

    instance.UI.addEventListener('userBookmarksChanged', (bookmarks) => {
      console.log('User bookmarks updated:', bookmarks);

    });
  });
}
// Bookmarks state
let bookmarks = {};

//Usage: 'setBookmarks({ ...bookmarks, [pageNumber]: bookmarkName });'
function setBookmarks(newBookmarks) {
  bookmarks = newBookmarks;
  // Optionally, update the UI or perform other actions here
}

// Page number state
let pageNumber = 1;
function setPageNumber(val) {
  pageNumber = val;
}

// Bookmark name state
let bookmarkName = 'My Bookmark';
function setBookmarkName(val) {
  bookmarkName = val;
}

// Error state
let error = '';
function setError(val) {
  error = val;
  // if empty, or null, hide error div
  if (val &#x26;&#x26; val !== '' &#x26;&#x26; val.length !== 0) {
    console.error('Bookmark Error:', val);

  } else {
    console.log('Clearing bookmark error');
  }

  const errorDiv = document.querySelector('#bookmark-error-div');
  if (errorDiv) {
    errorDiv.textContent = val;
    errorDiv.style.display = 'block';
  }
}

// Button enabled state
let isButtonEnabled = true;
function setIsButtonEnabled(enabled) {
  isButtonEnabled = enabled;
  const button = document.querySelector('.bookmark-add-btn');
  if (button) {
    button.disabled = !enabled;
  }
}

// Function to create a new bookmark
function createNewBookmark() {
  const doc = WebViewer.getInstance().Core.documentViewer.getDocument();
  const bookmarksObject = bookmarks;
  const bookmarkName = document.querySelector('.bookmark-title-input').value || 'My Bookmark';
  setPageNumber(document.querySelector('.bookmark-page-input').value || 1);

  console.log('Creating new bookmark at page', pageNumber, 'with name', bookmarkName);

  // validate bookmarksObject exists
  if (!bookmarksObject) {
    console.log('Bookmarks feature is not available.');
    return;
  }

  // validate doc exists
  if (!doc) {
    console.log('Document is not loaded.');
    return;
  }

  // validate pageNumber is valid 
  if (isNaN(pageNumber) || pageNumber &#x3C; 1 || pageNumber > doc.getPageCount()) {
    setError('Please enter a valid page number.');
  } else {
    bookmarks[pageNumber - 1] = bookmarkName;
    WebViewer.getInstance().UI.importBookmarks(bookmarksObject);
    setBookmarks({ ...bookmarksObject, [pageNumber - 1]: bookmarkName });
    setIsButtonEnabled(true);
    setError('');
  }
}

// Function to created bookmark controls
function createBookmarkControls() {
  console.log('Loading bookmark controls');

//Checking if controls already exist

  const existingControls = document.getElementById('controls-container');
  if (existingControls) {
    resetBookmarkControls();
    console.log('Bookmark controls already exist');
    return;
  }

  // Create a container for all controls (label, dropdown, and buttons)
  const controlsContainer = document.createElement('div');
  controlsContainer.className = 'control-container';
  controlsContainer.id = 'controls-container';
  const element = document.getElementById('viewer');
  if (!element) {
    console.error('Viewer div not found.');
    return;
  }
  element.insertBefore(controlsContainer, element.firstChild);
  
  console.log('controls.js loaded, createBookmarkControls:', window.createBookmarkControls);
  // Dynamically load controls.js if not already loaded
  if (!window.createBookmarkControls) {
    const script = document.createElement('script');
    script.src = '/showcase-demos/add-personal-bookmark-pdf/controls.js';
    script.onload = function () {
      console.log('Loaded controls', createControls);
      console.log('createNewBookmarks:', createNewBookmark);
      console.log('resetBookmarkControls:', resetBookmarkControls);

      createControls('controls-container', createNewBookmark, resetBookmarkControls);

    };
    document.head.appendChild(script);
  }
}

function resetBookmarkControls() {
  
  setBookmarks({});//Clear bookmarks state
  setPageNumber(1);
  setBookmarkName('My Bookmark');
  setError('');
  setIsButtonEnabled(true);
  
  console.log('Bookmark controls reset');
}

// Initialize the WebViewer and add user bookmarks feature
addUserBookmarks();



</code></pre>

{% endtab %}

{% tab title="controls.js" %}
{% code title="controls.js" lineNumbers="true" %}

```js
// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 4 (Preview), 2025-09-17
// File: showcase-demos/add-personal-bookmark-pdf/controls.js

function createControls(targetDivId, createNewBookmark, resetBookmarkControls) {
    const container = document.getElementById(targetDivId);
    if (!container) {
        console.error('Target div not found:', targetDivId);
        return;
    } 
    container.classList.add('bookmark-controls-container');

    // 1. Bolded element with text "Bookmark Details"
    const detailsP = document.createElement('p');
    detailsP.textContent = 'Bookmark Details';
    detailsP.style.fontWeight = 'bold';
    container.appendChild(detailsP);

    // 2. <p> with text "Bookmark Title:"
    const titleLabel = document.createElement('p');
    titleLabel.textContent = 'Bookmark Title:';
    container.appendChild(titleLabel);

    // 3. Textbox for bookmark title
    const titleInput = document.createElement('input');
    titleInput.type = 'text';
    titleInput.value = 'My Bookmark';
    titleInput.className = 'bookmark-title-input';
    container.appendChild(titleInput);

    // 4. <p> with text "Destination Page:"
    const pageLabel = document.createElement('p');
    pageLabel.textContent = 'Destination Page:';
    container.appendChild(pageLabel);

    // 5. Textbox for destination page
    const pageInput = document.createElement('input');
    pageInput.type = 'text';
    pageInput.value = '1';
    pageInput.className = 'bookmark-page-input';
    container.appendChild(pageInput);

    // 6. Button to add new bookmark
    const addButton = document.createElement('button');
    addButton.textContent = 'Add New Booknark';
    addButton.className = 'bookmark-add-btn';
    addButton.onclick = function () {
        createNewBookmark();
    };
    container.appendChild(addButton);

    //Add error div
    const errorDiv = document.createElement('div');
    errorDiv.style.color = 'red';
    errorDiv.style.display = 'none';
    errorDiv.id = 'bookmark-error-div';
    container.appendChild(errorDiv);
}

```

{% endcode %}
{% endtab %}

{% tab title="index.css" %}
{% code title="index.css" lineNumbers="true" %}

```css
/* Layout for viewer and its children */
#viewer {
    display: flex;
    margin: 0 0 0 0;
    width: 100%;
}

#playground-viewer{
    height: 90% !important;
}

/* Controls Container on the left */
.control-container {
    width: 150px;
    overflow-x: hidden;
    margin: auto;
    padding-bottom: 50px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    margin: 0;
    padding: 10px 5px 5px 10px;
    border-right: 1px solid #e0e0e0;
    background-color: rgba(112, 198, 255, 0.2);
    /* Light blue background for contrast */
}

.bottom-headers-wrapper {
    bottom: -11px !important;
}

#editorWrapper {
    width: 100% !important;
    height: 85% !important;
    padding-bottom: 50px !important;
    margin-bottom: 250px !important;
}

/* WebViewer container on the right */
#webviewer-1 {
    margin: 0;
    padding-bottom: 10px !important;


}

/* Responsive Design */
@media (max-width: 768px) {
    #viewer {
        flex-direction: column;
    }

    .control-container {
        width: 150px;
        height: auto;
        border-right: none;
        border-bottom: 1px solid #e0e0e0;
        align-items: center;
    }


}


#GenericFileTab{
    margin-bottom: 50px !important;
}

/* Gallery Picker Styles */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 16px;
    overflow-y: auto;
    max-height: 100vh;
    padding: 10px;
}

.gallery-thumb {
    cursor: pointer;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 8px;
    transition: box-shadow 0.2s;
}

.gallery-thumb:hover,
.gallery-thumb:focus {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    border-color: #70c6ff;
}

.gallery-thumb-img {
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: 4px;
    margin-bottom: 6px;
}

.gallery-thumb-label {
    font-size: 13px;
    color: #333;
    text-align: center;
    margin-top: 2px;
    word-break: break-word;
}
```

{% endcode %}
{% endtab %}

{% tab title="controls.css" %}
{% code title="controls.css" lineNumbers="true" %}

```css
.bookmark-controls-container {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 300px;
  margin: 0 auto;
}

.bookmark-title-input,
.bookmark-page-input {
  width: 100%;
  padding: 6px 8px;
  font-size: 1em;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.bookmark-add-btn {
  padding: 8px 12px;
  font-size: 1em;
  background: #1976d2;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: bold;
  transition: background 0.2s;
}

.bookmark-add-btn:hover {
  background: #1565c0;
}

.bookmark-controls-container p {
  margin: 0 0 2px 0;
}

```

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

[View the full sample on GitHub](https://github.com/ApryseSDK/webviewer-samples/tree/main/showcase-demos-playground)


---

# 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-add-personal-bookmark-pdf.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.
