Static Files

Reviewed & published by Brayan K

Static files in Django are the unchanging assets your site serves alongside HTML — CSS stylesheets, JavaScript, images, and fonts — and the staticfiles app provides a consistent way to locate, reference, and collect them for production.

Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll learn where static files live inside your apps, how to reference them in templates with the static tag, and how to gather them into one folder for deployment with collectstatic.

What You'll Learn in This Lesson

1 Where Static Files Live

Static files are assets that never change per request — CSS, JavaScript, images, and fonts. Django's staticfiles app (already in INSTALLED_APPS by default) expects each app to keep its assets in a folder named static inside the app.

The convention is to add an extra namespacing folder with the app's name, so the path becomes blog/static/blog/. This prevents two apps that both ship a style.css from overwriting each other when everything is collected together.

blog/
    static/
        blog/                 # <-- namespacing folder named after the app
            css/
                style.css
            js/
                main.js
            images/
                logo.png
    templates/
    views.py

The STATIC_URL setting in settings.py defines the public URL prefix Django uses when it generates links to these files:

# settings.py

INSTALLED_APPS = [
    "django.contrib.staticfiles",   # already here by default
    "blog",
]

STATIC_URL = "static/"   # browsers request files under /static/...
# Django combines STATIC_URL with the path you give the static tag to
# build the final URL the browser requests. Here's that logic in plain Python.

STATIC_URL = "static/"

def build_static_url(path):
    """Mimic how Django's static tag forms a URL."""
    prefix = "/" + STATIC_URL.strip("/") + "/"
    return prefix + path.lstrip("/")

print(build_static_url("blog/css/style.css"))
print(build_static_url("blog/images/logo.png"))

# ✅ Expected output:
# /static/blog/css/style.css
# /static/blog/images/logo.png

2 Loading Static Files in a Template

You should never hard-code /static/... URLs into your HTML. Instead, load Django's static template-tag library and use the {% static %} tag, which builds the correct URL from STATIC_URL for you.

First, put {% load static %} at the very top of the template. Then call {% static "blog/css/style.css" %} wherever you need a URL:

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
</head>
<body>
    <img src="{% static 'blog/images/logo.png' %}" alt="Logo">
    <script src="{% static 'blog/js/main.js' %}"></script>
</body>
</html>

When Django renders this template, each static tag is replaced with a real URL — for example {% static "blog/css/style.css" %} becomes /static/blog/css/style.css in the HTML the browser receives.

# We can simulate what the static tag does to a template line.
# In real Django the template engine handles this; here we fake it.

STATIC_URL = "/static/"

def render_static(path):
    """Pretend to be the {% static %} tag: return the resolved URL."""
    return STATIC_URL + path

# A line of template HTML before rendering:
template_line = '<link rel="stylesheet" href="{static}">'

resolved = template_line.format(static=render_static("blog/css/style.css"))
print(resolved)

# ✅ Expected output:
# <link rel="stylesheet" href="/static/blog/css/style.css">

3 collectstatic for Production

During development Django's server finds static files automatically, app by app. In production you want a single folder a fast web server can serve directly. That's what collectstatic builds.

You point STATIC_ROOT at the destination folder and, optionally, list extra project-wide asset folders in STATICFILES_DIRS:

# settings.py
from pathlib import Path

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

STATIC_URL = "static/"

# Where collectstatic copies everything for production:
STATIC_ROOT = BASE_DIR / "staticfiles"

# Extra folders (not tied to a single app) to also collect:
STATICFILES_DIRS = [
    BASE_DIR / "assets",
]

Then run the command as part of every deployment:

python manage.py collectstatic

# Django walks every app's static/ folder plus STATICFILES_DIRS
# and copies all of it into STATIC_ROOT (here: ./staticfiles/).
# Your web server then serves files from that one directory.
# A simplified model of what collectstatic does: gather files from many
# source folders into one destination list (the STATIC_ROOT folder).

app_static = {
    "blog": ["blog/css/style.css", "blog/js/main.js"],
    "shop": ["shop/css/cart.css"],
}
staticfiles_dirs = ["assets/favicon.ico"]

