Static Files
Static files - images, CSS, JavaScript, downloads - are served directly from disk without touching your endpoint code. FastAPI exposes the Starlette StaticFiles handler via app.mount, which maps a URL prefix to a directory and serves files with correct content types and conditional requests.
Mounting a Directory
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
def root():
return {"docs": "/docs", "logo": "/static/logo.png"}
After this, /static/logo.png serves static/logo.png, /static/css/app.css serves that file, and the directory listing behavior is disabled by default.
Useful Options
app.mount(
"/files",
StaticFiles(directory="downloads", check_dir=False),
name="files",
)
# serve with directory listing enabled:
app.mount(
"/media",
StaticFiles(directory="media", html=True),
name="media",
)
- check_dir=False - do not fail startup when the directory is missing yet.
- html=True - serve index.html and directory pages; useful for small SPA deployments.
- package_dir - serve files bundled inside a Python package.
Forcing a Download
StaticFiles serves inline; to trigger a browser download, use FileResponse with a filename:
from fastapi.responses import FileResponse
@app.get("/report.pdf")
def report():
return FileResponse("reports/report.pdf", filename="report.pdf")
SPAs and Reverse Proxies
For single-page apps, the cleanest setup is to serve the built bundle (index.html plus assets) from StaticFiles with html=True, and let a reverse proxy handle client-side routing fallbacks. In production, many teams serve static files entirely from the proxy or CDN instead.
Summary
Serve static content with app.mount("/static", StaticFiles(directory="static")) so file requests bypass your route handlers, use html=True for index-based serving, and use FileResponse when you need to force a download with a custom filename.