A lightweight Wagtail StreamField block + DRF serializer that exposes responsive WebP thumbnails with dimensions and focal points.
Project description
wagtail-thumbnails
A drop-in Wagtail StreamField block + DRF serializer that turns any uploaded image into a multi-variant WebP payload with dimensions and focal points — ready for headless frontends.
What you get
- A
ThumbnailBlock(StructBlock) withimage+ optional per-instancealt_textoverride - A
ThumbnailSerializerthat emits:- Source URL
- Resolved alt text (block override → image
contextual_alt_text→description→title) - Focal point (from Wagtail's built-in picker)
- A configurable map of responsive variants (defaults:
full_hd,large,medium,small) — each withurl,width,height,format
- Settings-driven variants — ship sensible defaults, override per project
You upload JPEG/PNG. Wagtail/Pillow generates and caches WebP renditions on first request. No user-side conversion required.
Install
pip install wagtail-thumbnails
Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"wagtail_thumbnails",
]
Quickstart
As a StreamField block
from wagtail.fields import StreamField
from wagtail_thumbnails.blocks import ThumbnailBlock
class ArticlePage(Page):
body = StreamField([
("thumbnail", ThumbnailBlock()),
# ... your other blocks
])
As a nested serializer
from rest_framework import serializers
from wagtail_thumbnails.serializers import ThumbnailSerializer
class ProductSerializer(serializers.ModelSerializer):
hero_image = ThumbnailSerializer()
class Meta:
model = Product
fields = ["hero_image"]
Example output
{
"src": "https://cdn.example.com/media/images/hero.jpg",
"alt_text": "A sunset over the bay",
"focal_point": { "x": 400, "y": 300, "width": 100, "height": 100 },
"variants": {
"full_hd": { "url": "https://.../hero.fill-1920x1280.format-webp.webp", "width": 1920, "height": 1280, "format": "webp" },
"large": { "url": "https://.../hero.fill-800x533.format-webp.webp", "width": 800, "height": 533, "format": "webp" },
"medium": { "url": "https://.../hero.fill-450x300.format-webp.webp", "width": 450, "height": 300, "format": "webp" },
"small": { "url": "https://.../hero.fill-125x83.format-webp.webp", "width": 125, "height": 83, "format": "webp" }
}
}
Notes on the shape:
focal_pointisnullwhen no focal point is set. Itswidth/heightarenullwhen only a centre point was picked (no focal area).alt_textisnullwhen nothing is available. Wagtail titles are intentionally not used as alt text — they're typically filenames, which makes for a poor screen-reader experience.- The block emits
alt_text: ""(empty string, not null) when the editor ticks the Decorative checkbox. Mark purely visual imagery this way so assistive tech can skip it. - Variants never upscale: if the source is narrower than a variant's target width, the variant is generated at the source's native dimensions.
Configuration
All settings live under a single dict. User-supplied keys override defaults (variant maps fully replace, not merge).
WAGTAIL_THUMBNAILS = {
"VARIANTS": {
"full_hd": {"width": 1920, "format": "webp", "quality": 80},
"large": {"width": 800, "format": "webp", "quality": 80},
"medium": {"width": 450, "format": "webp", "quality": 80},
"small": {"width": 125, "format": "webp", "quality": 40},
},
"MIN_IMAGE_WIDTH": 25,
"MIN_IMAGE_HEIGHT": 25,
}
| Key | Default | Description |
|---|---|---|
VARIANTS |
see above | Mapping of variant name → {width, format, quality}. Variant names become keys in the API output's variants dict. |
MIN_IMAGE_WIDTH |
25 |
Minimum source-image width enforced by image_resolution_validator and ThumbnailBlock.clean(). |
MIN_IMAGE_HEIGHT |
25 |
Minimum source-image height. |
Supported format values: webp (default), jpeg, png. quality is honoured for webp and jpeg.
Misconfigurations (unknown keys, bad variant shapes, unsupported formats, out-of-range quality) raise ImproperlyConfigured at first access — not at request time.
Migrating from a plain ImageBlock
ThumbnailBlock is a StructBlock, so the on-disk JSON shape inside a StreamField differs from a bare ImageBlock. If you're swapping an ImageBlock (whose value is just an image ID) for ThumbnailBlock (whose value is {"image": <id>, "alt_text": "", "decorative": false}), existing rows need a data migration:
# yourapp/migrations/00XX_migrate_image_blocks_to_thumbnail.py
from django.db import migrations
OLD_TYPE = "image_block"
NEW_TYPE = "thumbnail"
def forwards(apps, schema_editor):
YourPage = apps.get_model("yourapp", "YourPage")
for page in YourPage.objects.all():
changed = False
for block in page.body.raw_data:
if block["type"] == OLD_TYPE:
block["type"] = NEW_TYPE
block["value"] = {"image": block["value"], "alt_text": "", "decorative": False}
changed = True
if changed:
page.save()
class Migration(migrations.Migration):
dependencies = [("yourapp", "00XX_previous")]
operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]
For revisions (PageRevision), apply the same transform to revision.content. For images that have to keep the legacy shape (e.g. external API consumers), keep ImageBlock for those entries and use ThumbnailBlock only for new ones.
Compatibility
| Python | Django | Wagtail |
|---|---|---|
| 3.10 – 3.13 | 4.2 LTS, 5.1, 5.2 | 5.2, 6.x, 7.x |
Development
git clone https://github.com/profilsoftware/wagtail-thumbnails
cd wagtail-thumbnails
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run mypy src/
Contributing
See CONTRIBUTING.md. Bug reports and PRs welcome.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wagtail_thumbnails-0.1.0.tar.gz.
File metadata
- Download URL: wagtail_thumbnails-0.1.0.tar.gz
- Upload date:
- Size: 10.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe23f59fd234f8ac6d1b3e119d3c62c524386573ae68eaba4b748c3593a59e20
|
|
| MD5 |
2e8a99ea68c77e8dbe12126bd3319bfa
|
|
| BLAKE2b-256 |
abeca0370c6b9c25ebcf7c2dd5e1b0a96a8e61b1281aaaab3e971d386a8014eb
|
Provenance
The following attestation bundles were made for wagtail_thumbnails-0.1.0.tar.gz:
Publisher:
publish.yml on profilsoftware/wagtail-thumbnails
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_thumbnails-0.1.0.tar.gz -
Subject digest:
fe23f59fd234f8ac6d1b3e119d3c62c524386573ae68eaba4b748c3593a59e20 - Sigstore transparency entry: 1566351339
- Sigstore integration time:
-
Permalink:
profilsoftware/wagtail-thumbnails@7a5621b18658b2376435e3eca12a2193b95e08db -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/profilsoftware
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7a5621b18658b2376435e3eca12a2193b95e08db -
Trigger Event:
push
-
Statement type:
File details
Details for the file wagtail_thumbnails-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wagtail_thumbnails-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc30ae09a6fb6cbebd3cd9300d6b42635e59a44f3c666718da4b186bb5f1068d
|
|
| MD5 |
5a6c8d2b454cd9f07e81a3aea4f6c75e
|
|
| BLAKE2b-256 |
b2699a189c03f8017b4bdac1871a7642821401a60434e93de2e0aa926ee240fe
|
Provenance
The following attestation bundles were made for wagtail_thumbnails-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on profilsoftware/wagtail-thumbnails
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_thumbnails-0.1.0-py3-none-any.whl -
Subject digest:
fc30ae09a6fb6cbebd3cd9300d6b42635e59a44f3c666718da4b186bb5f1068d - Sigstore transparency entry: 1566351355
- Sigstore integration time:
-
Permalink:
profilsoftware/wagtail-thumbnails@7a5621b18658b2376435e3eca12a2193b95e08db -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/profilsoftware
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7a5621b18658b2376435e3eca12a2193b95e08db -
Trigger Event:
push
-
Statement type: