skip to content

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