def collectstatic():
    collected = []
    for files in app_static.values():
        collected.extend(files)
    collected.extend(staticfiles_dirs)
    return collected

result = collectstatic()
print(f"{len(result)} static files copied to STATIC_ROOT:")
for path in result:
    print(" -", path)

# EXPECTED: 4 files listed, ending with assets/favicon.ico

# ✅ Expected output:
# 4 static files copied to STATIC_ROOT:
#  - blog/css/style.css
#  - blog/js/main.js
#  - shop/css/cart.css
#  - assets/favicon.ico

🎯 Your Turn: Build the Static Path

Fill in the blank so the function returns the correct namespaced path for the blog app's stylesheet, matching what you'd pass to the static tag.

# 🎯 YOUR TURN: replace ___ with the app name "blog"

def static_path(app_name, asset):
    # The static tag expects: <app>/<asset>
    return f"{app_name}/{asset}"

app = "___"
print(static_path(app, "css/style.css"))

# EXPECTED OUTPUT:
# blog/css/style.css

Common Errors (And How to Fix Them)

❌ TemplateSyntaxError: Invalid block tag 'static'

You used the static tag but forgot to load its library.

✅ Fix: add {% load static %} at the very top of the template before any static tag.

❌ 404 Not Found on a CSS or image in production

Your assets exist in apps but were never gathered into STATIC_ROOT.

✅ Fix: run python manage.py collectstatic on every deploy, and make sure your web server serves STATIC_ROOT.

❌ Django can't find a file you know exists

The folder nesting is wrong — the file isn't under app/static/app/.

✅ Fix: place assets in blog/static/blog/... and reference them as blog/..., not the bare filename.

📋 Quick Reference

Setting / TagWhat It Does
STATIC_URLPublic URL prefix for static files (e.g. static/)
STATIC_ROOTDestination folder collectstatic copies files into
STATICFILES_DIRSExtra project-wide source folders to collect from
{% load static %}Imports the static tag library into a template
{% static "path" %}Builds the full URL for an asset
collectstaticGathers all static files into STATIC_ROOT

🏆 Mini Challenge: Static Asset Linker

Write a helper that builds the rendered HTML link tags for a list of an app's CSS files, exactly as the static tag would after rendering.

# 🏆 Mini Challenge: turn a list of assets into rendered <link> tags.

STATIC_URL = "/static/"

def link_tags(app_name, css_files):
    tags = []
    for css in css_files:
        url = STATIC_URL + f"{app_name}/css/{css}"
        tags.append(f'<link rel="stylesheet" href="{url}">')
    return tags

for tag in link_tags("blog", ["style.css", "dark.css"]):
    print(tag)

# EXPECTED:
# <link rel="stylesheet" href="/static/blog/css/style.css">
# <link rel="stylesheet" href="/static/blog/css/dark.css">

❓ Frequently Asked Questions

Lesson complete — your assets are wired up!

You now know where static files live, how the static tag turns a namespaced path into a real URL, and how collectstatic, STATIC_ROOT, and STATICFILES_DIRS prepare your assets for production.

Practice quiz

What does the STATIC_URL setting define?

  • The filesystem folder collectstatic writes to
  • Extra source folders to collect from
  • The public URL prefix browsers use to request static files
  • The default app for serving media uploads

Answer: The public URL prefix browsers use to request static files. STATIC_URL (e.g. 'static/') is the URL prefix Django uses when building links to static files.

Which app must be in INSTALLED_APPS to use Django's static files handling?

  • django.contrib.staticfiles
  • django.contrib.staticserve
  • django.static.files
  • django.contrib.assets

Answer: django.contrib.staticfiles. django.contrib.staticfiles is included by default and powers the staticfiles system.

What does the collectstatic command do?

  • It minifies CSS and JavaScript automatically
  • It copies every app's static files into the single STATIC_ROOT folder
  • It deletes unused static files from your apps
  • It uploads files directly to a CDN

