skip to content

In Django, what is the difference between MEDIA_ROOT and MEDIA_URL, and what does a FileField actually store in the database?

level: juniorimportance: must knowfreq 60%

answer

  1. a directory and a URL prefix
  2. both empty by default
  3. the row holds a name, not bytes
  4. url comes from the storage

basics

~20 s

MEDIA_ROOT is the filesystem directory where Django's default FileSystemStorage writes uploads; MEDIA_URL is the public URL prefix for them. A FileField stores only the file's name relative to the storage root, and .url asks the storage to build the link.

solid answer

~40 s

`MEDIA_ROOT` is an absolute filesystem path, and `MEDIA_URL` is the URL prefix under which those files are published; both default to empty strings. They are the defaults for the `location` and `base_url` of `FileSystemStorage`, the backend behind `STORAGES['default']`. A `FileField` or `ImageField` column holds only a string, the file's name relative to the storage root, such as `avatars/42/a1b2.png`; the bytes live in the storage. `instance.avatar.url` calls the storage's `url()`, which for the file system joins `MEDIA_URL` to that name. Django does not serve `MEDIA_ROOT` in production: the `static()` URL helper only adds a pattern when `DEBUG` is on, so production needs a web server or an object store. `MEDIA_URL` must differ from `STATIC_URL`, and deleting a model instance does not delete its file.

code

python · 11 lines
python
from django.conf import settings
from django.db import models


class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    avatar = models.ImageField(upload_to='avatars/%Y/%m/', blank=True)


# profile.avatar.name -> 'avatars/2026/09/me.png'  (what the row stores)
# profile.avatar.url  -> '/media/avatars/2026/09/me.png'  (MEDIA_URL + name)

go deeper

for a junior

Recall that MEDIA_ROOT is a directory, MEDIA_URL a URL prefix, and that the FileField column holds a relative name while .url builds the link.

for a middle

Explain how these settings feed FileSystemStorage's location and base_url, and why only the name in the row makes storage swappable.

for a senior

Plan for orphaned files, per-replica disks and production serving, since Django neither deletes files with rows nor serves media outside DEBUG.

for a principal

Decide early whether uploads live on local disk or shared storage, because that choice shapes deployments, backups and how many app instances you can run.

## Two settings, two jobs Django separates **static files** (assets shipped with the code) from **media files** (content users upload at run time). Media has two settings: | Setting | What it is | Default | Example | |---|---|---|---| | `MEDIA_ROOT` | absolute filesystem directory where uploads are written | `''` | `/var/app/media` | | `MEDIA_URL` | public URL prefix for those files | `''` | `/media/` or `https://files.example.net/` | Neither setting is used directly by your code. They are the defaults for the default storage backend, `django.core.files.storage.FileSystemStorage`, which reads `MEDIA_ROOT` as its `location` and `MEDIA_URL` as its `base_url` when you do not pass those options. That backend is what `STORAGES['default']` points to unless you change it. ## What the database stores A `FileField` (and `ImageField`, which subclasses it) is a string column. When a file is saved: 1. The field builds a name from `upload_to` and the uploaded file's name. 2. The storage writes the bytes and returns the final name it used. 3. That **relative name**, for example `avatars/42/a1b2c3.png`, is what goes into the row. The file content never enters the database. Reading `instance.avatar` gives a `FieldFile`; `instance.avatar.url` calls `storage.url(name)`, and `instance.avatar.open()` calls `storage.open(name)`. Because only the name is stored, you can move to a different storage backend without rewriting rows, as long as the same names exist there. ## Serving media - **Development**: add `static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)` from `django.conf.urls.static` to the URLconf. It returns a pattern only when `DEBUG` is `True` and `MEDIA_URL` is a local path; otherwise it returns an empty list. - **Production**: Django does not serve media. A web server serves `MEDIA_ROOT`, or an object-storage backend makes `url()` return links to the object store. ## Checks and traps - `MEDIA_URL` and `STATIC_URL` must be different; the staticfiles app raises `ImproperlyConfigured` when its storage or handler sees them equal. - `startproject` sets `STATIC_URL = 'static/'` but leaves both media settings at their empty defaults, so they must be added by hand. - `upload_to` must be relative; an absolute string such as `'/avatars'` fails the system check `fields.E202`. - `FileSystemStorage` writes new files with mode `0o644` by default (`FILE_UPLOAD_PERMISSIONS`); directories get the process's default mode unless `FILE_UPLOAD_DIRECTORY_PERMISSIONS` is set. - **Deleting a model instance does not delete its file**, and assigning a new file does not delete the old one. The docs leave orphan clean-up to you, for example a periodic management command, or an explicit `instance.avatar.delete(save=False)`. - In containers, `MEDIA_ROOT` inside the image is lost on redeploy and is not shared between replicas, which is why multi-instance deployments move uploads to shared or object storage. ## Putting it together ```python # settings.py MEDIA_ROOT = BASE_DIR / 'media' MEDIA_URL = '/media/' # urls.py (development only) from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` With this, a profile image saved as `avatars/42/a1b2c3.png` lands at `BASE_DIR / 'media' / 'avatars/42/a1b2c3.png'`, the row stores `avatars/42/a1b2c3.png`, and a template renders `{{ profile.avatar.url }}` as `/media/avatars/42/a1b2c3.png`.

  • A user replaces their avatar and later deletes their account; what happens to the image files?
    Both files stay in storage. Assigning a new file to the field does not remove the old one, and deleting the model instance does not delete its files. Remove the old file explicitly, for example `profile.avatar.delete(save=False)` before replacing it, and clean up orphans with a scheduled management command.
  • Why does the static() helper in the URLconf serve media locally but nothing in production?
    `django.conf.urls.static.static()` returns a URL pattern only when `DEBUG` is `True` and the prefix is a local path; otherwise it returns an empty list. Django's file-serving view is meant for development, so production media must be served by a web server or an object store.

saying these in an interview costs you the question

  • A FileField stores the uploaded file's bytes in the database column.
  • MEDIA_ROOT and STATIC_ROOT are interchangeable, so one directory can serve both.
  • Django serves MEDIA_URL in production once DEBUG is False.
  • Deleting a model instance also deletes its uploaded file.
  • MEDIA_URL defaults to /media/ in a new project.