skip to content

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%

answer

  1. STATICFILES_FINDERS order
  2. first match wins
  3. INSTALLED_APPS order matters
  4. a folder named after the app
  5. findstatic shows the search

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.

solid answer

~40 s

`STATICFILES_FINDERS` defaults to `FileSystemFinder`, which searches `STATICFILES_DIRS`, then `AppDirectoriesFinder`, which searches each app's `static/` folder in `INSTALLED_APPS` order. `finders.find()` returns the **first** match, and `collectstatic` copies the first file for each destination path and skips the rest. So if `exhibits` and `tickets` both ship `static/css/styles.css`, whichever app is listed first wins everywhere, and a project file at the same path in `STATICFILES_DIRS` beats both. The fix is namespacing, as with templates: `exhibits/static/exhibits/css/styles.css` is referenced as `{% static 'exhibits/css/styles.css' %}`, so paths never collide. To debug, `manage.py findstatic css/styles.css` lists every match (`--first` for the winner; `-v 2` shows the searched locations). Since Django 6.0, `collectstatic` summarises conflicts as "skipped due to conflict" at verbosity 1 and lists each at 2.

code

bash · 9 lines
bash
$ python manage.py findstatic css/styles.css
Found 'css/styles.css' here:
  /srv/museum/assets/css/styles.css
  /srv/museum/exhibits/static/css/styles.css
  /srv/museum/tickets/static/css/styles.css

$ python manage.py findstatic css/styles.css --first
Found 'css/styles.css' here:
  /srv/museum/assets/css/styles.css

go deeper

for a junior

Put each app's static files under static/<app_name>/ and reference them with that prefix in {% static %}.

for a middle

Explain the finder order, first-match-wins in both find() and collectstatic, and use findstatic to prove which file is served.

for a senior

Spot silent collisions in collectstatic output, and use STATICFILES_DIRS precedence for deliberate, documented overrides of third-party assets.

for a principal

Set conventions for asset namespaces across teams so adding or reordering apps can never change what a page loads.

## Finders and their order A **finder** is a class that knows how to locate static files in one kind of place. `STATICFILES_FINDERS` lists the enabled finders; the default is: 1. **`FileSystemFinder`**, which searches the directories in `STATICFILES_DIRS`. 2. **`AppDirectoriesFinder`**, which searches the `static/` subdirectory of every app in `INSTALLED_APPS`, in the order the apps are listed. A third, `DefaultStorageFinder`, ships commented out in the default list; it searches the default file storage and is rarely used. The module-level function `django.contrib.staticfiles.finders.find(path)` asks each finder in turn and returns the **first** absolute path found. Both the development server and `findstatic` use it. ## How collectstatic resolves duplicates `collectstatic` walks the finders in the same order and lists every file. For each **destination path**, the relative path under `STATIC_ROOT`, it keeps the first file it meets and skips later ones; at `--verbosity 2` each skip is logged as "Found another file with the destination path ... It will be ignored since only the first encountered file is collected." So precedence is identical in development and production, which is good, but the loser is silently absent everywhere, which is not. For the museum site: | Source file | Destination path | Collected? | |---|---|---| | `assets/css/styles.css` (in `STATICFILES_DIRS`) | `css/styles.css` | yes, `FileSystemFinder` runs first | | `exhibits/static/css/styles.css` | `css/styles.css` | no, skipped as a conflict | | `tickets/static/css/styles.css` | `css/styles.css` | no, skipped as a conflict | | `exhibits/static/exhibits/css/styles.css` | `exhibits/css/styles.css` | yes, unique path | ## Namespacing The standard remedy mirrors template namespacing: inside each app, put static files in a folder named after the app. - `exhibits/static/exhibits/css/styles.css` becomes `exhibits/css/styles.css`. - `tickets/static/tickets/css/styles.css` becomes `tickets/css/styles.css`. - Templates reference `{% static 'exhibits/css/styles.css' %}`. Now no two apps can collide, and reordering `INSTALLED_APPS` cannot change which stylesheet a page gets. There is one deliberate use of precedence: **overriding** a third-party app's asset. Because `FileSystemFinder` runs first, a project file at `admin/css/base.css` in `STATICFILES_DIRS` replaces the admin's own copy. That is a conscious override, not an accident. ## A collision, step by step How this typically surfaces on the museum site: 1. The `exhibits` app has shipped `static/css/styles.css` for months, un-namespaced. 2. The `tickets` team adds its own `static/css/styles.css`, and `tickets` sits above `exhibits` in `INSTALLED_APPS`. 3. Gallery pages suddenly render with the checkout styles, in development and after the next `collectstatic`. 4. `findstatic css/styles.css` shows both files, with `tickets` first. 5. The fix moves each file under its app's namespace and updates the `{% static %}` paths; the old un-namespaced copy in `STATIC_ROOT` is removed with the next clean collect. No error was raised at any point, which is the real lesson: precedence resolves collisions silently. ## Prefixes in STATICFILES_DIRS An entry can be a `(prefix, path)` tuple, for example `("brand", BASE_DIR / "assets" / "brand")`. Files from that directory are collected under `brand/`, giving project-level assets their own namespace. The prefix must not end with a slash, or the check `staticfiles.E003` fails. ## Debugging which file wins - `python manage.py findstatic css/styles.css` prints every matching absolute path, in precedence order. - `--first` prints only the winning file. - `-v 2` also prints the locations that were searched. - `collectstatic` reports conflicts: since **Django 6.0**, verbosity 1 shows only a summary count, "N skipped due to conflict", and `--verbosity 2` lists each skipped file. ## Custom finders A project can add its own finder class to `STATICFILES_FINDERS`, subclassing `BaseFinder` and implementing `find()` and `list()`, for example to pull assets from a front-end build directory. Its position in the list sets its precedence like any other finder.

  • How would you deliberately override the admin's stylesheet?
    Place your file at the same relative path, for example `admin/css/base.css`, in a directory listed in `STATICFILES_DIRS`. `FileSystemFinder` runs before `AppDirectoriesFinder`, so your copy wins in development and is the one `collectstatic` collects. Keep such overrides few and documented, since they break silently when the upstream file changes.
  • Does reordering INSTALLED_APPS change which static file is served?
    It can, for colliding paths: `AppDirectoriesFinder` searches apps in `INSTALLED_APPS` order, so moving an app earlier makes its file win. With namespaced paths there are no collisions, and app order stops mattering for static files.

saying these in an interview costs you the question

  • Django merges or errors out when two apps ship the same static path
  • The last app in INSTALLED_APPS wins because it is loaded last
  • App static folders take precedence over STATICFILES_DIRS
  • Namespacing static files is only needed for third-party apps
  • collectstatic always prints a warning line for every conflicting file