# watermark-js-plus: adding visible and blind watermarks in the browser

> watermark-js-plus is a canvas-based TypeScript library that puts a visible watermark over a page or container, burns one into an image, or hides a low-opacity blind watermark in a picture, with a decoder and DOM tamper recovery. It suits frontend teams that need to discourage screenshotting and copying of their own interface, without a backend service.

**zhensherlock/watermark-js-plus** — Lightweight, framework-agnostic browser watermark library with text, image, rich-text and blind watermarks, decoding, grid layouts, and tamper protection.

- Repository: https://github.com/zhensherlock/watermark-js-plus
- Website: https://zhensherlock.github.io/watermark-js-plus/
- Stars: 593 · Forks: 39
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/zhensherlock-watermark-js-plus

## A canvas library for watermarking that lives in the browser

watermark-js-plus is a canvas-based TypeScript library for adding watermarks in the browser. The README describes it as lightweight and framework-agnostic, and the important word in that claim is agnostic: it works with vanilla JavaScript and with frontend frameworks without requiring a framework-specific adapter.

That is a real design decision in a category where most libraries ship a React wrapper, a Vue wrapper and a core. A wrapper-free library means the same code runs in an Angular app, a Svelte app, a server-rendered page and a static HTML file, with no peer dependency and no version matrix to track against your framework.

The use cases the README names are three: protecting a page or container with a visible watermark, rendering a watermark directly into an existing image, and creating and revealing low-opacity blind watermarks. Around those sit runtime updates, grid layouts, rich styling and automatic recovery from common DOM tampering.

The audience, then, is a frontend developer or team who wants deterrence rather than cryptography. A dashboard that shows customer data can be stamped with the logged-in user's name, so a screenshot carries an identifier. A report preview can be stamped confidential. A chart image can be marked before it is embedded. The library does the drawing; the decision about what to write on the image, and whether to include a user id, belongs to the application.

The package is published on npm as watermark-js-plus at version 1.6.6, licensed MIT, written in TypeScript, and the last push to the repository was on 2026-10-20, with releases v1.6.6 on 2026-07-14, v1.6.5 on 2026-07-06 and v1.6.4 on 2026-06-24.

## Three exports, three different jobs

The README puts the choice of API in a table, and the three rows are worth reading closely because they solve genuinely different problems.

`Watermark` adds a watermark over the page or a specific container. This is the classic case, a tiled or positioned overlay drawn on a canvas above your content, and it is what most people want first.

`ImageWatermark` renders a text or an image watermark into an existing `<img>` element. This is not an overlay on top of the page, it is a modification of the picture itself, which matters when the image is later exported, downloaded or re-rendered elsewhere.

`BlindWatermark` creates a low-opacity watermark and reveals it from a captured image. This is the interesting one, because the mark is embedded into the pixel data rather than drawn on top, so it survives a crop, a screenshot of the canvas, or a re-encoding of the image, and it is only visible when you decode it with the right information.

The difference between the first two and the third is the difference between deterrence and provenance. A visible watermark is a label; anyone with developer tools can delete the element. A blind watermark is embedded in the pixels, so removing it means modifying the image data, which is a different problem with different costs.

The library also supports rich content, meaning text, multi-line text, images and rich text as the watermark body, and flexible layouts, with default and configurable grid layouts and precise positioning. Styling covers opacity, rotation, typography, shadows, gradients, patterns, filters and animation, which is a list that says the canvas is doing the rendering work rather than the browser compositing plain text.

## Quick start: content, size, rotation and opacity

Installation is a normal npm dependency, and the README gives all three package managers, so there is no framework-specific setup step.

```bash
npm install watermark-js-plus
```

```bash
pnpm add watermark-js-plus
```

```bash
yarn add watermark-js-plus
```

The quick start then creates a watermark with four options and one method call.

```ts
import { Watermark } from 'watermark-js-plus'

const watermark = new Watermark({
  content: 'Confidential',
  width: 220,
  height: 160,
  rotate: 22,
  globalAlpha: 0.15,
})

await watermark.create()
```

