Title: Niquelao Watermark
Author: Koke Pérez
Published: <strong>July 3, 2026</strong>
Last modified: July 3, 2026

---

Search plugins

![](https://ps.w.org/niquelao-watermark/assets/banner-772x250.png?rev=3595188)

![](https://ps.w.org/niquelao-watermark/assets/icon-256x256.png?rev=3595188)

# Niquelao Watermark

 By [Koke Pérez](https://profiles.wordpress.org/kokenet/)

[Download](https://downloads.wordpress.org/plugin/niquelao-watermark.1.5.4.zip)

 * [Details](https://wordpress.org/plugins/niquelao-watermark/#description)
 * [Reviews](https://wordpress.org/plugins/niquelao-watermark/#reviews)
 *  [Installation](https://wordpress.org/plugins/niquelao-watermark/#installation)
 * [Development](https://wordpress.org/plugins/niquelao-watermark/#developers)

 [Support](https://wordpress.org/support/plugin/niquelao-watermark/)

## Description

Niquelao Watermark is a standalone plugin (it does not require any other Niquelao
plugin) that protects your images with a watermark generated on the fly, without
ever touching the original files on disk.

The page itself always shows the clean, original image, so image SEO (descriptive
file name, indexing) stays intact. Only when a visitor right-clicks an image and
chooses “Open image in new tab” or “Save image as” — or drags the image out — do
they get a copy with your logo overlaid. That watermarked copy is generated once
and then served from an on-disk cache on later requests.

**Features:**

 * One-switch activation from the settings screen
 * Logo picker through the WordPress media library (transparent PNG recommended)
 * Configurable position: center, the 4 corners, or a repeating tile
 * Adjustable opacity and size (% of the image width) via sliders
 * Works with JPEG, PNG and WebP
 * The original file on disk is never modified
 * On-disk cache of already-watermarked images, regenerated automatically when any
   setting changes
 * No external dependencies: uses Imagick (preferred) or GD (fallback), available
   on virtually every shared host
 * Discreet “-hd” URL for the watermarked version, so the presentation URL stays
   clean for SEO and the mechanism is not obvious

### How it works

Since version 1.2.0 the approach is “watermark only on save/open”, using two URLs:

 1. PRESENTATION (what the site and Google see): the image is loaded by its
     real, 
    clean URL (e.g. `.../uploads/2026/06/photo.jpg`). That URL is NOT rewritten: the
    original file is served without a watermark, so image SEO (descriptive file name,
    indexing) stays intact.
 2. SAVE / OPEN: a frontend script (`assets/js/niquelao-wm-front.js`)
     intercepts the
    right-click and the drag, pointing to a variant with the -hd suffix before the 
    extension (e.g. `.../photo-hd.jpg`). That URL does not exist on disk; `.htaccess`
    recognizes it, strips the `-hd`, and passes the REAL file path to PHP, which returns
    the watermarked version (generated once and cached). So “Save image as” and “Open
    image in new tab” deliver the watermarked version, while the page stays clean and
    the discreet `-hd` URL does not reveal the mechanism.

The script does NOT modify the `src`/`srcset` of the page’s `<img>` (that
 would
break the WordPress lightbox): for right-click it overlays a transparent watermarked
clone, and for drag it only alters the dataTransfer.

Honest note: this is deterrence, not absolute protection. A user with the
 browser
developer tools (Network tab) can still see the real, unwatermarked image URL. The
goal is to make casual downloading harder (right-click / drag / copy link), not 
to make it impossible.

### Requirements

 * WordPress 5.8 o superior
 * PHP 7.4 o superior
 * Imagick or GD enabled on the server
 * Apache mod_rewrite (or equivalent) for the `.htaccess` rewrite

## Screenshots

[⌊Settings — enable watermark, select logo, position, opacity and size⌉⌊Settings—
enable watermark, select logo, position, opacity and size⌉[

Settings — enable watermark, select logo, position, opacity and size

## Installation

 1. Upload the `niquelao-watermark/` folder to `/wp-content/plugins/`
 2. Activate the plugin from **Plugins  Installed Plugins**
 3. Configure it under **Niquelao Watermark  Settings**: enable the watermark, upload
    your logo, choose position, opacity and size, then save

#### Nginx servers (no Apache/.htaccess)

On Apache or LiteSpeed hosts there is nothing to do: the plugin writes the rule 
block into `.htaccess` automatically when the watermark is enabled. On Nginx servers(
which do not read `.htaccess`) add this equivalent rule once, by hand, inside the
site’s `server { ... }` block (usually in /etc/nginx/sites-available/your-site.conf)
and reload Nginx with sudo nginx -t && sudo systemctl reload nginx:

    ```
    # Niquelao Watermark: rewrites "-hd" URLs to the watermarked version.
    # The real, clean image URL is left untouched (served normally).
    location ~* ^/wp-content/uploads/(.+)-hd\.(jpe?g|png|webp)$ {
        rewrite ^/wp-content/uploads/(.+)-hd\.(jpe?g|png|webp)$ /index.php?niquelao_wm_file=wp-content/uploads/$1.$2 last;
    }
    ```

If your uploads folder is not the standard `wp-content/uploads`, adjust that path
in both places. The rest of the flow (detecting the real file, generating and caching
the watermark) is handled by PHP just like on Apache.

## FAQ

### Does it modify my original images?

No. The original file in `wp-content/uploads` is never touched. The watermarked 
image is generated in a separate cache folder.

### Does the watermark show up inside my normal web page?

No. Since version 1.2.0 the page always displays the clean original image. The watermarked
version is only delivered when a visitor right-clicks and chooses “Save image as”/“
Open image in new tab”, or drags the image out. This keeps your design clean and
image SEO intact.

### Is this real protection?

It is deterrence, not absolute protection. A technical user with the browser developer
tools (Network tab) can still find the clean image URL. The goal is to discourage
casual downloading (right-click, drag, copy link), not to make it impossible.

### Does it work without Niquelao Image Optimizer installed?

Yes. It is a completely standalone plugin, with no dependency between the two.

### What happens if I change the logo or position?

The cache is invalidated automatically: the next time the image is requested it 
is regenerated with the new settings. You can also clear the cache manually from
the settings screen.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Niquelao Watermark” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ Koke Pérez ](https://profiles.wordpress.org/kokenet/)

[Translate “Niquelao Watermark” into your language.](https://translate.wordpress.org/projects/wp-plugins/niquelao-watermark)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/niquelao-watermark/),
check out the [SVN repository](https://plugins.svn.wordpress.org/niquelao-watermark/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/niquelao-watermark/)
by [RSS](https://plugins.trac.wordpress.org/log/niquelao-watermark/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.5.4

 * i18n fix: 5 error-log strings inside Niquelao_WM_Htaccess were still hardcoded
   in Spanish, bypassing the translation system entirely (they were missing from
   the .pot/.po files). They are now proper English source strings wrapped in `__()`,
   with the original Spanish wording preserved as the es_ES translation. languages/
   niquelao-watermark.pot and niquelao-watermark-es_ES.po/.mo were regenerated to
   include all 39 translatable strings (previously 8 were missing, including 3 English
   ones that had simply never been added to the .pot after being written).
 * Hardening (WordPress.org review readiness): replaced direct file_get_contents()/
   file_put_contents() calls in Niquelao_WM_Htaccess (writing/removing the .htaccess
   block) and the NIQUELAO_WM_DEBUG diagnostic logger in Niquelao_WM_Serve with 
   the WP_Filesystem API (get_contents()/put_contents()), removing the corresponding
   phpcs:ignore waivers. insert_with_markers() is still not used for the .htaccess
   block itself, since it always appends and this plugin needs the block at the 
   top of the file; WP_Filesystem gives the same manual position control without
   bypassing core’s filesystem abstraction. No functional change on standard installs.

#### 1.5.3

 * WordPress.org review compliance: (1) uninstall.php no longer directly includes
   wp-admin/includes/misc.php — the Niquelao_WM_Htaccess class already loads the
   required core files (misc.php + file.php) on demand with require_once and uses
   their functions immediately after, which is the allowed pattern. (2) The uploads
   path used to build the .htaccess rewrite rules (root and uploads/) and to normalise
   the requested path in the serve endpoint is now derived from wp_upload_dir()[‘
   baseurl’] via wp_parse_url(), through the new reusable Niquelao_WM_Htaccess::
   uploads_url_path() helper, instead of str_replace( ABSPATH, ”, wp_upload_dir()[‘
   basedir’] ). This works correctly with custom WP_CONTENT_DIR or external uploads
   locations, where the uploads directory is not inside the WordPress root. No functional
   change on standard installs.

#### 1.5.2

 * Hardening (WordPress.org review readiness): added an explicit current_user_can(‘
   manage_options’ ) check at the top of save_settings(), maybe_purge_cache() and
   maybe_fix_uploads_htaccess() as defence in depth, since those methods process
   $_POST directly (previously they relied only on the menu capability). Added the
   License URI header to the main plugin file (it was only in readme.txt). Shortened
   the plugin-header Description to a single concise line.

#### 1.5.1

 * Improved: watermark size now uses an attenuated scale instead of a plain linear
   percentage of the image width. Previously the logo looked too small on large 
   images and too big on small ones, because the final visual size depends on the
   resolution the image is displayed at, not on its real pixels. The size percentage
   is now corrected by the square root of the image-to-reference (1200px) ratio,
   clamped to [0.5x, 2.0x], so the watermark keeps a consistent visual size across
   images of very different dimensions. Applies to both the Imagick and GD render
   paths.

#### 1.5.0

 * Plugin Check hardening: removed a misplaced phpcs:ignore on the can_write() method
   signature (it silenced nothing), and expanded the inline justifications on all
   direct filesystem calls in the dual-.htaccess logic explaining why insert_with_markers()
   cannot be used (block-position control at the top of the file, and writing into
   a subdirectory .htaccess). No functional change.

#### 1.4.9

 * Fixed: uninstall.php now always calls disable() unconditionally (instead of only
   when is_installed() was true for the root .htaccess). This ensures the rewrite
   block is removed from BOTH the root .htaccess AND wp-content/uploads/.htaccess,
   even when the plugin was deactivated before being deleted (which had already 
   cleaned the root but left the uploads block orphaned). Also loads insert_with_markers()
   explicitly to avoid a fatal error in the uninstall context.

#### 1.4.8

 * Fixed: on plugin update/reactivation, niquelao_wm_activate() called sync() which
   only acted on state changes and did nothing if the watermark was already active.
   It now calls enable() directly when the watermark is active, so the .htaccess
   block (root + uploads/ if present) is always regenerated with the latest rules
   immediately after updating the plugin — no manual save needed.

#### 1.4.7

 * Fixed: the .htaccess block was not regenerated when saving settings if the watermark
   was already active (sync() only acted on state changes). Settings save now always
   calls enable()/disable() directly, so the block is always rewritten with the 
   latest rules after a plugin update — no need to deactivate/reactivate manually.

#### 1.4.6

 * Added: compatibility warning in the settings page when another plugin (WP-Optimize,
   Wordfence, iThemes Security, etc.) has created a .htaccess inside wp-content/
   uploads/ without the Niquelao Watermark rewrite rule. The notice includes a one-
   click “Fix automatically” button that writes the correct rule into that file 
   immediately, without requiring the user to save settings manually.
 * Improved: sync() now always rewrites the uploads/.htaccess block when the watermark
   is active, ensuring the rule stays up to date even if the file was modified by
   another plugin after activation.

#### 1.4.5

 * Fixed: dual .htaccess strategy. When plugins such as WP-Optimize, iThemes Security
   or Wordfence create their own .htaccess inside wp-content/uploads/, Apache opens
   a new directory context for image requests and stops applying the root .htaccess
   rules entirely. The plugin now detects that file and writes the rewrite rule 
   into it as well (using directory-relative paths and the correct RewriteBase for
   that subdirectory). The uploads/.htaccess block is added on enable(), removed
   on disable(), and kept in sync on every sync() call. The root .htaccess is still
   written as before for servers without an uploads/.htaccess.

#### 1.4.4

 * Fixed: added missing RewriteBase / to the .htaccess block. On some Apache servers
   without an explicit RewriteBase the RewriteRule path was not resolved correctly,
   causing the “-hd” URLs to return a 404 Apache error instead of being passed to
   PHP. The fix regenerates the .htaccess block automatically on the next Settings
   save (or plugin reactivation).

#### 1.4.3

 * Fixed: the cleanup listeners were registered via setTimeout(…, 0), which in many
   browsers fired fast enough to destroy the clone before “contextmenu” could read
   it — resulting in the browser showing the unwatermarked URL in the context menu.
   The cleanup now registers only AFTER “contextmenu” fires (one-shot listener),
   so the clone is guaranteed to be alive when the browser builds “Save image as”/“
   Open image in new tab”.

#### 1.4.2

 * Fixed (critical): the marked clone was inserted on “contextmenu”, which fires
   AFTER the browser has already decided which image to show in the context menu.
   Moved the clone insertion to “mousedown” (right button), which fires before “
   contextmenu”, so the browser sees the clone — pointing to the watermarked “-hd”
   URL — when it builds the “Save image as” / “Open image in new tab” menu entries.

#### 1.4.1

 * WordPress.org compliance: replaced base64_encode() with rawurlencode() for the
   admin menu SVG icon to avoid Plugin Check warnings. Removed emoji from the admin
   page title for accessibility and dashboard consistency. Added a 100 KB rotation
   limit to the debug log file to prevent unbounded growth if NIQUELAO_WM_DEBUG 
   is accidentally left enabled in production.

#### 1.4.0

 * WordPress.org readiness: the whole readme.txt (technical section and full changelog)
   and the plugin header Description are now in English. Admin UI strings are now
   English as the source language, with Spanish shipped as a translation in /languages(
   niquelao-watermark-es_ES.po/.mo) plus a base .pot. uninstall.php now reuses Niquelao_WM_Htaccess::
   disable() instead of calling insert_with_markers() directly, so a single routine
   owns the .htaccess block. Bundled translations are loaded via the load_textdomain_mofile
   filter (no discouraged load_plugin_textdomain() call) so the shipped es_ES translation
   works on self-hosted installs while keeping Plugin Check clean. Short description
   trimmed to under 150 characters.

#### 1.3.0

 * Plugin Check compliance: readme description and FAQ translated to English (required
   for the .org repository); cache deletion in uninstall.php and purge_cache() now
   uses WP_Filesystem instead of direct rmdir(); global variables in uninstall.php
   are prefixed; readfile() kept (correct for streaming binaries) with a documented
   phpcs waiver.

#### 1.2.2

 * Hardening (Plugin Check): the request input ($_GET / $_SERVER) is captured once
   with wp_unslash() + sanitize_text_field() before use. Behavior is unchanged (
   accented characters are preserved) and the unsanitized-input warnings are silenced.
   The endpoint is documented as a public read-only image endpoint (no nonce).

#### 1.2.1

 * Fixed (important for non-ASCII file names): images whose file name contained 
   accents, ñ, spaces or other non-ASCII characters (e.g. “sesion-Malaga.jpg”) returned
   a 404 on save/open. The path sanitizer used an ASCII-only pattern that rejected
   those names. The path is now decoded (rawurldecode) and validated as UTF-8, accepting
   any character valid in real file names while still blocking path traversal (“..”).

#### 1.2.0

 * New approach: the watermark is no longer shown on the page. The page ALWAYS presents
   the clean original image (image SEO intact). The watermarked version is only 
   delivered when the visitor right-clicks -> “Save/Open image” or drags the image.
 * Discreet URL: the watermarked version is served from a URL with the “-hd” suffix
   before the extension (e.g. photo-hd.jpg) instead of the ?niquelao_wm=1 parameter,
   so the mechanism is not obvious. The .htaccess rule recognizes “-hd”, strips 
   it, and serves the real file watermarked.
 * New: assets/js/niquelao-wm-front.js is enqueued on the frontend (only when the
   watermark is enabled). It intercepts right-click (overlaying a watermarked clone,
   without touching the real ) and drag (dataTransfer), in a NON-intrusive way so
   it does not break the WordPress lightbox.
 * Fixed: double concatenation of wp-content/uploads/ in the served path, which 
   made all images disappear (file_exists failed).
 * Compatibility: added an equivalent Nginx rule in the readme (Installation section)
   for hosts without Apache/mod_rewrite.

#### 1.0.3

 * Fixed (root cause of the persistent 404): on the target LiteSpeed server, `%{
   REQUEST_FILENAME}` does not resolve to the absolute filesystem path, so the `
   RewriteCond %{REQUEST_FILENAME} -f` condition was never true — neither in this
   rule nor in the standard WordPress rule (`!-f`) — which made WordPress also rewrite
   images to `/index.php` with no recognizable parameter, showing the theme’s 404
   page. Changes: (1) the rule no longer depends on `-f`, only on the `%{REQUEST_URI}`
   pattern; (2) the block is now inserted at the START of `.htaccess`, before the
   WordPress rule, so it is evaluated first.

#### 1.0.2

 * Added: temporary diagnostic log (disabled by default). Enable with `define( '
   NIQUELAO_WM_DEBUG', true );` in `wp-config.php` to record each decision step 
   into `wp-content/uploads/niquelao-wm-debug.log` while serving an image, to pinpoint
   the exact cause of a 404.

#### 1.0.1

 * Fixed: images returned a 404 instead of being served. The entry point was hooked
   on `init`, which runs after WordPress has already tried to resolve the rewritten
   URL as a normal query (post/page) and marked it as 404. Moved to `plugins_loaded`,
   which runs before WordPress parses the URL but with all required functions (`
   wp_upload_dir()`, `get_option()`, etc.) already available.

#### 1.0.0

 * First release: on-the-fly watermark via `.htaccess` rewrite, without modifying
   original files
 * JPEG, PNG and WebP support
 * Positions: center, 4 corners, tile
 * Configurable opacity and size
 * On-disk cache with automatic invalidation when settings change
 * Compatible with Imagick and GD

## Meta

 *  Version **1.5.4**
 *  Last updated **22 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 5.8 or higher **
 *  Tested up to **7.0**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [copyright](https://wordpress.org/plugins/tags/copyright/)[image protection](https://wordpress.org/plugins/tags/image-protection/)
   [imagick](https://wordpress.org/plugins/tags/imagick/)[performance](https://wordpress.org/plugins/tags/performance/)
   [watermark](https://wordpress.org/plugins/tags/watermark/)
 *  [Advanced View](https://wordpress.org/plugins/niquelao-watermark/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/niquelao-watermark/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/niquelao-watermark/reviews/)

## Contributors

 *   [ Koke Pérez ](https://profiles.wordpress.org/kokenet/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/niquelao-watermark/)