PHPackages                             craftyhedge/craft-thumbhash - 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. [Image &amp; Media](/categories/media)
4. /
5. craftyhedge/craft-thumbhash

ActiveCraft-plugin[Image &amp; Media](/categories/media)

craftyhedge/craft-thumbhash
===========================

Automatic ThumbHash placeholders for Craft CMS

2.0.4(3w ago)00proprietaryPHPPHP ^8.2CI passing

Since Mar 31Pushed 3w agoCompare

[ Source](https://github.com/craftyhedge/craft-thumbhash)[ Packagist](https://packagist.org/packages/craftyhedge/craft-thumbhash)[ RSS](/packages/craftyhedge-craft-thumbhash/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (16)Versions (9)Used By (0)

ThumbHash for Craft CMS
=======================

[](#thumbhash-for-craft-cms)

[![CI](https://github.com/craftyhedge/craft-thumbhash/actions/workflows/ci.yml/badge.svg)](https://github.com/craftyhedge/craft-thumbhash/actions/workflows/ci.yml)[![PHPStan Level 7](https://camo.githubusercontent.com/797fcfed94913f509c34a34eddcb3019ef7fe96435ca6cbcdab00e26918d3230/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230372d627269676874677265656e3f6c6f676f3d706870)](https://github.com/craftyhedge/craft-thumbhash/blob/master/phpstan.neon)[![PHP 8.2+](https://camo.githubusercontent.com/91c32053ff9bef6ba397e97fb6224bdb85a98947e8a7b3555a8c76cb655d16a9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e32253230253743253230382e33253230253743253230382e342d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://github.com/craftyhedge/craft-thumbhash)[![Craft 5](https://camo.githubusercontent.com/d6f30693ee25d6b3ce8c5be7ca3c7bb93e6b89bb2a801e7541428ebfdb2a76ad/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4372616674253230434d532d352e302532422d6535343232623f6c6f676f3d63726166742d636d73266c6f676f436f6c6f723d7768697465)](https://github.com/craftyhedge/craft-thumbhash)

Automatic thumbhash placeholder generation for Craft CMS image assets.

Table of Contents
-----------------

[](#table-of-contents)

- [What is ThumbHash?](#what-is-thumbhash)
- [Example](#example)
- [Requirements](#requirements)
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Rendering](#rendering)
- [Configuration](#configuration)
- [Performance &amp; Scaling](#performance--scaling)
- [Control Panel Features](#control-panel-features)
- [CLI &amp; Maintenance](#cli--maintenance)
- [Reference](#reference)
- [Notes](#notes)
- [Logging](#logging)
- [License](#license)

What is ThumbHash?
------------------

[](#what-is-thumbhash)

ThumbHash is a tiny visual fingerprint of an image — a ~28-byte base64 string that captures the overall color and structure. This plugin generates those hashes on the backend and ships a small client-side JS decoder that turns each hash into a PNG placeholder right in the browser.

The result: an immediate, content-aware preview while the full image loads, improving perceived performance and reducing layout jank — with virtually zero per-image cost in your HTML.

### How it works

[](#how-it-works)

1. **Backend** — On upload (or via CLI), the plugin creates a small transform of the original image and encodes it to a ~28-byte base64 hash using the ThumbHash algorithm. Generation runs in a queue job so it never blocks a request.
2. **Frontend** — A single inline decoder script (~5 KB minified) registers a MutationObserver that automatically converts every `data-thumbhash` attribute into a PNG placeholder data URL as the DOM is built. It then applies that placeholder using the configured render method (`bg` by default, or `img`/`picture`). No extra network requests, no visible pop-in.

That's it for most sites. Drop the hash in your markup, register the script once, and placeholders appear before images even start loading.

Using the `bg` mode even provides LQIPs for eager loaded images! Since the placeholder is decoupled from lazy loading the full image simply overlays the placeholder once downloaded natively by the browser.

### Optional: Inline PNG Data URLs

[](#optional-inline-png-data-urls)

If you need placeholders without any JavaScript — for example in RSS feeds, AMP pages, or HTML emails — the plugin can also pre-decode each hash to a PNG data URL on the server and store it in the database.

- Typically ~0.8–2 KB per image (before HTTP compression), still smaller and better-looking than most blurred LQIPs.
- The backend generator automatically reads the original image dimensions and applies an exact center-crop to the decoded PNG to perfectly match the asset's aspect ratio, preventing layout shifts.
- Opt in with the `generateDataUrl` setting (enabled by default).
- Use `thumbhashDataUrl(asset)` in your templates to get the ready-made data URL.

For regular websites, the JS decoder is the recommended approach — it keeps per-image markup tiny and works great with lazy-loading libraries and infinite scroll.

Example
-------

[](#example)

ThumbHash placeholders retain accurate colors and smooth gradients. They encode information to a string that is smaller than a typical LQIP URL!

Check it out - [ThumbHash Example](https://craftyhedge.github.io/thumbhash-example/)

  [![Example of a thumbhash placeholder decoded to a tiny PNG data URL](assets/thumbhash-example.png)](assets/thumbhash-example.png) [![Example full image](assets/image-example.avif)](assets/image-example.avif) Photo by [Sanjeevan SatheesKumar](https://unsplash.com/@sanjeevan_s?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/photos/tree-surrounded-by-grass-MG8c-4n1QVE?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

Requirements
------------

[](#requirements)

- Craft CMS 5.0+
- PHP 8.2+

Installation
------------

[](#installation)

```
composer require craftyhedge/craft-thumbhash
php craft plugin/install thumbhash
```

Basic Usage
-----------

[](#basic-usage)

> **Note:** The inline script `{{ thumbhashScript() }}` must be included on any page where you want placeholders to render unless using the No-JS option.

The decoder script decodes each hash to a tiny PNG data URL and applies it according to the render method (`bg`, `img`, or `picture`; default is `bg`). Use `data-thumbhash-render="srcset"` explicitly on a `` when each child source should use its own hash. The script is inlined (no extra HTTP request). By default it is registered in `` (configurable via `scriptPosition`), which allows the MutationObserver to start before `` elements are parsed so placeholders appear as the DOM is built rather than after.

```
{# Register the inline decoder script (safe to call; Craft includes it once per page) #}
{{ thumbhashScript() }}
```

Your lazy loading library (lazysizes, lozad, etc.) handles swapping `data-src` → `src` when the element enters the viewport.

For `` placeholders written directly to `src`, either set `data-thumbhash-render="img"` per element, or set `renderMethod` to `'img'` globally in config.

```
{# For each image, use data-thumbhash with your preferred lazy loading approach #}
{% set hash = thumbhash(asset) %}

```

Rendering
---------

[](#rendering)

### Background Image Support

[](#background-image-support)

Pass the hash with `data-thumbhash` and set `data-thumbhash-render="bg"` (or rely on the global default). The decoder will populate `style.backgroundImage` and apply the configured background placeholder styles for you. By default that is `background-repeat: no-repeat`, `background-size: cover`, and `background-position: center`.

A distinct benefit of the background image approach is that it **decouples your LQIP from lazyloading**. Eager-loaded images display the placeholder instantly, and the full image seamlessly overlays it once downloaded natively by the browser.

```
{% set hash = thumbhash(asset) %}

```

Use `src="{{ thumbhashTransparentSvg() }}"` when using background mode. This prevents empty `src` violations and native browser 'no image' placeholders.

### Picture Element Support

[](#picture-element-support)

The decoder also supports `` elements with multiple `` children. When `data-thumbhash` is placed on a `` with `data-thumbhash-render="picture"`, the decoder propagates that single hash to child `` and `` elements so each gets its own placeholder.

The placeholder ratio is derived from the `width` and `height` attributes on each element, so you can have different aspect ratios for each breakpoint if needed. If no valid dimensions are found, the decoder falls back to the ThumbHash's native decoded dimensions (no ratio crop/resize).

```

```

Use `picture` mode when your sources are transforms or crops of the same asset and one representative hash is good enough.

### Srcset Picture Support

[](#srcset-picture-support)

When each `` uses a different Craft asset, use `data-thumbhash-render="srcset"` explicitly on the parent `` and put each asset's own `data-thumbhash` on the corresponding child. The parent still handles applying placeholders to child `` elements and the fallback ``, but each child is decoded from its own hash.

```

```

Like `picture` mode, each child placeholder is cropped/resized from that child's own `width` and `height` attributes.

### What should you use?

[](#what-should-you-use)

MethodUse Case`bg`Most use cases (recommended default)`picture`Responsive images with one shared asset/hash across sources`srcset`Responsive images where each source uses a different asset/hash; set explicitly on the parent ```img`Single images without responsive breakpoints> **Note:** Due to the nature of ThumbHash placeholders, the accuracy the picture method provides tends to be overkill. ThumbHashes are blurry and slight differences between it and the final image are very hard to notice for most use cases. Background placeholders with `cover` sizing do the job very well and have the bonus of giving eager loaded images LQIPs too!

### Fallback vs. Explicit Rendering

[](#fallback-vs-explicit-rendering)

Choose one of these patterns depending on how much per-element control you need.

**Approach A: Explicit per element (recommended for mixed projects)**

Set `data-thumbhash-render` on each element (or wrapper) so your CSS can branch safely by mode.

```
{% set hash = thumbhash(asset) %}

{# bg mode on a wrapper #}

{# img mode on the image itself #}

```

**Approach B: Config fallback (minimal markup)**

Omit `data-thumbhash-render` and let the plugin-wide `renderMethod` config decide mode.

```
{% set hash = thumbhash(asset) %}

```

In this fallback approach, CSS cannot infer the active mode from element attributes alone. Use mode-agnostic CSS or add a global page class that mirrors your configured default mode.

```
return [
    // ...
    'renderMethod' => 'bg', // or 'img' or 'picture'
];
```

### No JavaScript Option

[](#no-javascript-option)

For the no JS decoding option, you can use `thumbhashDataUrl()` to get the decoded PNG data URL directly and set it as an inline background image:

```
{% set placeholder = thumbhashDataUrl(asset) %}

```

### CSS for Smooth Lazyloading Class Swaps

[](#css-for-smooth-lazyloading-class-swaps)

`.img-wrapper` is just an example wrapper class for your image elements, adjust as needed for your markup. This could be a `picture` element.

If you are mixing modes, prefer explicit `data-thumbhash-render` attributes and use mode-aware CSS:

```
/* Only hide the real image while loading when placeholder is on container background */
.img-wrapper[data-thumbhash][data-thumbhash-render="bg"] img.lazyload,
.img-wrapper[data-thumbhash][data-thumbhash-render="bg"] img.lazyloading {
    opacity: 0;
}

.img-wrapper[data-thumbhash][data-thumbhash-render="bg"] img.lazyloaded {
    animation: lazy-image-fade-in 130ms cubic-bezier(0.2, 0, 0, 1) both;
}

@keyframes lazy-image-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
```

Configuration
-------------

[](#configuration)

Copy the plugin config template from [src/config.php](src/config.php) to `config/thumbhash.php` in your Craft project, then uncomment and adjust only the options you need:

- [src/config.php](src/config.php)

Minimal example:

```
