skip to content

Static & Media Files

How Django gathers and serves its own CSS, JavaScript and images, and where it stores the files users upload. Interviewers probe the split between static assets and untrusted media.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

17

In Django, what are STATIC_URL, STATIC_ROOT and STATICFILES_DIRS each for, and why should templates use the {% static %} tag?

level: juniorimportance: must knowfreq 72%

answer

  1. one is a URL, two are paths
  2. where files are gathered to
  3. project-wide assets outside apps
  4. ask the storage for the URL

basics

~20 s

STATIC_URL is the URL prefix static files are served under; STATIC_ROOT is the one directory collectstatic copies into; STATICFILES_DIRS lists extra project-wide source directories. {% static %} gets each URL from the staticfiles storage instead of hard-coding it.

solid answer

~40 s

`STATIC_URL` is a **URL** prefix such as `'static/'` (what `startproject` writes), and it must end with a slash. `STATIC_ROOT` is a **filesystem path**, the one output directory `collectstatic` fills for the web server or CDN; you never put source files there. `STATICFILES_DIRS` is a list of **extra source** directories, for assets that belong to the project rather than one app, such as a brand stylesheet in `assets/`; each app's own `static/` directory is found automatically. The `{% static 'css/site.css' %}` tag, after `{% load static %}`, asks the configured staticfiles storage for the URL when `django.contrib.staticfiles` is installed. That is why it keeps working when `STATIC_URL` moves to a CDN or the storage adds content hashes to file names, where a hard-coded `/static/css/site.css` breaks.

code

python · 14 lines
python
# settings.py for the museum site
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

INSTALLED_APPS = [
    "django.contrib.staticfiles",
    "exhibits",
    "tickets",
]

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "assets"]  # project-wide sources
STATIC_ROOT = BASE_DIR / "staticfiles"    # collectstatic output only

go deeper

for a junior

Remember which setting is a URL and which are directories, and always load static and use the {% static %} tag in templates.

for a middle

Explain that app static/ folders are found automatically, STATICFILES_DIRS is for project assets, and STATIC_ROOT is output only, with the checks that enforce it.

for a senior

Show how {% static %} delegating to the storage lets you move assets to a CDN or hashed names without touching templates.

for a principal

Decide where shared front-end assets live across many apps and how the static pipeline fits the team's front-end build.

