> 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-watermark.md).

# Add and Edit Watermark Showcase Demo Code Sample

Add and edit watermarking on PDF documents. Watermark text is displayed as an overlay text on top of the original 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#Security" class="button primary">Package: Security</a><a href="https://showcase.apryse.com/add-watermark" class="button primary">Live demo</a>
{% endhint %}

Easily add and edit watermarks on PDF documents. Watermark text is displayed as an overlay text on top of the original document.

This demo allows you to:

* Choose your own PDF file
* Add and edit watermark text
* Customize styles of the watermark text

**Implementation steps** To add watermarking capability with WebViewer:

Step 1: Follow get started guide in 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.0, GPT-4.1, September 22, 2025
// File: index.js

import WebViewer from '@pdftron/webviewer';

// Add Watermark Demo
// 
// This code demonstrates the document watermarking capability in WebViewer, a JavaScript-based PDF SDK for web applications.
// It allows the user to update the text and easily place an overlay text on top of the original document.
//

const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/arrest-warrant-signed.pdf';

// Default colors for color picker
const DEFAULT_COLORS = [
    '#EB7532', // Orange
    '#F7D038', // Yellow
    '#A3E048', // Green
    '#519A64', // Dark Green
    '#33BBE6', // Blue
    '#4355DB', // Dark Blue
    '#D23BE7', // Purple
    '#E6261F', // Red
];

// Default watermark properties
let text = 'Hello World!';
let size = 60;
let opacity = 50;
let color = DEFAULT_COLORS[0];
let showTimestamp = true;

// Customize UI section
const customizeUI = async (instance) => {
    // Load default document
    await instance.Core.documentViewer.loadDocument(defaultDoc, {
        extension: 'pdf'
    });

    drawWatermark(instance);
};

const drawWatermark = async (instance) => {
    let watermarks = {
        diagonal: {
            text: text,
            fontSize: size,
            color: color,
            opacity: opacity,
        },
        header: {
            center: '',
            fontSize: 10,
            color: color,
            opacity: 100,
        }
    };

    if (showTimestamp) {
        watermarks.header.center = `${new Date(Date.UTC(2000,0,0,0,0,0,0)).toTimeString()}`;
    }

    instance.Core.documentViewer.setWatermark({
        ...watermarks,
    });

    instance.Core.documentViewer.refreshAll();
    instance.Core.documentViewer.updateView();
};


// WebViewer section
// 
// This code initializes the WebViewer with the basic settings
// that are found in the default showcase WebViewer
//

const searchParams = new URLSearchParams(window.location.search);
const history = window.history || window.parent.history || window.top.history;
const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';
const element = document.getElementById('viewer');

WebViewer({
    path: '/lib',
    licenseKey: licenseKey,
}, element).then((instance) => {
    // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
    const cloudyTools = [
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
    ];
    instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
    instance.UI.disableTools(cloudyTools);

    // Set default toolbar group to Annotate
    instance.UI.setToolbarGroup('toolbarGroup-Annotate');

    // Set default tool on mobile devices to Pan.
    // https://apryse.atlassian.net/browse/WVR-3134
    if (isMobileDevice()) {
        instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
    }

    instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
        if (searchParams.has('file')) {
            searchParams.delete('file');
            history.replaceState(null, '', '?' + searchParams.toString());
        }
    });

    instance.Core.annotationManager.enableAnnotationNumbering();

    instance.UI.NotesPanel.enableAttachmentPreview();

    // Add the demo-specific functionality
    customizeUI(instance).then(() => {
        // Create UI controls after demo is initialized
        createUIControls(instance);
    });
});

// Function to check if the user is on a mobile device
const isMobileDevice = () => {
    return (
        /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
            window.navigator.userAgent
        ) ||
        /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
            window.navigator.userAgent.substring(0, 4)
        )
    );
}

// Cleanup function for when the demo is closed or page is unloaded
const cleanup = (instance) => {
    if (typeof instance !== 'undefined' &#x26;&#x26; instance.UI) {
        // Clean up any resources if needed
        console.log('Cleaning up complex-generation demo');
    }
};

// Register cleanup for page unload
window.addEventListener('beforeunload', () => cleanup(instance));
window.addEventListener('unload', () => cleanup(instance));

// UI section
//
// Helper code to add controls to the viewer holding the buttons
// This code creates a container for the buttons, styles them, and adds them to the viewer
//

// Watermark text input
const watermarkTextSection = (instance) => {
    const section = document.createElement('div');
    section.className = 'watermark-Text-Section';

    const label = document.createElement('label');
    label.textContent = 'Text';

    section.appendChild(label);

    const input = document.createElement('input');
    input.className = 'watermark-text-input';
    input.type = 'text';
    input.value = text;

    input.addEventListener('input', (e) => {
        text = e.target.value;
        drawWatermark(instance);
    });

    section.appendChild(input);

    return section;
};

// Font Size slider
const fontSizeSlider = (instance) => {
    const section = document.createElement('div');
    section.className = 'font-size-slider-section';

    const label = document.createElement('label');
    label.textContent = 'Font Size';

    section.appendChild(label);

    const slider = document.createElement('input');
    slider.className = 'font-size-slider';
    slider.type = 'range';
    slider.defaultValue = '48';
    slider.value = '48';
    slider.min = '14';
    slider.max = '200';
    slider.step = '1';

    slider.addEventListener('input', (e) => {
        size = parseInt(e.target.value, 10);
        drawWatermark(instance);
    });

    section.appendChild(slider);


    return section;
};

// Opacity slider
const opacitySlider = (instance) => {
    const section = document.createElement('div');
    section.className = 'opacity-slider-section';

    const label = document.createElement('label');
    label.textContent = 'Opacity';

    section.appendChild(label);

    const slider = document.createElement('input');
    slider.className = 'font-size-slider';
    slider.type = 'range';
    slider.defaultValue = '48';
    slider.value = '48';
    slider.min = '0';
    slider.max = '100';
    slider.step = '1';

    slider.addEventListener('input', (e) => {
        opacity = parseInt(e.target.value, 10);
        drawWatermark(instance);
    });


    section.appendChild(slider);

    return section;
};

// Color picker
const colorPicker = (instance) => {
    const wrapper = document.createElement('div');

    const colorDisplay = document.createElement('div');
    colorDisplay.className = 'color-display';

    const colorDisplayLabel = document.createElement('p');
    colorDisplayLabel.className = 'color-display-label';
    colorDisplayLabel.textContent = 'Color:';

    const colorDisplayInput = document.createElement('input');
    colorDisplayInput.className = 'color-display-input';
    colorDisplayInput.type = 'text';
    colorDisplayInput.readOnly = true;

    colorDisplayInput.addEventListener('input', (e) => {
        color = e.target.value;
        drawWatermark(instance);
    });

    colorDisplay.appendChild(colorDisplayLabel);
    colorDisplay.appendChild(colorDisplayInput);

    wrapper.appendChild(colorDisplay);

    const checkboxContainer = document.createElement('div');
    checkboxContainer.className = 'checkbox-container';

    DEFAULT_COLORS.forEach((default_color) => {
        // Actual radio/checkbox input
        const checkbox = document.createElement('input');
        checkbox.className = 'checkbox-input';
        checkbox.name = 'watermark-color';
        checkbox.id = `checkbox-watermark-color-${default_color.replace('#', '')}`;
        checkbox.type = 'radio';
        checkbox.value = default_color;

        if (default_color === color) {
            checkbox.checked = true;
            colorDisplayInput.value = `${default_color}`;
        }

        checkbox.onchange = (e) => {
            if (e.target.checked) {
                colorDisplayInput.value = `${e.target.value}`;
                colorDisplayInput.dispatchEvent(new Event('input'));
            }
        };

        // Hide to use custom checkbox instead
        checkbox.style.display = 'none'; 

        checkboxContainer.appendChild(checkbox);

        // Custom checkbox
        const span = document.createElement('span');
        span.className = 'checkbox-checkmark';
        span.id = `checkmark-watermark-color-${default_color.replace('#', '')}`;

        if (checkbox.checked) {
            span.textContent = '✓'; // U+2713
        }

        span.onclick = () => {
            
            // Update all checkmarks of the same group
            document.querySelectorAll(`input[name=watermark-color]`).forEach((cb) => {
                const checkmark = document.getElementById(`checkmark-watermark-color-${cb.value.replace('#', '')}`);
                if (checkmark) checkmark.textContent = '';
            });
            document.getElementById(`checkmark-watermark-color-${default_color.replace('#', '')}`).textContent = '✓'; // U+2713

            checkbox.checked = true;
            checkbox.onchange({ target: checkbox });
        };

        // Set each checkbox's color to a color in the default colors array
        span.style.backgroundColor = default_color;
        span.style.border = `3px solid ${default_color}`;

        checkboxContainer.appendChild(span);
    });

    wrapper.appendChild(checkboxContainer);

    return wrapper;
};


