Store and Transform Django Media With Cloudinary

Use Cloudinary for Django media uploads: configure django-cloudinary-storage or the Cloudinary SDK, upload user images and video, generate resized and optimized URLs on the fly, sign uploads from the browser, and keep secrets and costs under control.

Problem statement

Your Django app accepts user images or video and needs several sizes, modern formats, and fast delivery. Doing that yourself means Pillow thumbnails, background processing, object storage, and a CDN. Cloudinary combines storage, transformation, and CDN delivery: you upload once, and URLs describe the size and format you want. The setup decisions are how Django stores files (as a storage backend or through the SDK), how browsers upload, and how to avoid surprise transformation costs.

Cloudinary for DjangoUploadserver or browserCloudinary storesoriginal assetURL transformssize, formatCDN deliverycached variantsStore the original once; ask for variants through URL parameters.
  1. Set CLOUDINARY_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME as a secret.
  2. For ImageField/FileField uploads, use django-cloudinary-storage as the default storage backend, or CloudinaryField from the cloudinary package.
  3. Generate variants with transformations: width=400, crop="fill", fetch_format="auto", quality="auto".
  4. For large or direct browser uploads, use signed upload parameters generated by a Django view.
  5. Limit allowed transformations (strict transformations) and monitor credits.

Step-by-step solution

1. Install and configure

pip install cloudinary django-cloudinary-storage
INSTALLED_APPS = [
    # put cloudinary_storage before staticfiles if you also use it for static files
    "cloudinary_storage",
    "django.contrib.staticfiles",
    "cloudinary",
    # ...
]
STORAGES = {
    "default": {"BACKEND": "cloudinary_storage.storage.MediaCloudinaryStorage"},
    "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}

The libraries read CLOUDINARY_URL from the environment. Keep static files on WhiteNoise or a CDN; Cloudinary is best used for user media. For video files, VideoMediaCloudinaryStorage or RawMediaCloudinaryStorage handle non-image types.

2. Upload from Django

Existing ImageField models work unchanged with the storage backend:

class Product(models.Model):
    image = models.ImageField(upload_to="products/")

product.image.url returns a Cloudinary delivery URL. Uploading through Django sends the file from the client to your server and then to Cloudinary, which is fine for small images.

where the file travelsThrough Django✓works with ImageField✓server validates file✕ties up workers for big filesSigned direct upload✓browser to Cloudinary✓no worker time for bytes✕needs a signing viewUse direct uploads for video and large imagesdjango-deployment.com
Validate the uploaded asset’s type and size in the callback either way.

3. Generate transformed URLs

from cloudinary import CloudinaryImage

def product_thumb(product):
    return CloudinaryImage(product.image.name).build_url(
        width=400, height=400, crop="fill", gravity="auto",
        fetch_format="auto", quality="auto", secure=True,
    )

In templates, the cloudinary template tag does the same. f_auto serves AVIF or WebP to browsers that support them, and q_auto picks a quality level per image. The first request for a new variant generates it; later requests come from the CDN cache.

4. Sign direct uploads

import time, cloudinary.utils
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required

@login_required
def upload_signature(request):
    params = {"timestamp": int(time.time()), "folder": f"users/{request.user.pk}", "upload_preset": "user_images"}
    signature = cloudinary.utils.api_sign_request(params, cloudinary.config().api_secret)
    return JsonResponse({**params, "signature": signature, "api_key": cloudinary.config().api_key,
                         "cloud_name": cloudinary.config().cloud_name})

The browser posts the file with these fields to Cloudinary's upload endpoint, then sends the resulting public_id to Django to save on the model. An upload preset can restrict formats, size, and folder.

5. Control costs and access

Every new transformation consumes credits. Enable strict transformations so only named transformations you define can be generated, which stops anyone from creating unlimited variants by editing URLs. Use private or authenticated delivery types for files that should not be public, and generate signed URLs for them.

before launchCLOUDINARY_URL as a secretnever in gitStrict transformations onnamed variants onlyf_auto and q_automodern formatsSigned uploads for browserspresets limit sizePrivate delivery when neededsigned URLsStrict transformations protect your credit balancedjango-deployment.com
Watch the usage dashboard after launch for unexpected transformation growth.

Explanation

Cloudinary stores each upload as an asset identified by a public ID. Delivery URLs encode transformations in the path, and Cloudinary's CDN generates and caches each variant on first request. A Django storage backend maps FileField operations (save, url, delete) onto Cloudinary's API, so models keep working. The trade-off compared with S3 plus your own thumbnails is cost per transformation and bandwidth against far less code to maintain.

Edge cases / notes

  • Deleting files. Deleting model instances does not delete assets unless the storage backend or your code calls delete; clean up orphans periodically.
  • Migration. Moving existing media means uploading originals and updating field values to new public IDs.
  • Vendor lock-in. Transformation URLs are Cloudinary-specific; wrap them in a helper so switching providers touches one place.

Next steps

FAQ

Can I use Cloudinary with existing ImageField models?

Yes, with django-cloudinary-storage as the default storage backend.

Should static files go to Cloudinary too?

Usually not. Use WhiteNoise or a CDN for static files and Cloudinary for user media.

How do I stop people generating unlimited image sizes?

Enable strict transformations and define named transformations.

Can browsers upload directly to Cloudinary?

Yes, with a signature generated by a Django view and an upload preset that limits files.

Keep going