In Django, what is the difference between MEDIA_ROOT and MEDIA_URL, and what does a FileField actually store in the database?
answer
- a directory and a URL prefix
- both empty by default
- the row holds a name, not bytes
- url comes from the storage
basics
~20 sMEDIA_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 linesfrom 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
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.
Explain how these settings feed FileSystemStorage's location and base_url, and why only the name in the row makes storage swappable.
Plan for orphaned files, per-replica disks and production serving, since Django neither deletes files with rows nor serves media outside DEBUG.
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.