Oxyde Admin Auto-generated admin panel for Oxyde ORM with zero boilerplate.
- Automatic CRUD - list, create, edit, delete from your Oxyde models
- Search & filters - text search across fields, column filters (FK, bool, string)
- Foreign key handling - select dropdowns with inline create dialog
- Many-to-many relations - multi-select widget with junction-table sync
- Enum & array fields -
Enumcolumns rendered as dropdowns,list[T]as array editors - Streaming export - CSV / JSON export of large tables in chunks, with row caps
- Bulk operations - bulk delete and update from the list view
- Authentication - pluggable sync/async callback, JWT-ready
- Theming - 3 presets, 17 colors, 8 surface palettes
- Multi-framework - FastAPI, Litestar, Sanic, Quart and Falcon adapters
- Python ≥ 3.10
oxyde≥ 0.5.0- One of the supported frameworks (
fastapi/litestar/sanic/quart/falcon) - An ASGI server (e.g.
uvicorn,hypercorn)
pip install oxyde-adminfrom fastapi import FastAPI
from oxyde import db
from oxyde_admin import FastAPIAdmin
from models import User, Post, Comment
admin = FastAPIAdmin(title="My Admin")
admin.register(User, list_display=["name", "email"], search_fields=["name", "email"])
admin.register(Post, list_display=["title", "is_published"], list_filter=["is_published"])
admin.register(Comment)
app = FastAPI(lifespan=db.lifespan(default="sqlite:///app.db"))
app.mount("/admin", admin.app)Open http://localhost:8000/admin/ and get a full CRUD interface for your models.
The admin ships its own SPA frontend, so no separate frontend build is required —
the static assets are served by the same mount.
All adapters accept the same constructor parameters:
| Parameter | Default | Description |
|---|---|---|
title |
"Oxyde Admin" |
Title shown in the UI |
prefix |
"/admin" |
URL prefix (FastAPI / Sanic / Quart / Falcon; on Litestar set the path via mount) |
preset |
Preset.AURA |
PrimeVue preset |
primary_color |
PrimaryColor.SKY |
Accent color |
surface |
Surface.SLATE |
Surface palette |
per_page |
100 |
Maximum page size for the list view |
export_chunk_size |
10_000 |
Rows per chunk while streaming an export |
max_export_rows |
100_000 |
Hard cap on total exported rows |
auth_check |
None |
Callable (request) -> bool, sync or async |
login_url |
None |
Where the UI redirects on 401 |
from oxyde_admin import FastAPIAdmin
admin = FastAPIAdmin(title="My Admin")
# register models...
app.mount("/admin", admin.app)from litestar import Litestar, asgi
from oxyde_admin import LitestarAdmin
admin = LitestarAdmin(title="My Admin")
# register models...
app = Litestar(
route_handlers=[
asgi(path="/admin", is_mount=True)(admin.app),
],
)from sanic import Sanic
from oxyde_admin import SanicAdmin
admin = SanicAdmin(title="My Admin", prefix="/admin")
# register models...
app = Sanic("MyApp")
admin.register_exception_handlers(app)
app.blueprint(admin.blueprint)from quart import Quart
from oxyde_admin import QuartAdmin
admin = QuartAdmin(title="My Admin", prefix="/admin")
# register models...
app = Quart(__name__)
admin.init_app(app)import falcon.asgi
from oxyde_admin import FalconAdmin
admin = FalconAdmin(title="My Admin", prefix="/admin")
# register models...
app = falcon.asgi.App()
admin.init_app(app)admin.register(
Post,
list_display=["title", "author_id", "is_published", "views"],
search_fields=["title", "content"],
list_filter=["author_id", "is_published"],
readonly_fields=["views"],
ordering=["-views"],
display_field="title",
column_labels={"author_id": "Author", "is_published": "Published"},
exportable=True,
group="Content",
icon="pi pi-file-edit",
)| Parameter | Description |
|---|---|
list_display |
Columns shown in the list view |
search_fields |
Fields included in text search |
list_filter |
Columns available as filters |
readonly_fields |
Fields disabled in the edit form |
ordering |
Default sort order (prefix - for descending) |
display_field |
Field used as label in FK dropdowns |
column_labels |
Custom column headers |
exportable |
Enable CSV/JSON export (default: True) |
group |
Sidebar group name |
icon |
Sidebar icon (PrimeIcons) |
You can also auto-register all models at once:
admin.register_all()
# or exclude specific models
admin.register_all(exclude={InternalModel})The admin reads field metadata from _db_meta and renders an appropriate widget:
| Type | Widget |
|---|---|
str / int / float |
Text / number input |
bool |
Toggle |
date / datetime |
Date / datetime picker |
UUID |
Text input |
Enum |
Dropdown with the enum members |
list[T] |
Array editor (chips for primitive item types) |
| Foreign key | Searchable dropdown with inline create dialog |
| Many-to-many | Multi-select; junction rows synced on save |
Tip: Foreign-key and M2M target models must also be registered. Use
display_fieldon the target model to control the label shown in dropdowns; otherwise the first string field (or the primary key) is used.
from oxyde_admin import Preset, PrimaryColor, Surface
admin = FastAPIAdmin(
title="My Admin",
preset=Preset.AURA,
primary_color=PrimaryColor.TEAL,
surface=Surface.ZINC,
)Presets: AURA, LARA, NORA
Colors: NOIR EMERALD GREEN LIME ORANGE AMBER YELLOW TEAL CYAN SKY BLUE INDIGO VIOLET PURPLE FUCHSIA PINK ROSE
Surfaces: SLATE GRAY ZINC NEUTRAL STONE SOHO VIVA OCEAN
Note: This section describes the current behavior. The authentication flow will be reworked in the future.
Pass an auth_check callback and a login_url:
async def check_admin(request) -> bool:
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
return await verify_admin_token(token)
admin = FastAPIAdmin(
auth_check=check_admin,
login_url="/auth/login",
)auth_checkmay be sync or async — the adapter detects this at runtime.GET <prefix>/api/configis intentionally not gated byauth_checkso the unauthenticated UI can readlogin_urland the theme on the login screen.- The frontend stores the token in
localStorageunder the keyadmin_tokenand sends it asAuthorization: Bearer <token>on every API request. On a401it clears the token and redirects tologin_url.
Your login_url endpoint must accept POST {"email": "...", "password": "..."}
and return {"token": "<...>"} on success, or a non-2xx response with
{"detail": "..."} on failure.
The admin UI talks to the backend through the following endpoints (relative to
the admin prefix):
GET /api/config
GET /api/models
GET /api/models/counts
GET /api/<model>/schema
GET /api/<model>?page=&per_page=&ordering=&search=&<filter>=
POST /api/<model>
GET /api/<model>/<pk>
PATCH /api/<model>/<pk>
DELETE /api/<model>/<pk>
GET /api/<model>/options?search=&limit=&include=
GET /api/<model>/export?format=csv|json&ids=&ordering=&search=
POST /api/<model>/bulk-delete { "ids": [...] }
POST /api/<model>/bulk-update { "ids": [...], "data": {...} }
<model> is the table name (e.g. users, not User). Schema responses
include x-db-* extensions (primary key, FK target, nullable, default,
db type, max length, enum members, array item type, M2M target / through)
that you can use to drive a custom UI — see
oxyde_admin/schema.py for the full list.
Working applications with auth, fixtures, FK / M2M relations, and enum / array
fields are in examples/ for each supported framework
(FastAPI, Litestar, Sanic, Quart, Falcon).
This project is licensed under the terms of the MIT license.