Answer: It copies every app's static files into the single STATIC_ROOT folder. collectstatic gathers files from each app and STATICFILES_DIRS into STATIC_ROOT for production.

Which setting names the single destination folder collectstatic writes into?

  • STATIC_URL
  • STATICFILES_DIRS
  • MEDIA_ROOT
  • STATIC_ROOT

Answer: STATIC_ROOT. STATIC_ROOT is the filesystem path collectstatic copies all static files into.

What must appear at the top of a template before using the static tag?

  • {% load static %}
  • {% static load %}
  • {% import static %}
  • {% include static %}

Answer: {% load static %}. Adding {% load static %} imports the tag library so {% static %} is available in that template.

In a template, how do you build the URL for an asset named blog/css/style.css?

  • {{ static 'blog/css/style.css' }}
  • {% url 'blog/css/style.css' %}
  • {% static 'blog/css/style.css' %}
  • {% asset 'blog/css/style.css' %}

Answer: {% static 'blog/css/style.css' %}. The {% static 'path' %} tag turns a relative path into the full URL using STATIC_URL.

What is STATICFILES_DIRS used for?

  • The destination collectstatic writes to
  • Extra project-wide source folders to collect static files from
  • The URL prefix for media uploads
  • A list of apps allowed to serve static files

Answer: Extra project-wide source folders to collect static files from. STATICFILES_DIRS lists additional source folders, not tied to a single app, that collectstatic reads from.

Why is the extra app-name folder used, as in blog/static/blog/?

  • It is required by Python's import system
  • It enables automatic CSS minification
  • It tells Django to compress the files
  • It namespaces each app's assets so files with the same name do not collide

Answer: It namespaces each app's assets so files with the same name do not collide. The namespacing folder keeps two apps' identically named files separate once collected together.

If you forget {% load static %} but use {% static %}, what happens?

  • The file is served from MEDIA_ROOT instead
  • Django silently returns an empty string
  • Django raises a TemplateSyntaxError about an invalid 'static' tag
  • The tag works because static is built in

Answer: Django raises a TemplateSyntaxError about an invalid 'static' tag. Without loading the library, Django reports the static tag is invalid (TemplateSyntaxError).

STATICFILES_DIRS and STATIC_ROOT should never point at the same directory because:

  • One is a source folder and the other is the destination collectstatic writes to
  • STATIC_ROOT must always be empty
  • Django only allows one static folder per project
  • STATICFILES_DIRS must live inside an app

Answer: One is a source folder and the other is the destination collectstatic writes to. STATICFILES_DIRS lists sources Django reads; STATIC_ROOT is the destination it writes to, so they must differ.

Continue this course

Frequently asked questions

What are static files in Django?

Static files are the unchanging assets your site serves alongside HTML: CSS stylesheets, JavaScript files, images, icons, and fonts. They are 'static' because Django doesn't generate them per request the way it generates HTML from templates — the same file is sent every time. Django's staticfiles app gives you a consistent way to organise these assets in your apps, reference them in templates, and collect them into one place for production.

What is the difference between STATIC_URL and STATIC_ROOT?

STATIC_URL is the public URL prefix browsers use to request static files, such as '/static/'. It appears in the HTML Django generates. STATIC_ROOT is a filesystem path — the single folder where the collectstatic command gathers every static file from your apps so a real web server can serve them in production. STATIC_URL is about the URL; STATIC_ROOT is about the disk location. They serve completely different roles.

What does the collectstatic command do?

The 'python manage.py collectstatic' command walks through every app's static/ folder and any folders listed in STATICFILES_DIRS, then copies all of those files into the single directory named by STATIC_ROOT. In production your web server (or a CDN) serves files straight from STATIC_ROOT, which is fast and avoids running Python for every CSS or image request. You run collectstatic as part of each deployment.

Why do I need the load static tag in my template?

Django templates don't know about the static tag by default. Adding the load static tag at the top of a template imports the template-tag library that provides the static helper, which turns a relative path like 'css/style.css' into the correct full URL using STATIC_URL. If you forget to load it, Django raises a TemplateSyntaxError saying the static tag is invalid, because the tag library was never imported.