STATIC_URL, STATIC_ROOT, STATICFILES_DIRS, finders, and collectstatic
coreintermediateSTATIC_URL is the URL PREFIX static files are served under (e.g. "/static/") — never a filesystem path. STATICFILES_DIRS lists extra directories (beyond each app's own static/ folder) to also search for static files, typically a project-wide static/ directory. STATIC_ROOT is the single directory collectstatic copies EVERY static file into for deployment — never used in development, and never itself a source directory. Static finders are the pluggable lookup system (FileSystemFinder for STATICFILES_DIRS, AppDirectoriesFinder for each app's static/) that collectstatic (and the dev server's runserver static handling) uses to locate files across all these sources. collectstatic is the management command that runs the finders and copies everything they find into STATIC_ROOT, ready to be served by a real web server or CDN in production.
Think of it as
Static files in Django are deliberately split into "where they live during development" (scattered across every app's own static/ directory, plus STATICFILES_DIRS) versus "where they live in production" (one single STATIC_ROOT directory) — because development wants files organized per-app for maintainability, while production wants one flat, deployable directory a web server or CDN can serve directly without any Django-specific logic at request time. Finders are the abstraction that makes this split possible: rather than collectstatic (or the dev server) hardcoding "look in every app's static/ folder," it asks each configured finder to report what it can find, which is also why a custom finder (checking a CMS-managed directory, say) can participate in the exact same collection process. STATIC_URL being a URL prefix rather than a path is the other half of the same decoupling — application code and templates reference static files by URL ({% static "app.css" %}), never by filesystem location, so where those files physically live (STATIC_ROOT locally, an S3 bucket, a CDN origin) can change without touching a single template.
What we're doing: Configure static files for a project with both app-level and project-wide static assets, ready for a production deploy.
- 2
- STATICFILES_DIRS is for assets that don't belong to any single app — an app's OWN static files go in that app's own <app>/static/<app>/ directory instead, found automatically by AppDirectoriesFinder.
- 3
- STATIC_ROOT should be .gitignored — it's a fully regenerated build artifact, not source content.
Why this works: Keeping app-specific static files inside each app (found by AppDirectoriesFinder) while using STATICFILES_DIRS only for genuinely project-wide assets keeps a reusable app's static files bundled with it — dropping the app into another project brings its CSS/JS along automatically.
Setting STATIC_ROOT and STATICFILES_DIRS to the SAME directory
Wrong
Better
What you see: collectstatic raises an error (Django explicitly detects and refuses this configuration) or, in looser setups, silently mixes source and collected files in one directory — running collectstatic repeatedly re-copies files into the same place they were found, corrupting the distinction between source and build output.
Why: STATIC_ROOT must be a directory collectstatic fully owns and regenerates — it is documented as a destination, never a source finders should also be scanning. Django validates against exactly this overlap because treating the same directory as both erases the entire point of separating development sources from a production build artifact.
- Whole: STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "staticfiles"
- "/static/" — STATIC_URL: a URL prefix — never a filesystem path
- [BASE_DIR / "static"] — STATICFILES_DIRS: extra SOURCE directories, beyond each app's own static/
- BASE_DIR / "staticfiles" — STATIC_ROOT: the single collectstatic DESTINATION — regenerated, never a source
Static-file settings, what each is
Together
Remember: STATIC_URL is a URL prefix, never a path. STATICFILES_DIRS adds extra SOURCE directories beyond each app's own static/; STATIC_ROOT is the single, regenerated collectstatic DESTINATION — never the same directory as a source, and never used directly in development. Re-running collectstatic is a required deploy step whenever new static files are added, since production serves only what STATIC_ROOT already contains.
See also: production static architecture · locale and file settings · built in tags

