PHPackages                             justinholtweb/craft-waterfall - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. justinholtweb/craft-waterfall

ActiveCraft-plugin

justinholtweb/craft-waterfall
=============================

Watermarking for Craft CMS — image and text watermarks applied to transforms, to originals with backups, or at your CDN, with Imager-X and ImageOptimize integration.

10PHP

Since Aug 23Pushed today1 watchersCompare

[ Source](https://github.com/justinholtweb/craft-waterfall)[ Packagist](https://packagist.org/packages/justinholtweb/craft-waterfall)[ RSS](/packages/justinholtweb-craft-waterfall/feed)WikiDiscussions main Synced today

READMEChangelog (1)DependenciesVersions (1)Used By (0)

Waterfall
=========

[](#waterfall)

Watermarking for Craft CMS 5.

Put a mark on your images — automatically, everywhere, and reversibly. Waterfall is the Craft answer to the WordPress plugin *Easy Watermark*: image and text watermarks applied to generated transforms, burned into the original file with a restorable backup, produced on demand from Twig, or built at your CDN by imgix, Cloudinary or ImageKit.

- **Craft CMS 5.3+**, PHP 8.2+
- **Lite $59** · **Pro $79**

Why
---

[](#why)

Four problems, and Waterfall exists for all four.

**A watermark applied by hand is applied inconsistently.** Somebody forgets, somebody uses the wrong corner, somebody uploads the unmarked original next Tuesday.

**A watermark burned into the original cannot be taken back.** Waterfall backs the original up before it writes anything, and will not write if the backup fails. Restoring is one click, one element action, or one console command — in Lite as well as Pro.

**The mark has to survive the image pipeline.** Craft's transforms, ImageOptimize's `.webp` and `.avif` variants and Imager-X's output are all *new files made from the source*, so a mark that lives only in the source file disappears from every derivative that gets regenerated, and one that lives only in a transform is missing from the file people download. Waterfall covers both.

**Some sites do not process images at all** — the CDN does. So the CDN is a driver, not a different plugin.

Install
-------

[](#install)

```
composer require justinholtweb/craft-waterfall
php craft plugin/install waterfall
```

A profile
---------

[](#a-profile)

A **profile** is one watermark: what it is, how big, where it goes, how see-through, and which images get it. Profiles live in project config, so they deploy with the site.

**Type**An image (any asset — a PNG with transparency is best) or, on Pro, text**Size**A percentage of the base image's width or height, a box to fit inside or cover, or the mark's own size**Position**Nine anchors, an exact offset, or tiled across the whole image (Pro)**Offsets**In pixels or as a percentage of the image**Opacity**0–100**Rotation and blend mode**Pro; blend modes need Imagick**Applies to**Volumes, file types, named transforms, a minimum size, and any Craft asset condition (Pro)**Drawn by**This server (GD or Imagick), or one of the delivery services (Pro)Text watermarks are rendered as object templates, so they can carry the asset and the site with them:

```
© {{ now|date('Y') }} {{ siteName }} — {{ asset.title }}
```

Four fonts are bundled (Lato, Lato Bold, IBM Plex Mono, EB Garamond, all OFL). A profile can also point at an uploaded `.ttf` or `.otf`, which wins.

Where a watermark can be applied
--------------------------------

[](#where-a-watermark-can-be-applied)

Three modes, switchable per profile, because they answer different questions.

**Transforms.** The mark is composited into generated transforms. Originals are never touched, everything is undone by clearing the transform cache, and the mark reaches ImageOptimize's variants and Imager-X's output. What most sites want, and it needs no template changes: `asset.getUrl()`already returns a watermarked image.

**Permanent.** The stored file itself is rewritten — on upload, from the asset index, from the asset's own edit screen, or from the console. The original is copied to the backup store first. This is the only mode that protects the file somebody downloads straight from the volume.

**On demand.** Nothing automatic; a Twig call, an element action or a console command asks for a watermarked derivative, which Waterfall stores itself.

Twig
----

[](#twig)

Most sites need none of this — a profile in `transforms` mode watermarks `asset.getUrl()` with no template changes at all. This is for the cases that need to ask.

```
{# A watermarked URL, from the profile's provider or Waterfall's own derivative store #}

{# Would this asset get a watermark? #}
{% if craft.waterfall.appliesTo(asset, 'copyright') %}…{% endif %}

{# Has this asset's own file been watermarked? #}
{% if craft.waterfall.isWatermarked(asset) %}…{% endif %}

{# Why not? One verdict per profile, each with a sentence #}
{% for verdict in craft.waterfall.explain(asset) %}
    {{ verdict.profile.name }}: {{ verdict.applies ? 'yes' : verdict.message }}
{% endfor %}

{# A profile as Imager-X's own watermark parameter, to merge into a transform you write yourself #}
{% set params = craft.waterfall.imagerParams('copyright', 1200) %}
```

ImageOptimize
-------------

[](#imageoptimize)

Nothing to configure. ImageOptimize listens to the same Craft event Waterfall does, and re-saves, optimizes and derives its `.webp`/`.avif` variants from the image on that event — so the only thing that matters is running first. Waterfall registers its handler *prepended*, composites onto the image before ImageOptimize ever sees it, and every file ImageOptimize produces carries the mark. An integration check asserts the ordering, because if it ever regressed the symptom would be "the `.webp` has no watermark and nothing is in the log".

If ImageOptimize is set to transform through a service rather than on this server, Waterfall says so on its settings screen and in `waterfall/doctor` — mark those images with a provider instead.

Imager-X
--------

[](#imager-x)

Imager-X does not use Craft's transform pipeline, so it gets its own wiring.

**Automatic.** Waterfall registers a transformer that adds matching profiles to every transform, so the watermark appears with no template changes and each profile gets its own cache entry. Requires Imager-X **Pro**, which is the edition where transformers can be registered.

**By hand**, on any Imager-X edition:

```
{% set image = craft.imagerx.transformImage(asset, {
    width: 1200,
    effects: { waterfall: 'copyright' }
}) %}
```

The effect runs Waterfall's own compositor, so text, tiling and rotation all work — none of which Imager-X's built-in `watermark` parameter can do.

Delivery services (Pro)
-----------------------

[](#delivery-services-pro)

ProviderImage marksText marksTilingNotesimgix✓—✓`mark`, `mark-align`, `mark-alpha`, `mark-tile`; signs URLs when a secure token is setCloudinary✓✓✓Fetch or upload delivery; `l_text:` overlaysImageKit✓✓—`tr=…l-image/l-text…l-end`URL template✓——Your own pattern, for Bunny, Cloudflare Images, Thumbor, anythingA provider that cannot do what a profile asks for says so on the profile screen rather than quietly delivering something else. The URL template takes `{src}`, `{srcEncoded}`, `{path}`, `{mark}`, `{markEncoded}`, `{position}`, `{opacity}`, `{scale}`, `{width}`, `{height}`, `{format}`.

Console
-------

[](#console)

```
php craft waterfall/apply/run --volume=photos --dry-run   # count and explain, write nothing
php craft waterfall/apply/run --volume=photos             # write watermarks into the files
php craft waterfall/apply/restore --volume=photos         # put the originals back
php craft waterfall/apply/status                          # what carries a permanent watermark
php craft waterfall/doctor                                # what this server can actually do
```

`waterfall/doctor` is worth running once on any new server. Watermarking fails quietly more often than it fails loudly — GD compiled without FreeType draws no text, a server without Imagick has no blend modes, a provider with no credentials returns no URL — and it prints all of that in one place.

Editions
--------

[](#editions)

**Lite ($59)** — one profile, image watermarks, nine positions and offsets, scaling, opacity, GD and Imagick, all three modes, backups and restore, volume/file-type/size rules.

**Pro ($79)** — unlimited and stackable profiles, text watermarks, tiling, rotation, blend modes, asset conditions, the four delivery services, the Imager-X and ImageOptimize integrations, the per-asset Waterfall field, and the console tooling.

Backups and restore are in Lite deliberately. Charging for the undo of a destructive operation is not a business model.

What Waterfall refuses, and says so
-----------------------------------

[](#what-waterfall-refuses-and-says-so)

Vector files, animated GIFs (unless you turn that off — re-encoding loses the animation), images below a profile's minimum size, files Craft cannot manipulate, assets in volumes a profile does not cover, and assets whose Waterfall field says never. Each refusal comes with the sentence that explains it, on the asset's edit screen, in the dry run, and in `craft.waterfall.explain()`.

Licence
-------

[](#licence)

The Craft License. See `LICENSE.md`. Waterfall is commercial — Lite $59, Pro $79 — and is licensed through the Craft Plugin Store.

Support
-------

[](#support)

###  Health Score

21

—

LowBetter than 17% of packages

Maintenance65

Regular maintenance activity

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/035cb655c55af0e9e5b96754b80fd9703e195c32dbdfc49ae9a43ab9cf8db560?d=identicon)[justinholtweb](/maintainers/justinholtweb)

---

Top Contributors

[![justinholtweb](https://avatars.githubusercontent.com/u/295903?v=4)](https://github.com/justinholtweb "justinholtweb (3 commits)")

### Embed Badge

![Health badge](/badges/justinholtweb-craft-waterfall/health.svg)

```
[![Health](https://phpackages.com/badges/justinholtweb-craft-waterfall/health.svg)](https://phpackages.com/packages/justinholtweb-craft-waterfall)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
