# Static files
EDRI's API Listener serves files from a directory on disk — stylesheets, scripts, images, fonts — for any request URL that no `@api(...)` event claims. This page describes how a request reaches a static file, what the response contains, and where the limits are.
---
## Quick start
Point `EDRI_ASSETS_PATH` at a directory (default: `assets`, relative to the process working directory):
```bash
export EDRI_ASSETS_PATH=assets
```
```
assets/
└── static/
├── css/
│ └── style.css
├── js/
│ └── app.js
└── images/
└── logo.png
```
The URL path maps one-to-one onto the directory:
| URL | File |
|-----------------------------|------------------------------------|
| `/static/css/style.css` | `assets/static/css/style.css` |
| `/static/js/app.js` | `assets/static/js/app.js` |
| `/static/images/logo.png` | `assets/static/images/logo.png` |
Reference them from templates with root-absolute paths:
```html+jinja
```
Relative paths (`css/style.css`) resolve against the current page URL and break on nested routes such as `/user/group/456/12`. There is no template tag for static URLs; `{% url %}` only generates URLs for events.
---
(static-files-resolution)=
## How a request reaches a static file
```mermaid
flowchart TD
Req["HTTP request GET /static/css/style.css"] --> Route{"URL matches an @api route? (any method)"}
Route -->|yes| Event["event handling (405 if method not registered)"]
Route -->|no| Json{"JSON client? X-Requested-With: XMLHttpRequest or Accept lists application/json"}
Json -->|yes| J404["404 (JSON)"]
Json -->|no| Path{"path valid and inside EDRI_ASSETS_PATH?"}
Path -->|no| E400["400 (HTML)"]
Path -->|yes| File{"regular file?"}
File -->|no| E404["404 (HTML)"]
File -->|yes| Method{"GET or HEAD?"}
Method -->|yes| Serve["200 + file contents (headers only for HEAD)"]
Method -->|no| E405["405 (HTML) Allow: GET, HEAD"]
```
The lookup happens in the Listener, before any event is created. Routes always win, and the static fallback has three consequences:
* **A route on the path shadows the file.** If an event is registered at `/css` for `POST`, a `GET /css` returns `405 Method Not Allowed`, not a file named `assets/css`.
* **A dynamic segment shadows a whole level.** With an event at `/docs/{slug}`, the URL `/docs/readme.txt` is routed to that event with `slug="readme.txt"`; `assets/docs/readme.txt` is never served. Keep assets under a prefix no event uses (for example `/static/`).
* **JSON clients never get static files.** A request carrying `X-Requested-With: XMLHttpRequest`, or whose `Accept` header lists `application/json` (including `application/json, text/plain, */*`, the default of axios), receives a JSON `404` even when the file exists. A browser navigation, ``, `