How to Generate Image Thumbnails in Django with sorl-thumbnail

Blog / Django · April 24, 2016 · Updated June 10, 2026 · 7 min read
How to Generate Image Thumbnails in Django with sorl-thumbnail

sorl-thumbnail is a Django app that generates image thumbnails on the fly from your ImageField files, caches the result in a key-value store, and serves the resized version through a {% thumbnail %} template tag or the get_thumbnail() Python API. It is still actively maintained and works with Django 5.x and modern Pillow, so it remains a solid way to resize, crop, and convert images (including to WebP) without writing manual image-processing code. The team at MicroPyramid uses it across Django development projects where media-heavy pages need fast, consistent thumbnails.

Key takeaways

  • sorl-thumbnail needs three pieces: the app in INSTALLED_APPS, a key-value store (the cached-DB backend by default, Redis at scale), and an image engine (Pillow by default).
  • Use the {% thumbnail %} template tag in templates and get_thumbnail() in views, models, serializers, or background tasks.
  • Geometry strings like 200x100 resize while preserving aspect ratio; add crop, quality, and format to fine-tune the output.
  • Generate WebP with format="WEBP" or the THUMBNAIL_FORMAT setting to cut page weight versus JPEG.
  • Honest alternatives: easy-thumbnails and django-imagekit; in production, pair any of them with a CDN and responsive srcset.

What is sorl-thumbnail and how does it work?

sorl-thumbnail sits between your stored originals and the rendered page. The first time a thumbnail is requested for a given source + geometry + options, it:

  1. Builds a unique key from the source file and the requested options.
  2. Looks that key up in the key-value store (KVStore).
  3. If it is missing, the image engine (Pillow) generates the thumbnail, writes it to your media storage, and saves the metadata back to the KVStore.
  4. On every later request it returns the cached thumbnail instantly, so each image is processed only once.

Storage can be your own server or a cloud service such as Amazon S3 or Azure Blob Storage (via django-storages). The KVStore holds only metadata and keys, never the image bytes themselves.

How do you install and configure sorl-thumbnail?

Install the package together with an image library — Pillow is the default engine:

pip install sorl-thumbnail Pillow

Add the app to INSTALLED_APPS, then choose a KVStore and engine in settings.py. The cached-DB KVStore is the recommended default: it stores metadata in your database and caches reads through Django’s cache framework.

# settings.py
INSTALLED_APPS = [
    # ...
    "sorl.thumbnail",
]

# Image engine (Pillow is the default; pgmagick / wand are optional)
THUMBNAIL_ENGINE = "sorl.thumbnail.engines.pil_engine.Engine"

# Key-value store: cached database backend (recommended default)
THUMBNAIL_KVSTORE = "sorl.thumbnail.kvstores.cached_db_kvstore.KVStore"

# For high traffic, swap in Redis instead:
# THUMBNAIL_KVSTORE = "sorl.thumbnail.kvstores.redis_kvstore.KVStore"
# THUMBNAIL_REDIS_URL = "redis://127.0.0.1:6379/0"

The cached-DB store keeps its keys in a database table, so run migrations once:

python manage.py migrate thumbnail

How do you use the {% thumbnail %} template tag?

Load the template library, then wrap your image in a {% thumbnail %} ... {% endthumbnail %} block. Pass the image field and a geometry string; the resized im object exposes url, width, and height:

{% load thumbnail %}

{% thumbnail item.image "200x100" as im %}
  <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" alt="{{ item.title }}">
{% empty %}
  <p>No image available.</p>
{% endthumbnail %}

Geometry preserves aspect ratio by default. Add crop, quality, or format to control the result — for example, a center-cropped square delivered as WebP:

{% thumbnail item.image "100x100" crop="center" quality=85 format="WEBP" as im %}
  <img src="{{ im.url }}" alt="{{ item.title }}">
{% endthumbnail %}

How do you generate thumbnails in Python with get_thumbnail()?

When you need a thumbnail outside a template — in a view, a model method, a management command, or a DRF serializer — use the low-level get_thumbnail() API. It accepts the same geometry and options and returns an object with .url, .width, and .height:

from sorl.thumbnail import get_thumbnail, delete

# Generate (or fetch the cached) thumbnail
im = get_thumbnail(profile.avatar, "300x300", crop="center", quality=90)
print(im.url, im.width, im.height)

# Remove a source image and all of its generated thumbnails
delete(profile.avatar)

What geometry, crop, quality and format options are available?

The table below summarises the options you will reach for most often.

Option Example What it does
Geometry "200x100", "200", "x100" Target size; width-only or height-only keeps aspect ratio
crop crop="center", crop="smart" Crops to fill the box; smart trims the least-busy edges
quality quality=85 JPEG/WebP compression, 1-100 (default 95)
format format="WEBP" Output format: JPEG, PNG, or WEBP
upscale upscale=False Whether smaller images are enlarged (default True)
padding padding=True Pads to the exact box instead of cropping
progressive progressive=True Progressive (interlaced) JPEG output