// Timestamp checkbox
const timestampCheckbox = (instance) => {
    const section = document.createElement('div');
    section.className = 'timestamp-checkbox-section';

    const checkbox = document.createElement('input');
    checkbox.className = 'timestamp-checkbox';
    checkbox.type = 'checkbox';
    checkbox.checked = showTimestamp;

    checkbox.onchange = (e) => {
        showTimestamp = e.target.checked;
        drawWatermark(instance);
    };

    checkbox.onchange = (e) => {
        showTimestamp = e.target.checked;
        drawWatermark(instance);
    };

    section.appendChild(checkbox);

    const label = document.createElement('label');
    label.className = 'timestamp-label';
    label.textContent = 'Show timestamp';

    section.appendChild(label);

    return section;
};

const createUIControls = (instance) => {
    // Create a container for all controls (label, dropdown, and buttons)
    const controlsContainer = document.createElement('div');
    controlsContainer.className = 'controls-container';

    controlsContainer.appendChild(watermarkTextSection(instance));
    controlsContainer.appendChild(fontSizeSlider(instance));
    controlsContainer.appendChild(opacitySlider(instance));
    controlsContainer.appendChild(colorPicker(instance));
    controlsContainer.appendChild(timestampCheckbox(instance));

    // Add the controls container to the viewer
    element.insertBefore(controlsContainer, element.firstChild);
};
</code></pre>

{% endtab %}

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

```css
/* CSS Standards Compliant Syntax */
/* GitHub Copilot v1.0, GPT-4.1, September 22, 2025 */
/* File: index.css */

.picker-title {
    font-weight: 900;
    font-size: 14px;
    line-height: 125%;
    color: #334250;
    letter-spacing: 0;
    padding-bottom: 10px;
    display: flex;
    justify-content: space-between;
}

.watermark-Text-Section label,
.font-size-slider-section label,
.opacity-slider-section label {
  font-weight: 900;
  font-size: 14px;
  line-height: 125%;
  color: #334250;
  letter-spacing: 0;
  padding-bottom: 10px;
  display: flex;
  justify-content: space-between;
}

.color-display {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.color-display-label {
    font-size: 14px;
    line-height: 20px;
    color: #485056;
    letter-spacing: -0.3px;
    margin-right: 5px;
    font-weight: 700;
}

.color-display-input {
    width: 100%;
    min-width: 0px;
    outline: 2px solid transparent;
    outline-offset: 2px;
    transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
    transition-duration: 200ms;
    border-radius: 6px;
    background-color: #FFFFFF;
    color: #485056;
    border: 1px solid;
    box-sizing: border-box;
    font-size: 14px;
    padding-inline-start: 16px;
    padding-inline-end: 16px;
    height: 40px;
}

.watermark-text-input {
  width: 100%;
  min-width: 0px;
  outline: 2px solid transparent;
  outline-offset: 2px;
  position: relative;
  appearance: none;
  transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
  transition-duration: 200ms;
  border-radius: 6px;
  background-color: #FFFFFF;
  color: #485056;
  border: 1px solid;
  box-sizing: border-box;
  font-size: 14px;
  padding-inline-start: 16px;
  padding-inline-end: 16px;
  height: 40px;
}

.font-size-slider-section,
.opacity-slider-section {
  display: flex;
  flex-direction: column;
}

.font-size-slider,
.opacity-slider-section input[type="range"] {
  width: 100%;
}

.checkbox-container {
  display: flex;
  flex-wrap: wrap;
}

.checkbox-checkmark {
    border-radius: 6px;
    width: 24px;
    height: 24px;
    margin: 3px;
    cursor: pointer;
    text-align: center;
    line-height: 18px;
    display: inline-block;
    /* The following will be set dynamically in JS:
     background-color, color, border */
}

.timestamp-checkbox-section {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  vertical-align: top;
  position: relative;
  padding-bottom: 10px;
}

.timestamp-checkbox {
  fill: none;
  stroke-width: 2;
  stroke: currentColor;
  stroke-dasharray: 16;
  opacity: 1;
  stroke-dashoffset: 0;
  font-size: 10px;
  transition-property: transform;
  transition-duration: 200ms;
}

.timestamp-label {
  font-size: 14px;
  line-height: 20px;
  color: #485056;
  letter-spacing: -0.3px;
  font-weight: 700;
  margin-inline-start: 8px;
}

.controls-container {
    display: flex;
    flex-direction: row;
    gap: 20px;
}
```

{% 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-watermark.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.