## Three settings, two kinds of value `django.contrib.staticfiles` manages **static files**: CSS, JavaScript, fonts and images that ship with the code, as opposed to user uploads. Three settings drive it, and the classic confusion is that one of them is a URL while the other two are directories. | Setting | Kind | Global default | Purpose | |---|---|---|---| | `STATIC_URL` | URL prefix | `None` (`startproject` writes `'static/'`) | where browsers fetch static files from | | `STATIC_ROOT` | absolute directory | `None` | the **destination** `collectstatic` copies every file into | | `STATICFILES_DIRS` | list of directories | `[]` | extra **sources** beyond each app's `static/` folder | ## A museum site as the example Imagine a museum website with two apps, `exhibits` and `tickets`, plus project-wide branding: - `exhibits/static/exhibits/gallery.js` and `tickets/static/tickets/checkout.css` live inside their apps and are found automatically, because `AppDirectoriesFinder` looks in every installed app's `static/` directory. - `assets/brand/museum.css` and `assets/brand/logo.svg` belong to no single app, so the project lists `BASE_DIR / "assets"` in `STATICFILES_DIRS`. - `STATIC_ROOT = BASE_DIR / "staticfiles"` is an empty output directory. `manage.py collectstatic` copies every file from both kinds of source into it, keeping the relative paths, so production has one directory to serve. - `STATIC_URL = "static/"` means a browser asks for `/static/brand/logo.svg`. ## Rules the settings enforce Django's system checks catch the common wiring mistakes: 1. `STATIC_URL` must end with `/`; otherwise the URL checks report `urls.E006`. 2. `STATICFILES_DIRS` must **not** contain `STATIC_ROOT` (`staticfiles.E002`): the output directory is not a source. 3. A `STATICFILES_DIRS` entry that does not exist produces the warning `staticfiles.W004`. 4. Running `collectstatic` without `STATIC_ROOT` fails with `ImproperlyConfigured`: "You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path." A relative `STATIC_URL` such as `'static/'` is prefixed with the script name at runtime, so a site mounted under a sub-path still gets correct URLs. ## Where each setting is read Knowing which component reads which setting makes most static-file bugs easy to place: - **`STATIC_URL`** is read by `staticfiles_storage.url()`, and therefore by `{% static %}`, and by the development server's static handler to decide which requests are static. - **`STATIC_ROOT`** is read by the default `StaticFilesStorage` as its location, so `collectstatic` writes there; whatever serves production reads the same directory. - **`STATICFILES_DIRS`** is read only by `FileSystemFinder`, which the development server, `findstatic` and `collectstatic` all use. - **Each app's `static/` folder** is read by `AppDirectoriesFinder`, driven by `INSTALLED_APPS`. So a wrong `STATIC_URL` shows up as broken links in the HTML, a wrong `STATIC_ROOT` as an empty or misplaced output directory, and a wrong `STATICFILES_DIRS` as project assets missing in every environment. ## Why {% static %} instead of a literal path After `{% load static %}`, the tag `{% static 'brand/museum.css' %}` works like this: - With `django.contrib.staticfiles` in `INSTALLED_APPS`, it calls `staticfiles_storage.url(path)`, so whatever storage `STORAGES["staticfiles"]` names decides the final URL. - Without the app, it simply joins `STATIC_URL` and the quoted path. That indirection is the point. If the site later serves assets from a CDN host, or switches to a storage that writes content-hashed names like `museum.4f2a9c.css`, templates using the tag keep working with no edits. A literal `<link href="/static/brand/museum.css">` silently breaks in both cases. The tag also accepts a variable (`{% static image_path %}`) and can store its result with `as`. ## Common mistakes - Putting source files straight into `STATIC_ROOT`, where the next `collectstatic --clear` deletes them. - Using `STATIC_ROOT` as the only asset directory in development; runserver reads the sources through the finders, not `STATIC_ROOT`. - Hard-coding `/static/` in templates or JavaScript instead of building URLs with the tag.

  • Do you need to run collectstatic during local development?
    No. With `DEBUG = True`, the staticfiles version of `runserver` serves files directly from the finders, meaning each app's `static/` folder and `STATICFILES_DIRS`. `STATIC_ROOT` is only read by whatever serves the collected files in production.
  • What changes if django.contrib.staticfiles is not in INSTALLED_APPS?
    `{% static %}` falls back to joining `STATIC_URL` with the path, so no storage is consulted and hashed names or custom storages are ignored. You also lose `collectstatic`, `findstatic` and the development server's automatic static serving, which all come from the app.

saying these in an interview costs you the question

  • STATIC_ROOT is where you put your CSS while developing
  • STATICFILES_DIRS must list every app's static folder
  • Adding STATIC_ROOT to STATICFILES_DIRS so collected files are found
  • Hard-coding /static/ paths in templates is equivalent to {% static %}
  • STATIC_URL is a filesystem path
open as a page

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%

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.

open as a page

How do you serve a Django app's static files with WhiteNoise when it runs in a single container with no separate web server?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Install WhiteNoise, add WhiteNoiseMiddleware right after SecurityMiddleware, set STATIC_ROOT and STORAGES['staticfiles'] to CompressedManifestStaticFilesStorage, and run collectstatic while building the image so the files exist before the app starts.

open as a page

Why does a Django site's CSS load under runserver with DEBUG = True but return 404 as soon as DEBUG is set to False?

level: middleimportance: must knowfreq 66%

basics

~20 s

With DEBUG on, staticfiles' runserver serves files straight from the finders. With DEBUG off that handler is not installed and nothing serves STATIC_URL, so run collectstatic and serve STATIC_ROOT from a web server, CDN or static-serving middleware.

open as a page

In Django, how does ManifestStaticFilesStorage bust browser caches for static files, and what is stored in staticfiles.json?

level: middleimportance: must knowfreq 55%

basics

~20 s

ManifestStaticFilesStorage saves, during collectstatic, a copy of each static file named with a 12-character MD5 content hash and records original-to-hashed names in staticfiles.json; {% static %} resolves through that map, so a changed file gets a new URL.

open as a page

In a Django project, why does WhiteNoiseMiddleware go directly after SecurityMiddleware, and what happens when it receives a static file request?

level: middleimportance: must knowfreq 45%

basics

~20 s

WhiteNoiseMiddleware answers any request whose path matches a file it indexed at startup and returns without calling the rest of the stack. Placing it right after SecurityMiddleware keeps HTTPS redirects and security headers on assets while skipping every other middleware.