The option names are self-describing. `content` is the text or rich content to draw, `width` and `height` are the size of the watermark tile, `rotate` is the angle in degrees, and `globalAlpha` is the opacity, set low here at 0.15 so the mark does not obscure the content underneath.

Two details are worth noticing. The `create` method is awaited, so the API is asynchronous even though the drawing itself is synchronous canvas work, and it returns a promise rather than nothing. And the instance is kept in a variable, which is not incidental, because the lifecycle methods on the next section all operate on that instance.

The `width` and `height` values are the size of a single watermark element, which is the tile unit. If you are after a repeated diagonal grid across the container, that is what the grid layout feature is for, and the two values are what define the spacing of the repetition.

## Scoping to a container and the lifecycle methods

A page-wide watermark is rarely what an application wants, so the library takes a `parent` option, which accepts a selector or an element.

```ts
const watermark = new Watermark({
  parent: '#report',
  content: 'Internal use only',
})

await watermark.create()
```

The README states one requirement for the target: it must establish a positioning context, for example with `position: relative`.

```css
#report {
  position: relative;
}
```

That is the kind of detail that separates a library that has been used from one that has been described, and it is worth reading before the first integration because the symptom of a missing positioning context is a watermark that lands in the wrong place rather than an error message.

The lifecycle is explicit, and the README shows all three operations on the same instance.

```ts
await watermark.changeOptions({ content: 'Updated watermark' }, 'append')

const exists = await watermark.check()

watermark.destroy()
```

`changeOptions` takes new options and a mode, and the mode used here is `append`, which suggests the update can be additive rather than a full replacement of the rendered output. `check` returns whether the watermark is currently present, which is the primitive you need for a verification routine, and `destroy` removes it.

The `check` and `destroy` pair is the API that makes this usable in a real application. A page that shows a confidential report can stamp it on mount, remove it when the user navigates away, and verify on demand that the stamp is actually present rather than assuming it is.

## Blind watermarks and decoding a captured image

The blind watermark path is the one with a genuinely different failure model, and the README shows how decoding works.

The method is described as enhancing a blind watermark contained in an image and returning the processed image as a data URL.

```ts
import { BlindWatermark } from 'watermark-js-plus'

BlindWatermark.decode({
  url: '/images/blind-watermark.png',
  onSuccess: (imageBase64: string) => {
    const decodedImage = new Image()
    decodedImage.src = imageBase64
    decodedImage.alt = 'Decoded blind watermark'
    document.body.appendChild(decodedImage)
  },
})
```

The API is callback based, with a `url` to fetch and an `onSuccess` handler that receives the processed image as a base64 data URL, which the example then loads into an `Image` element and appends to the document.

The reason to embed rather than overlay is durability. A visible overlay is a DOM node that a user can delete, and a screenshot of it proves nothing once it is gone. A blind watermark lives in the pixel data, so a screenshot still carries it, and the question of whether a given image was produced by a given user becomes a question with a technical answer rather than a question about what a user did in developer tools.

The same section of the README notes that the ESM entry exports `BlindWatermark` and `ImageWatermark` alongside `Watermark`, and that with `movable: true` the stylesheet has to be imported separately.

```ts
import 'watermark-js-plus/style.css'
```

That is the one place where the library is not self-contained, and it is a small but real integration detail: a movable watermark is positioned with CSS, and the CSS lives in a separate file.

## Tamper recovery, bundle formats and a tree-shakeable entry

The feature list describes tamper recovery as optional `MutationObserver` monitoring that recreates modified or removed watermark nodes. That is the direct answer to the obvious objection to a DOM-based watermark, since removing an element is trivial, so the library watches for it and puts it back.

It is worth being precise about what that does and does not achieve. It raises the cost of casual removal and it restores the visible state of the page. It does not stop someone who reads the source, and it cannot survive a user who disables the observer before the library runs. It is deterrence with a little engineering behind it, not protection.

