FastPanel
A developer debug toolbar for FastAPI — see every SQL query, request detail, log record, and cache operation in a floating in-browser panel, with zero code changes to your routes.
Inspired by django-debug-toolbar, built for the modern async FastAPI stack.
Features
| Panel | What it shows |
|---|---|
| SQL | Every SQLAlchemy query: timing, formatted SQL, slow query highlighting (>100ms), calling location |
| Request | Method, URL, path/query params, headers, cookies, JSON body |
| Response | Status code, headers, content type, size |
| Performance | Total wall time, CPU time, panel overhead |
| Logging | All Python logging records at WARNING+ during the request |
| Cache | Hit/miss/set/delete events, hit rate, per-operation log |
| Headers | Full request and response header tables |
- Zero overhead in production (
enabled=Falseis a pure pass-through) - Two-line mount — no route decorators, no config files
- Async-native — works with
asyncio,asyncpg,aiosqlite - Pluggable custom panels via
AbstractPanel - No frontend build step — vanilla HTML/CSS/JS
Installation
pip install fastpanel
# With SQLAlchemy support (SQL panel):
pip install fastpanel[sqlalchemy]
# With Redis support (Cache panel):
pip install fastpanel[redis]
# Everything:
pip install fastpanel[all]
Or with Poetry:
poetry add fastpanel
poetry add fastpanel[sqlalchemy]
Quickstart
from fastapi import FastAPI
from fastpanel import FastPanel
import os
app = FastAPI()
# Your routes here...
# Mount FastPanel — two lines, that's it.
FastPanel(app, enabled=os.getenv("ENVIRONMENT") == "development")
Start your app and visit any HTML page. The toolbar appears in the bottom-right corner.
Tip: Set the
FASTPANEL_ENABLED=trueenvironment variable instead of hardcodingenabled=True. That way it's impossible to accidentally enable it in production.
Configuration
All settings can be passed to the FastPanel constructor or set via environment
variables (FASTPANEL_ prefix):
| Setting | Env var | Default | Description |
|---|---|---|---|
enabled |
FASTPANEL_ENABLED |
False |
Master switch. Never True in production. |
mount_path |
FASTPANEL_MOUNT_PATH |
/__fastpanel |
URL prefix for internal routes |
store_max_requests |
FASTPANEL_STORE_MAX_REQUESTS |
100 |
Max requests in memory (LRU) |
show_sql |
FASTPANEL_SHOW_SQL |
True |
Enable SQL panel |
show_logging |
FASTPANEL_SHOW_LOGGING |
True |
Enable Logging panel |
show_cache |
FASTPANEL_SHOW_CACHE |
True |
Enable Cache panel |
slow_query_ms |
FASTPANEL_SLOW_QUERY_MS |
100.0 |
SQL query slow threshold (ms) |
excluded_paths |
— | [] |
URL prefixes to skip (mount_path always excluded) |
extra_panels |
— | [] |
Custom panel classes to append |
FastPanel(
app,
enabled=True,
slow_query_ms=50.0,
store_max_requests=200,
excluded_paths=["/health", "/metrics"],
)
Cache Panel — CacheTracker
The Cache panel requires wrapping your cache client:
from fastpanel.panels.cache import CacheTracker, InMemoryCache
import redis.asyncio
# In-memory cache (development/testing):
cache = CacheTracker(InMemoryCache())
# Redis (production-like):
raw_redis = redis.asyncio.Redis.from_url("redis://localhost")
cache = CacheTracker(raw_redis)
# Use cache normally — all operations are tracked:
await cache.set("user:1", user_data)
value = await cache.get("user:1") # recorded as a hit
await cache.delete("user:1")
SQLAlchemy Integration
The SQL panel hooks into SQLAlchemy's global event system automatically — no changes needed to your engine or session setup. Works with:
AsyncEngine+AsyncSession(SQLAlchemy 2.x)create_async_engine("sqlite+aiosqlite://...")create_async_engine("postgresql+asyncpg://...")- Synchronous engines too
Note on async location tracking: With SQLAlchemy async engines, the query source location (
app/models.py:42) may show<unknown>. This is because async SQLAlchemy uses greenlets to run sync drivers, and the async call stack isn't visible from within the cursor execute event. This is a known limitation. See DEVLOG.md Step 8 for details.
Writing a Custom Panel
Subclass AbstractPanel:
from fastpanel.panels.base import AbstractPanel
from starlette.requests import Request
from starlette.responses import Response
from typing import Any
class TimingPanel(AbstractPanel):
panel_id = "timing"
title = "Timing"
def __init__(self) -> None:
super().__init__()
self._events: list[str] = []
def reset(self) -> None:
self._events = []
async def process_request(self, request: Request) -> None:
self._events.append(f"Request received: {request.url.path}")
def get_stats(self) -> str:
return str(len(self._events))
def get_data(self) -> dict[str, Any]:
return {"events": self._events}
Register it:
FastPanel(app, enabled=True, extra_panels=[TimingPanel])
⚠️ Security Warning
FastPanel must never be enabled in production.
The toolbar exposes internal request data (headers, SQL queries, log records)
via the /__fastpanel/api/ endpoint. While the endpoint requires a UUID4
request_id (not guessable), it is still sensitive data that should never be
exposed in a production environment.
Recommended pattern:
import os
FastPanel(
app,
enabled=os.getenv("ENVIRONMENT") == "development"
# or:
# enabled=os.getenv("FASTPANEL_ENABLED", "false").lower() == "true"
)
Ensure ENVIRONMENT=development (or FASTPANEL_ENABLED=true) is never set in
your production environment or deployment configuration.
When enabled=False:
- All
/__fastpanel/routes return404(not403— the 404 does not reveal that FastPanel is installed) - The middleware is a 4-line pass-through with zero overhead
- No panel data is collected or stored
How This Was Built
See DEVLOG.md for the full step-by-step build narrative — from
pyproject.toml to the final test run. Every design decision, architectural
trade-off, and gotcha is documented.
Roadmap
- WebSocket panel
- Async log streaming (live panel updates)
- Redis store backend (persist panel data across restarts)
- Tortoise-ORM support
- Browser extension version (persistent across sessions)
- Profiler panel (line-level
cProfileintegration) - Template panel (Jinja2 render times)
- Custom panel plugin registry
Contributing
See CONTRIBUTING.md for development setup, testing instructions, branch naming conventions, and the PR checklist.
License
MIT — © 2026 officialalkenes
Metadata
Release files for fastpanel 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastpanel-0.2.1.tar.gz | 44.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastpanel-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 100.7 kB
Release files / fastpanel-0.2.1.tar.gz
| Download URL | fastpanel-0.2.1.tar.gz |
|---|---|
| Size | 44.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
795f8a03c730301bd6db1aa5b48764207006b6e42baf7c278c3f8cbcf4cbdccf
|
|
BLAKE2b-256 checksum How to use checksums |
0472fdb5cc02b2d932abc1d5acc974d8915d8f2879857eefb0c1d661c54d5bff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.2 CPython/3.12.9 Darwin/25.3.0
|
Release files / fastpanel-0.2.1-py3-none-any.whl
| Download URL | fastpanel-0.2.1-py3-none-any.whl |
|---|---|
| Size | 56.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c06e4cf2de82f9a33879c7a0fb97b56881ca4f1fda39ddbb942989e9cd7bb06b
|
|
BLAKE2b-256 checksum How to use checksums |
b04d627cb7a89e2ed2a9e8715761d84d99cf5ead91cff468a88b8bbc0e80965e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.2 CPython/3.12.9 Darwin/25.3.0
|