open as a page

In Django 6.1, how do you configure ManifestStaticFilesStorage in settings, and what happened to the STATICFILES_STORAGE setting?

level: juniorimportance: should knowfreq 40%

basics

~10 s

Set STORAGES['staticfiles']['BACKEND'] to 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' and keep a 'default' entry, because STORAGES replaces the built-in value wholesale. STATICFILES_STORAGE was deprecated in Django 4.2 and removed in 5.1.

open as a page

When two Django apps both ship static/css/styles.css, which file do the staticfiles finders pick, and why namespace static files by app name?

level: middleimportance: should knowfreq 40%

basics

~10 s

Django's finders return the first match: FileSystemFinder (STATICFILES_DIRS) before AppDirectoriesFinder, which walks apps in INSTALLED_APPS order. collectstatic keeps only that first file, so apps put assets under static/<app_name>/ to make paths unique.

open as a page

In Django, how do you store user avatars in remote object storage instead of MEDIA_ROOT, and what must the storage backend provide?

level: middleimportance: should knowfreq 40%

basics

~20 s

Point STORAGES['default'], or a named alias passed to the avatar field through a storage callable, at an object-storage backend. The backend is a deconstructible Storage subclass implementing _open and _save plus exists, url, delete and size; only path() stays unavailable.

open as a page

How does Django turn an uploaded file's original name into its final storage name, from upload_to through get_available_name()?

level: middleimportance: should knowfreq 45%

basics

~20 s

Django's FileField applies upload_to (a strftime string or a callable), the storage sanitises the name with get_valid_name(), and save() calls get_available_name(), which appends an underscore and seven random characters if the name is taken or too long.

open as a page

Which Cache-Control headers does WhiteNoise send for hashed versus unhashed Django static files, and how does it tell them apart?

level: middleimportance: should knowfreq 40%

basics

~20 s

WhiteNoise marks a file immutable, max-age=315360000, public, immutable, when stripping its hash and asking Django's staticfiles storage for the URL gives back the same name; other files get max-age=60, public by default (0 under DEBUG).

open as a page

A Django site renders fine locally but raises 'ValueError: Missing staticfiles manifest entry' in production; why, and how do you fix it?

level: seniorimportance: should knowfreq 45%

basics

~10 s

ManifestStaticFilesStorage consults staticfiles.json only when DEBUG is False, and manifest_strict makes a missing name raise ValueError. The file never reached the manifest the process loaded; fix the collectstatic step, not the flag.

open as a page

Why should a Django site never serve user uploads as trusted content from its own domain, and how do you serve MEDIA files safely?

level: seniorimportance: should knowfreq 45%

basics

~20 s

An upload can pass Django's ImageField check yet be interpreted as HTML or script; served from the site's own origin it runs with the site's privileges. Serve MEDIA_URL from a separate registrable domain, allowlist types and limit size.

open as a page

When a Django app serves static files through WhiteNoise, when should you put a CDN in front, and what must you configure so it works?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Add a CDN once asset traffic or distant users make serving from the Django process costly: point STATIC_URL at the CDN host, let the CDN pull from WhiteNoise at the origin, and rely on WhiteNoise's immutable headers for hashed files.

open as a page

When Django's ManifestStaticFilesStorage post-processes CSS, how are url() and @import references rewritten, and why can that break collectstatic?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

During collectstatic, ManifestStaticFilesStorage rewrites url(), @import and source-map references in CSS to the targets' hashed names before hashing the CSS itself; a reference to a file that was not collected raises ValueError and aborts the command.

open as a page

What does WhiteNoise's CompressedManifestStaticFilesStorage add on top of Django's ManifestStaticFilesStorage, and how are the compressed files served?

level: middleimportance: nice to knowfreq 30%

basics

~10 s

CompressedManifestStaticFilesStorage hashes files like Django's ManifestStaticFilesStorage, then writes .gz and, with the Brotli package, .br copies during collectstatic; WhiteNoiseMiddleware serves the smallest variant the browser's Accept-Encoding allows.

open as a page

On a re-run, how does Django's collectstatic decide which files to copy, and when do you need --clear, --link or --ignore?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Django's collectstatic copies each first-found file but skips an existing target at least as new as its source. It never deletes files whose source vanished; --clear wipes the destination first, --link symlinks instead of copying, --ignore skips glob patterns.

open as a page