Add Static Files in Django
Learn how to add static files like CSS, JS, and images to your Django projects in this hands-on tutorial. Step-by-step guidance, troubleshooting, and next steps included.
Focus: add static files css js django
You've built Django views, wired up URL patterns, and rendered data with templates — but your pages still look like unstyled HTML from 1999. The missing piece is static files: the CSS that makes your app beautiful, the JavaScript that makes it interactive, and the images that make it feel real. In this tutorial, you'll learn how to add static files: css, js, and images in Django, using best practices that scale from a toy project to production.
The problem this lesson solves
By default, Django does not serve static files. When you reference a CSS file in a template, the browser requests it, but Django returns a 404 — unless you configure it. If you've ever seen a page render with unstyled HTML, or a broken image icon, you've hit this exact wall. This lesson eliminates that pain: you'll learn how to organize static files, reference them in templates, and serve them correctly in both development and production.
Core concept / mental model
Think of static files as the non-changing assets of your site — CSS, JavaScript, images, fonts, and PDFs. They are called static because they don't change per request, unlike your dynamic HTML templates. Django separates these from templates for two powerful reasons:
- Performance: Static files can be served by a fast web server (like Nginx or CDN) without hitting Python at all.
- Maintainability: Keeping CSS/JS/images in their own
static/directory makes your project structure clear and deployable.
A mental model: your Django project is a restaurant. Templates are the menu (what you present to customers), views are the kitchen (preparing dishes), and static files are the décor, table settings, and music — they make the experience pleasant but aren't part of the meal itself.
Key definitions
- Static files: Assets like
style.css,app.js,logo.pngthat don't change per request. STATIC_URL: The URL prefix for static files, e.g.,/static/.STATICFILES_DIRS: A list of directories where Django looks for static files during development.STATIC_ROOT: The absolute filesystem path where Django collects all static files for production using thecollectstaticcommand.
How it works step by step
Here's the sequence Django follows when you configure static files correctly:
- Configure
STATIC_URLinsettings.py— the URL prefix for serving static files. This tells Django how to map a URL like/static/css/style.cssto a file on disk. - Set
STATICFILES_DIRS— a list of file-system directories to search for static files. This is especially useful for project-wide assets like a global stylesheet. - Create the
static/directory in your app (or project) and place files inside it. Django automatically finds static files in each app'sstatic/folder. - Use the
{% load static %}tag in templates to generate the correct URL for each asset. - Run
collectstaticwhen you deploy — this copies all static files intoSTATIC_ROOT, which your web server can serve directly.
Pro tip: In development, Django's built-in server serves static files automatically if your
DEBUG = Trueanddjango.contrib.staticfilesis inINSTALLED_APPS(it is by default). But in production, you must runcollectstaticand configure your web server — otherwise, your styles will vanish.
Hands-on walkthrough
Let's put this into practice. We'll add a global CSS file, a JavaScript file for interactivity, and an image to a simple Django project. Follow along in your terminal.
1. Set up the project structure
Ensure your project looks like this (we assume you've completed prior lessons on models, views, and templates):
myproject/
├── manage.py
├── myproject/
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── myapp/
├── views.py
├── templates/myapp/home.html
└── static/myapp/
├── css/style.css
├── js/main.js
└── img/logo.png
2. Update settings.py
Open myproject/settings.py and add the following near the bottom (after STATIC_URL = 'static/' which Django generates):
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static", # project-wide static directory
]
STATIC_ROOT = BASE_DIR / "staticfiles" # for production
STATICFILES_DIRStells Django to look in thestatic/directory at the project root for files that aren't tied to a specific app.STATIC_ROOTis the destination forcollectstatic.
3. Create the static files
Create a project-level static/ directory and add a global CSS file:
/* static/css/style.css */
body {
background-color: #f0f4f8;
font-family: Arial, sans-serif;
}
.btn-primary {
background-color: #0056b3;
color: white;
padding: 10px 20px;
text-decoration: none;
}
Create a small JavaScript file that adds an alert on button click:
// static/js/main.js
document.addEventListener('DOMContentLoaded', function() {
const button = document.querySelector('.btn-primary');
if (button) {
button.addEventListener('click', function() {
alert('Button clicked!');
});
}
});
And add an image to static/img/ — you can use your own logo or a placeholder like logo.png.
4. Update your template
Modify myapp/templates/myapp/home.html to load and use the static files:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<script src="{% static 'js/main.js' %}"></script>
</head>
<body>
<img src="{% static 'img/logo.png' %}" alt="Logo">
<h1>Welcome to My App</h1>
<a href="#" class="btn-primary">Click me</a>
</body>
</html>
5. Run the server and see it work
python manage.py runserver
Visit http://127.0.0.1:8000/. You should see the background color, the styled button, the logo image, and clicking the button shows an alert. If something's off, check the troubleshooting section below.
Complete example: app-level vs project-level static files
Here's a more complete scenario where we place static files inside an app:
# settings.py (partial)
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
STATIC_ROOT = BASE_DIR / "staticfiles"
In your app's templates/myapp/about.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<p>Static files work!</p>
</body>
</html>
If you place style.css inside myapp/static/css/style.css, you must reference it as myapp/css/style.css in the template — Django namespaces static files by the app name to avoid collisions. Use {% static 'myapp/css/style.css' %}.
Pro tip: Always use the
{% static %}template tag, never hardcode/static/...URLs. This way, if you changeSTATIC_URLlater, your templates automatically update.
Compare options / when to choose what
There are several ways to manage static files, and choosing the right one depends on your project size and deployment strategy:
| Option | Pros | Cons | Best for |
|---|---|---|---|
| App-level static folders | Keeps assets close to the app; Django automatically finds them | Can cause naming collisions if apps have same filename | Small/medium projects with per-app assets |
Project-level STATICFILES_DIRS |
Centralized; easy to share assets across apps | Requires explicit path config; extra step to set up | Projects with a shared design system |
CDN + whitenoise |
Great performance; no server config needed | Requires extra dependency; not ideal for local development | Production deployment |
| Django's built-in development serving | Zero config in dev; works out of the box | Not secure/performant for production | Development only |
When to choose what:
- For learning and small apps, developer server with STATICFILES_DIRS is straightforward.
- For production, use collectstatic and serve via a web server or whitenoise.
- For design assets shared across your whole site, prefer project-level static dirs.
Troubleshooting & edge cases
Common issues you'll encounter when adding static files:
-
"Static file not found" 404 in development — If
DEBUG = Falseor you forgotdjango.contrib.staticfilesinINSTALLED_APPS, the dev server stops serving static files. SetDEBUG = Truein development. -
Styles load but images don't — This often means you're using absolute URLs like
/static/img/logo.pnginstead of the{% static %}tag. Always use the tag to generate the correct path. -
collectstaticshows "0 static files copied" — YourSTATICFILES_DIRSmight point to a non-existent directory, or your app static folder isn't in a discovered app. Verify the paths insettings.pyand runpython manage.py collectstatic --dry-runto see what it finds. -
Files load in development but break in production — This is classic. In production, you must set
DEBUG = Falseand configure a web server to serveSTATIC_ROOT.whitenoisecan simplify this if you're on a platform like Heroku. -
Caching issues where old CSS/JS persists — Browsers cache static files aggressively. Rename files or use Django's
ManifestStaticFilesStorageto append content hashes to filenames.
Pro tip: If
DEBUG = Falsein your local environment while testing, you can still serve static files by addingdjango.views.static.serveto your URLconf, but that's insecure — only use it in development.
What you learned & what's next
You've just unlocked the ability to style and add interactivity to your Django apps. Specifically, you learned:
- The core concept of static files and why they're separate from templates.
- How to configure
STATIC_URL,STATICFILES_DIRS, andSTATIC_ROOTinsettings.py. - How to create project-level and app-level static directories.
- How to use the
{% load static %}tag in templates to link CSS, JS, and images. - How to run
collectstaticfor production and avoid common pitfalls.
Next up is Forms and User Input Handling — you'll learn how to build forms that collect data, validate it, and save it to your database. Static files will make those forms look professional and user-friendly.
Now that your app looks good, it's time to make it interactive. Go ahead and experiment by adding a new CSS class or a JavaScript function to your project — the more you practice, the more confident you'll become.
Practice recap
Try this mini-exercise: add a new CSS file that changes the background color on your existing page. Then add a JavaScript alert on button click to see interactivity. Finally, run python manage.py collectstatic and inspect where files are copied — this prepares you for deployment.
Common mistakes
- Hardcoding
/static/...URLs in templates instead of using{% static %}tag — breaks whenSTATIC_URLchanges. - Forgetting to set
STATICFILES_DIRSwhen using project-level static files — Django only searches app-level folders automatically. - Running
collectstaticwithout settingSTATIC_ROOT— results in an error. - Leaving
DEBUG = Truein production — Django will not serve static files safely, causing 404s.
Variations
- Use
django-storageswith cloud services like S3 to serve static files directly from a CDN. - Adopt
whitenoisemiddleware to serve static files efficiently in production with zero web server configuration. - Use Django's
ManifestStaticFilesStoragefor hashed filenames and better caching.
Real-world use cases
- A corporate website uses a global CSS file for consistent branding across all pages.
- An e-commerce store loads product images dynamically from the app's static folder.
- A data dashboard includes custom JavaScript to render charts and user interactions.
Key takeaways
- Static files are non-dynamic assets like CSS, JS, and images, served separately from templates.
- Configure
STATIC_URL,STATICFILES_DIRS, andSTATIC_ROOTinsettings.pyfor development and production. - Use the
{% load static %}template tag to reference static files correctly. - In production, run
collectstaticand serve files via a web server or CDN for performance. - Avoid naming collisions by using app-level static namespaces like
myapp/css/style.css.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.