To make WebP the default everywhere, set THUMBNAIL_FORMAT = "WEBP" in settings; sorl-thumbnail then encodes thumbnails as WebP through Pillow without per-tag flags.

How do you serve retina and responsive thumbnails?

For high-DPI (retina) screens, sorl-thumbnail can emit alternative resolutions. List the multipliers you want in settings:

# settings.py
THUMBNAIL_ALTERNATIVE_RESOLUTIONS = [1.5, 2]

Then build a srcset with the resolution filter so browsers pick the right density:

{% thumbnail item.image "200x100" as im %}
  <img src="{{ im.url }}"
       srcset="{{ im.url|resolution:'1.5x' }} 1.5x,
               {{ im.url|resolution:'2x' }} 2x"
       alt="{{ item.title }}">
{% endthumbnail %}

For a full breakpoint-based srcset/sizes workflow across multiple widths, see our guide on responsive thumbnails in Django templates with sorl-thumbnail.

How do you show thumbnails in the Django admin?

Mix AdminImageMixin into your ModelAdmin (or inline admin) to render a thumbnail preview and a nicer upload widget for image fields:

from django.contrib import admin
from sorl.thumbnail.admin import AdminImageMixin
from .models import Product


@admin.register(Product)
class ProductAdmin(AdminImageMixin, admin.ModelAdmin):
    list_display = ("name", "image")

How do you keep the thumbnail cache clean?

Thumbnails accumulate over time. Two management commands keep storage and the KVStore tidy:

# Remove KVStore references to images that no longer exist
python manage.py thumbnail cleanup

# Empty the KVStore (does not delete the generated image files)
python manage.py thumbnail clear

Filenames matter for SEO and caching, and by default sorl-thumbnail writes hashed names. If you need readable, stable thumbnail filenames, see preserving file names with sorl for better SEO.

sorl-thumbnail vs easy-thumbnails vs django-imagekit

sorl-thumbnail is not the only option, and the honest answer is that the best tool depends on your workflow:

Feature sorl-thumbnail easy-thumbnails django-imagekit
Primary API {% thumbnail %} tag + get_thumbnail() {% thumbnail %} tag + thumbnail_url filter ImageSpec fields + {% generateimage %}
Named presets No (inline options) Yes (THUMBNAIL_ALIASES) Yes (image specs)
Metadata cache KVStore (DB / cache / Redis) Database tables Cache + optional model fields
WebP output Yes (Pillow) Yes (<picture> / signal) Yes (processors)
Smart cropping Yes (crop="smart") Focal-point add-ons Via processors
Best fit Template-driven resizing with a fast cache Reusable named presets Declarative, model-level specs

If you mostly need to choose between calling the tag in templates and the Python API:

{% thumbnail %} template tag get_thumbnail() low-level API
Where it runs Templates Views, models, serializers, tasks
Returns A bound template variable A Python thumbnail object
Best for Page rendering APIs, background jobs, precomputing

Whatever library you pick, modern image delivery in 2026 goes beyond resizing: serve WebP (and AVIF where your Pillow build supports it), add srcset/sizes for responsive layouts, lazy-load below-the-fold images, and put a CDN in front of your media. For media at scale, pair sorl-thumbnail with Amazon S3 and CloudFront for faster loading so generated thumbnails are cached at the edge.

Frequently Asked Questions

Is sorl-thumbnail still maintained and compatible with Django 5?

Yes. sorl-thumbnail is actively maintained and works with current Django (including 5.x) and modern Pillow. It is still a reliable choice for on-the-fly thumbnails — just keep the package and Pillow updated alongside your Django version.

Do I really need a key-value store and an image engine?

Yes, both are required. The image engine (Pillow by default) does the actual resizing, while the key-value store caches each thumbnail’s metadata so images are processed only once. The cached-DB KVStore works out of the box; switch to Redis when traffic grows.

How do I generate WebP thumbnails with sorl-thumbnail?

Pass format="WEBP" to the {% thumbnail %} tag or get_thumbnail(), or set THUMBNAIL_FORMAT = "WEBP" to make it the default. Pillow handles the encoding, so WebP works anywhere your Pillow build supports it and noticeably reduces image weight versus JPEG.

What is the difference between the template tag and get_thumbnail()?

The {% thumbnail %} tag is for templates and binds a thumbnail variable you render inline. get_thumbnail() is the same engine exposed to Python, so you can build thumbnails in views, model methods, serializers, or background tasks. Both share the same caching and options.

sorl-thumbnail vs easy-thumbnails vs django-imagekit — which should I use?

Use sorl-thumbnail for template-driven resizing with a fast KVStore cache, easy-thumbnails when you want reusable named aliases, and django-imagekit when you prefer declarative image specs on the model. All three use Pillow and support WebP; the difference is workflow style.

How do I stop thumbnails from piling up in storage?

Run python manage.py thumbnail cleanup to drop KVStore entries for missing sources and python manage.py thumbnail clear to empty the store. Use the delete() API to remove a source image plus its thumbnails, and front your media with a CDN so regenerated files are cached at the edge.

Share this article