Introduction to Django Media Handling
This article introduces Django media handling and explains how to manage user-uploaded files such as images, documents, and attachments. It covers MEDIA_ROOT, MEDIA_URL, FileField, ImageField, upload forms, request.FILES, storage backends, validation, private media, production storage, file cleanup, testing, and common mistakes.
Introduction to Django Media Handling
Django applications often need to work with files uploaded by users.
Examples include:
- profile pictures
- product photos
- PDF documents
- attachments
- videos
- audio files
- spreadsheets
- scanned documents
Django refers to these user-controlled files as media files.
Media files are different from static files.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Django provides file fields, upload handling, and a storage API for working with these files. By default, Django can store files on the local filesystem, but the storage system can also be replaced with remote or custom storage.
Basic Media Configuration
A basic local development setup uses two settings:
⧉
1 2 | |
These settings have different purposes:
| Setting | Purpose |
|---|---|
MEDIA_ROOT |
Directory where uploaded files are stored |
MEDIA_URL |
URL prefix used to access uploaded files |
Given:
⧉
1 2 | |
the project might contain:
⧉
1 2 3 4 5 6 7 | |
A stored file might exist at:
⧉
1 | |
and be available during development through a URL such as:
⧉
1 | |
With the default local filesystem storage, Django uses filesystem-backed storage for uploaded files, while alternative storage systems can implement the same storage interface.
Media Files and Static Files
Static and media files should normally be kept separate.
A common project structure is:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Typical settings:
⧉
1 2 3 4 5 | |
A site logo might be:
⧉
1 | |
A user-uploaded profile picture might be:
⧉
1 | |
Do not store uploaded user content inside the static directory.
Static files are deployment assets. Media files are runtime data.
FileField
Django’s FileField represents a file associated with a model.
Example:
⧉
1 2 3 4 5 6 7 8 9 | |
If a user uploads:
⧉
1 | |
Django may store it as:
⧉
1 | |
The database does not normally contain the complete contents of the file.
Instead, the model field stores the file’s name or storage-relative path, while the storage backend manages the actual file. Django exposes the stored file through a FieldFile object.
upload_to
The upload_to argument determines where files are stored relative to the configured storage location.
Example:
⧉
1 2 3 | |
Possible location:
⧉
1 | |
For images:
⧉
1 2 3 | |
Possible location:
⧉
1 | |
Organizing uploads into directories keeps media easier to manage.
Date-Based Upload Paths
upload_to can contain date formatting.
Example:
⧉
1 2 3 | |
An uploaded image might be stored under:
⧉
1 | |
This can help prevent one directory from accumulating a very large number of files.
Dynamic Upload Paths
upload_to can also be a callable.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
For user 42, the path might become:
⧉
1 | |
The function receives:
⧉
1 2 3 4 5 | |
It must return the path that the storage system should use.
Be Careful With Unsaved Primary Keys
A new model instance may not yet have a primary key when Django determines the upload path.
For example:
⧉
1 2 | |
If the object has not been saved yet:
⧉
1 | |
may be:
⧉
1 | |
This could create a path such as:
⧉
1 | |
If a stable identifier is needed before the first save, consider using something already available on the instance, such as:
- a UUID
- a user ID
- another stable field
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
ImageField
ImageField is designed specifically for uploaded images.
⧉
1 2 3 4 5 6 7 | |
It provides image-specific validation and can expose image dimensions.
Django’s image handling uses Pillow, so projects using ImageField normally need Pillow installed. Django also warns that uploaded media must still be treated as untrusted content; image validation alone does not make arbitrary uploaded files safe to serve.
Install Pillow with:
⧉
1 | |
Optional File Fields
An upload can be optional:
⧉
1 2 3 4 | |
A nullable database value may also be used when appropriate:
⧉
1 2 3 4 5 | |
For many file fields, blank=True is sufficient when an empty filename is an acceptable representation of “no file.”
Accessing an Uploaded File
Suppose a model contains:
⧉
1 2 3 4 | |
Then:
⧉
1 | |
returns a file-related object rather than a plain path string.
Useful properties include:
⧉
1 2 3 | |
For example:
⧉
1 | |
might return:
⧉
1 | |
And:
⧉
1 | |
might return:
⧉
1 | |
The exact URL and storage behavior depend on the configured storage backend. Django’s file API abstracts access so code can work with local or custom storage.
File Paths Are Not Always Available
Local filesystem storage may provide a physical path:
⧉
1 | |
For example:
⧉
1 | |
However, remote storage may not have a meaningful local filesystem path.
For example, files stored in object storage may exist only remotely.
Code that assumes this always works:
⧉
1 | |
is less portable.
Prefer Django’s file interface:
⧉
1 2 3 | |
This allows the storage backend to decide how the file is retrieved.
Displaying Uploaded Images in Templates
Suppose a profile contains:
⧉
1 2 3 4 | |
A template can display it with:
⧉
1 2 3 4 5 6 | |
Do not use the {% static %} tag for uploaded media.
Incorrect:
⧉
1 2 3 4 5 6 | |
Correct:
⧉
1 2 3 4 | |
{% static %} is for application-owned static assets.
.url is used for stored media files.
Creating an Upload Form
A model form can expose a file field automatically.
Model:
⧉
1 2 3 4 5 6 7 8 9 | |
Form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Template:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
The important attribute is:
⧉
1 | |
Without it, the browser will not submit the uploaded file correctly.
Handling Files in a Function-Based View
Uploaded files are available through:
⧉
1 | |
Django places uploaded file data in request.FILES when a request contains a correctly encoded file upload.
When using a Django form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Notice:
⧉
1 2 3 4 | |
If request.FILES is omitted, the uploaded file will not be passed to the form.
The Upload Flow
A basic model-form upload follows this sequence:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Django separates HTTP upload handling from long-term file storage. Upload handlers process incoming data, while the storage API determines where saved files live.
Inspecting request.FILES
Without a model form, the file can be accessed directly:
⧉
1 2 3 4 5 6 7 | |
Common properties include:
⧉
1 2 3 | |
The uploaded object also provides methods for reading its contents.
Avoid Reading Large Files All at Once
This may be acceptable for very small files:
⧉
1 | |
For potentially large uploads, process the file in chunks:
⧉
1 2 | |
Django’s upload system supports handling uploaded data in memory or temporary files depending on upload size and configured upload handlers.
Using chunks() avoids unnecessarily loading an entire large file into application memory.
Saving a File Manually
Django provides a storage abstraction that can be used without a model.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The storage system decides where the file is written.
With local storage this may be:
⧉
1 | |
With another backend, the same API can point to remote storage. Django’s storage API is specifically designed to abstract filesystem and custom storage implementations.
FileSystemStorage
Django includes FileSystemStorage for storing files on the local filesystem.
Example:
⧉
1 2 3 4 5 6 7 8 9 | |
Save a file:
⧉
1 2 3 4 | |
Get its URL:
⧉
1 | |
Delete it:
⧉
1 | |
For most ordinary model uploads, you do not need to instantiate FileSystemStorage yourself.
Django’s configured default storage is usually enough.
The Default Storage
Code can access the configured default storage through:
⧉
1 2 3 | |
Use:
⧉
1 2 3 4 5 | |
This is preferable to directly manipulating files with Python’s filesystem functions when the application might later move to remote storage.
Instead of:
⧉
1 2 3 4 | |
prefer:
⧉
1 | |
when working with a Django-managed stored file.
Storage Configuration
Modern Django versions use the STORAGES setting to configure storage aliases.
A basic configuration can define default and static-file storage separately:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The default storage is commonly used for uploaded media.
The staticfiles storage handles static assets.
Django’s current storage API exposes configurable storage backends and includes filesystem, in-memory, and custom-storage support.
Different Storage for One Field
A specific field can use its own storage backend.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Django’s file fields support passing a storage object so individual fields can use storage different from the project default.
This can be useful for separating:
- public images
- private documents
- generated reports
- archived files
Serving Media During Development
For local development, Django can expose media files through the development server.
Project URL configuration:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
With:
⧉
1 2 | |
a file such as:
⧉
1 | |
can be requested at:
⧉
1 | |
This development helper is not a production file-serving strategy.
Media Files in Production
Production media handling is different from development.
Common options include:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
The important requirement is persistence.
If an application platform replaces or destroys its local filesystem during deployment or restart, uploaded files must not be stored only on that temporary filesystem.
A production storage system should match the deployment environment.
Django supports custom storage backends specifically so files can live outside the local filesystem when required.
Media on Object Storage
A common production architecture is:
⧉
1 2 3 4 5 6 7 | |
Later:
⧉
1 2 3 4 5 | |
The model still works with:
⧉
1 | |
while the storage backend may return a remote URL rather than:
⧉
1 | |
This is one of the main advantages of using Django’s storage abstraction instead of hard-coding filesystem paths.
Public and Private Media
Not every uploaded file should be publicly accessible.
Public media might include:
- product images
- public avatars
- article images
Private media might include:
- invoices
- medical documents
- contracts
- private messages
- identity documents
Do not assume that hiding a media URL from a template makes the file private.
If a file is served publicly at:
⧉
1 | |
anyone who obtains the URL may potentially request it.
Private files usually require a different access strategy, such as:
⧉
1 2 3 4 5 6 7 | |
or:
⧉
1 2 3 4 5 6 7 | |
Authorization should happen server-side.
A Protected Download View
For small files, Django can return a protected file response.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
This allows Django to check ownership before returning the file.
For large files or high-traffic systems, letting Django stream every protected file may be less efficient than using a storage system or web server that supports secure delegated downloads.
File Validation
Uploaded files should be treated as untrusted input.
A browser-provided filename or content type should not be considered proof of what the file actually contains.
Possible validation includes:
- maximum file size
- allowed extensions
- expected content type
- file signatures
- image validation
- image dimensions
- malware scanning
- document-specific parsing
Django’s security documentation explicitly warns that uploaded media can be dangerous if served incorrectly. For example, a file may satisfy image checks while also containing content that becomes dangerous when interpreted by a browser.
Validating File Size
A form can reject large files:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
This improves application-level validation.
Infrastructure may also need its own upload-size limits.
Validating File Extensions
Django provides file-extension validation.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
This checks the filename extension.
It does not prove that the file contents actually match that extension.
A file named:
⧉
1 | |
is not automatically a valid PDF simply because the name ends in .pdf.
Validating Images
ImageField performs image-specific validation, but uploaded files should still be treated carefully.
Example:
⧉
1 2 3 4 5 6 7 | |
Additional requirements may include:
- maximum dimensions
- maximum file size
- approved formats
- re-encoding images after upload
For applications accepting uploads from untrusted users, security should not rely only on the filename or client-provided MIME type. Django’s security documentation recommends careful deployment and serving of uploaded media.
Filename Collisions
Two users might upload files with the same name:
⧉
1 | |
Django storage backends are responsible for determining an available stored name.
You should generally not write application logic that assumes the final filename will always equal the original filename.
Use:
⧉
1 | |
after saving to obtain the actual stored name.
Do Not Trust Original Filenames
An original filename should normally be treated as user-controlled data.
Avoid using it blindly for:
- shell commands
- operating-system paths
- HTML output
- authorization
- determining file type
If the application needs predictable storage names, generate its own names.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 | |
A file might then be stored as:
⧉
1 2 | |
Replacing Uploaded Files
Suppose a profile already has:
⧉
1 | |
and the user uploads:
⧉
1 | |
Saving the new model field does not necessarily mean the old physical file should automatically disappear.
Applications that allow file replacement should decide explicitly how obsolete files are cleaned up.
Possible strategies include:
- delete the previous file after replacement
- periodically remove orphaned files
- keep old versions intentionally
- use lifecycle rules in remote storage
Be careful not to delete a file that is still referenced elsewhere.
Deleting Model Objects and Files
Deleting a database object does not always mean its underlying storage file should be removed automatically.
For example:
⧉
1 | |
may remove the database row while the stored file remains.
If cleanup is required, it must be implemented deliberately.
One approach is:
⧉
1 2 | |
Another approach is application-level cleanup triggered from an explicit service.
Signals such as post_delete are sometimes used, but they can hide side effects and require careful handling of shared files and transactions.
Avoid File Operations Inside Transactions When Possible
A database transaction can roll back database changes.
A file-storage operation may not roll back with it.
For example:
⧉
1 2 3 4 5 | |
or:
⧉
1 2 3 4 5 | |
Database state and file-storage state are separate systems.
Critical file workflows should account for this difference.
Working With Existing Files
A model field can open a stored file:
⧉
1 | |
Read:
⧉
1 | |
Close:
⧉
1 | |
A context manager is often clearer:
⧉
1 2 | |
For large files:
⧉
1 2 3 | |
Avoid loading large files fully into memory without a reason.
Saving Generated Files
Not every media file needs to come from a browser upload.
Applications may generate:
- PDF reports
- CSV exports
- thumbnails
- invoices
- transformed images
A generated file can be saved with Django’s file API.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
This lets the configured storage backend decide where the generated file is stored.
Direct Uploads Versus Django Uploads
For ordinary applications, files may travel through Django:
⧉
1 2 3 4 5 | |
For large uploads or cloud-heavy systems, another architecture may be preferable:
⧉
1 2 3 | |
with Django first issuing an authorized upload instruction or signed URL.
This prevents large file data from passing through the Django application server.
That architecture is more advanced, but it can improve scalability for large files.
Media and Backups
Uploaded files are application data.
Backing up only the database may not be enough.
For example, the database might contain:
⧉
1 | |
but if the media storage is lost, the actual image is gone.
A complete backup strategy may need:
⧉
1 2 3 | |
or storage-level replication/versioning.
The database and media storage should be treated as related but separate persistent resources.
Do Not Commit Media Files to Git
The media directory should normally not be part of application source control.
A .gitignore might contain:
⧉
1 | |
Uploaded files are runtime data rather than source code.
Test fixtures or deliberately committed sample files are a different case and should live in an appropriate test or source directory.
Testing File Uploads
Django tests can create an uploaded file with SimpleUploadedFile.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Tests should avoid writing permanent files into the real production media directory.
A temporary media directory or test-specific storage configuration is safer.
Testing Upload Forms
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Files are passed separately from ordinary form data.
Common Beginner Mistakes
Confusing Static and Media Files
Static:
⧉
1 2 3 | |
Media:
⧉
1 2 3 | |
Keep the systems separate.
Forgetting multipart/form-data
Incorrect:
⧉
1 | |
Correct:
⧉
1 2 3 4 | |
Forgetting request.FILES
Incorrect:
⧉
1 | |
Correct:
⧉
1 2 3 4 | |
Using {% static %} for Media
Incorrect:
⧉
1 | |
Correct:
⧉
1 | |
Hard-Coding Filesystem Paths
Avoid:
⧉
1 2 3 4 | |
Prefer the storage API:
⧉
1 | |
or:
⧉
1 | |
Assuming .path Always Exists
Remote storage may not provide a local filesystem path.
Use storage-independent file methods when possible.
Trusting File Extensions
A .jpg filename does not prove that the uploaded contents are a safe JPEG.
Validate according to the application’s risk level.
Storing Sensitive Files Publicly
A private document should not become accessible merely because somebody knows its media URL.
Use server-side authorization or private storage.
Using Development Media Serving in Production
The development helper:
⧉
1 2 3 4 | |
is not a production media-delivery system.
Assuming Deleting a Model Deletes the File
Database and storage cleanup are separate concerns.
Implement cleanup intentionally.
Assuming Storage Operations Roll Back
A database transaction does not automatically undo remote or filesystem operations.
Design workflows accordingly.
Storing Uploads on an Ephemeral Filesystem
If the production platform replaces its filesystem during deployment, locally stored media can disappear.
Use persistent storage.
A Complete Basic Example
Model:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Settings:
⧉
1 2 | |
Form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Template:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Detail template:
⧉
1 2 3 4 5 6 7 8 | |
Development URLs:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Possible project structure:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Recommended Media Workflow
For a basic application:
- Configure
MEDIA_ROOT. - Configure
MEDIA_URL. - Add
FileFieldorImageFieldto the model. - Choose an appropriate
upload_topath. - Create a form that accepts the file.
- Add
multipart/form-datato the HTML form. - Pass
request.FILESto the Django form. - Validate the uploaded file.
- Use the field’s
.urlwhen displaying it. - Serve files through Django only during development.
- Use persistent storage in production.
- Protect private files with server-side authorization.
- Plan how replaced and deleted files are cleaned up.
- Include media storage in backup planning.
Mini Reference
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
Basic settings:
⧉
1 2 | |
Basic model field:
⧉
1 2 3 | |
Basic image field:
⧉
1 2 3 | |
Basic form:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Basic view handling:
⧉
1 2 3 4 | |
Basic template output:
⧉
1 2 3 | |
Django media handling provides the tools needed to receive, store, retrieve, and manage files created or uploaded while an application is running.
The most important concepts are:
- media files are different from static files
MEDIA_ROOTdefines local media storageMEDIA_URLdefines the media URL prefixFileFieldrepresents general uploaded filesImageFieldprovides image-specific behaviorupload_tocontrols storage organization- uploaded files arrive through
request.FILES - HTML upload forms require
multipart/form-data - Django’s storage API separates application code from the physical storage system
- local development storage can later be replaced by remote storage
- sensitive media requires real authorization
- uploaded files must always be treated as untrusted data
- database deletion and file deletion are separate operations
- production media must live on persistent storage
- backups need to account for both database records and stored media files
For a small project, start with MEDIA_ROOT, MEDIA_URL, and Django’s default filesystem storage. As the application grows, the same file and storage APIs allow the project to move toward private media, remote object storage, signed URLs, and more advanced upload workflows without redesigning every model that contains a file.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.