The delivery story is thorough, and this matters for a library that has to work in an existing app. The README lists ESM, CommonJS, UMD, IIFE, CDN and a dedicated legacy-browser entry, and the package manifest matches that with separate fields: `main` pointing at a CommonJS bundle, `module` and `webpack` and `browser` pointing at the ESM bundle, `unpkg` and `jsdelivr` pointing at the IIFE bundle, and a `types` entry for the type declarations. The exports map adds a Node import condition.

For bundler users there is an optimised entry.

```ts
import { Watermark } from 'watermark-js-plus/es'
```

The README says most applications should use the standard entry shown earlier, and that the preserved-module ESM entry helps a bundler that supports tree-shaking exclude unused modules.

The CDN section starts by asking you to pin the package version when loading from a CDN, which is standard advice and worth repeating, because an unpinned script tag on a page that displays confidential data is a supply chain question and not a style question.

The manifest also shows the project is built with Rollup, with Jest for tests, ESLint for linting, husky for commit hooks, commitlint for commit messages, and VitePress for the documentation site, and a renovate configuration for dependency updates. The CHANGELOG is generated with conventional-changelog, and the repository carries an `AGENTS.md`, a `CLAUDE.md` and a `design-qa.md`, which suggests the project keeps written instructions for automated contributors.

## Conclusion

watermark-js-plus is a good fit for a frontend team that wants to stamp a user identifier or a confidentiality label over a dashboard, a report view or a generated chart, and that prefers a dependency with no server component and no framework adapter. It is the wrong choice for a requirement that is real protection of an asset, since a watermark rendered in the DOM lives on a machine the user controls, and it is the wrong choice for a Node backend that has to watermark files at rest. Verify first which of the three APIs your case needs, read the blind watermark encoding requirements before you rely on decoding, and pin the npm version if you load it from a CDN, since the README asks you to pin the version.

## FAQ

### How do I install watermark-js-plus?

It is a normal npm package, installed with `npm install watermark-js-plus`, or with `pnpm add watermark-js-plus` or `yarn add watermark-js-plus`. The library ships ESM, CommonJS, UMD, IIFE, CDN and legacy browser entry points, and complete TypeScript declarations.

### What is the difference between Watermark, ImageWatermark and BlindWatermark?

Watermark adds a visible overlay to the page or a container, ImageWatermark renders a text or image watermark into an existing img element, and BlindWatermark creates a low-opacity watermark embedded in the image that can later be decoded from a captured copy.

### How do I scope a watermark to one container?

Pass the `parent` option with a selector or an element, for example `parent: '#report'`. The target container must establish a positioning context, which the README suggests doing with `position: relative` in CSS.

### How do I update or remove a watermark at runtime?

The instance exposes lifecycle methods: `changeOptions` with new options and a mode, `check` to test whether the watermark is present, and `destroy` to remove it. The quick start shows `await watermark.changeOptions({ content: 'Updated watermark' }, 'append')`.

### How does watermark-js-plus handle someone deleting the watermark?

Optional MutationObserver monitoring watches the DOM and recreates modified or removed watermark nodes. The README describes this as tamper recovery, and it is an obstacle to casual removal rather than a guarantee.

### Can watermark-js-plus be used with a frontend framework?

Yes, it is described as framework-agnostic, working with vanilla JavaScript and frontend frameworks without requiring a framework-specific adapter.

## Sources

- [License: MIT](https://github.com/zhensherlock/watermark-js-plus/blob/main/LICENSE)
- [Project website](https://zhensherlock.github.io/watermark-js-plus/)
- [README](https://github.com/zhensherlock/watermark-js-plus/blob/main/README.md)
- [Releases](https://github.com/zhensherlock/watermark-js-plus/releases)
- [zhensherlock/watermark-js-plus on GitHub](https://github.com/zhensherlock/watermark-js-plus)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zhensherlock-watermark-js-plus
