File And Image Fields

Store user uploads with FileField and ImageField. Configure upload_to, storage backends, Pillow, validations, and clean up files on delete.

1. Introduction

Django provides two fields for handling user uploads: FileField for any file type and ImageField specifically for images. Both fields store the file path in the database — not the file itself. The actual file is saved to the folder you define in MEDIA_ROOT.

This guide covers how to set up both fields, configure upload paths, install Pillow for image support, validate uploads, and handle file cleanup.

  • Your MEDIA_URL and MEDIA_ROOT must already be configured in settings. If not, see Static and Media Configuration.
  • Your .venv must be active and Django 5.2 installed.

2. FileField

Use FileField to allow users to upload any type of file — PDFs, documents, spreadsheets, zip files. The field stores the relative file path in the database.

# pages/models.py

from django.db import models


class Document(models.Model):
    title = models.CharField(max_length=200)
    file = models.FileField(upload_to='documents/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

The upload_to argument tells Django where inside MEDIA_ROOT to save the file. In this example, uploaded files go to media/documents/. Django creates this folder automatically if it does not exist.

3. ImageField

ImageField works exactly like FileField but adds validation to confirm the uploaded file is a valid image. It requires the Pillow library to be installed.

class Profile(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
    photo = models.ImageField(upload_to='profiles/', blank=True)
    bio = models.TextField(blank=True)

    def __str__(self):
        return f'Profile of {self.user.username}'

Install Pillow inside your active .venv:

pip install Pillow
pip freeze > requirements.txt

4. Configuring upload_to

upload_to controls where files are saved inside MEDIA_ROOT. You can use a static string, a path with date formatting, or a callable function.

Static folder

photo = models.ImageField(upload_to='photos/')
# Saves to: media/photos/filename.jpg

Date-based subfolders

photo = models.ImageField(upload_to='photos/%Y/%m/')
# Saves to: media/photos/2026/05/filename.jpg

Callable — full control over the path

import os
import uuid

def upload_profile_photo(instance, filename):
    ext = filename.split('.')[-1]
    filename = f'{uuid.uuid4()}.{ext}'
    return os.path.join('profiles', str(instance.user.id), filename)


class Profile(models.Model):
    photo = models.ImageField(upload_to=upload_profile_photo, blank=True)

The callable approach is the most flexible. It lets you rename files, organize them by user ID, and avoid filename conflicts by using a UUID. instance is the model instance being saved and filename is the original file name from the upload.

5. Accessing files in templates

File and image fields provide a .url property that builds the full URL to the file using MEDIA_URL. Always use .url in templates — never build the path manually.


    <img src="/static/images/default-avatar.png" alt="Default avatar">

Always check if the field has a value before calling .url. Calling .url on an empty field raises a ValueError.

6. Validating uploads

Django does not restrict file types or sizes by default. You need to add validation yourself. The best place for this is a custom validator function:

# pages/validators.py

from django.core.exceptions import ValidationError


def validate_file_size(value):
    limit = 5 * 1024 * 1024  # 5 MB
    if value.size > limit:
        raise ValidationError('File size must be under 5 MB.')


def validate_image_extension(value):
    allowed = ['.jpg', '.jpeg', '.png', '.webp']
    ext = value.name.lower().rsplit('.', 1)[-1]
    if f'.{ext}' not in allowed:
        raise ValidationError('Only JPG, PNG, and WebP images are allowed.')

Apply the validators to the field:

from .validators import validate_file_size, validate_image_extension

class Profile(models.Model):
    photo = models.ImageField(
        upload_to='profiles/',
        blank=True,
        validators=[validate_file_size, validate_image_extension]
    )

7. Cleaning up files on delete

When you delete a model instance, Django removes the database row but does not delete the associated file from disk. Over time this leaves orphaned files in your media folder.

Use a post_delete signal to clean up files automatically:

# pages/models.py

from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver


class Profile(models.Model):
    photo = models.ImageField(upload_to='profiles/', blank=True)


@receiver(post_delete, sender=Profile)
def delete_profile_photo(sender, instance, **kwargs):
    if instance.photo:
        instance.photo.delete(save=False)

save=False tells Django to delete the file from storage without trying to save the model instance again — which would fail since the record has already been deleted.

8. Next steps

File and image uploads are now covered. The next step is choices and enums — a clean way to restrict a field to a fixed set of values and keep that logic inside the model.


Never miss a story on Django.wiki

Subscribe for fresh tutorials, snippets, and updates.

By subscribing you agree to our Privacy Policy.