Themes & UI Customization¶
Custom templates, CSS, and JavaScript overrides for the admin panel.
Custom CSS¶
Inline CSS¶
admin = Admin(
app=app,
engine=engine,
secret_key="...",
custom_css="""
.sidebar { background-color: #1e293b; }
.topbar { border-bottom: 2px solid #6366f1; }
""",
)
External CSS File¶
admin = Admin(
app=app,
engine=engine,
secret_key="...",
custom_css_url="/static/admin-overrides.css",
)
Custom JavaScript¶
Inline JS¶
admin = Admin(
app=app,
engine=engine,
secret_key="...",
custom_js="console.log('Admin loaded');",
)
External JS File¶
admin = Admin(
app=app,
engine=engine,
secret_key="...",
custom_js_url="/static/admin-overrides.js",
)
Environment Badge¶
Show an environment badge in the topbar:
admin = Admin(
app=app,
engine=engine,
secret_key="...",
environment_label="Production",
environment_color="danger", # info, success, warning, danger
)
Custom Templates¶
Override any Jinja2 template by placing files in your template directory and
passing the directory path to Admin via AdminConfig.template_dirs:
from fastapi_admin_kit import Admin
from fastapi_admin_kit.admin.admin_config import AdminConfig
admin = Admin(
app=app,
engine=engine,
secret_key="...",
config=AdminConfig(template_dirs=["my_templates/"]),
)
Directory paths — use the actual path, not just the folder name¶
template_dirs accepts a list of directory paths (relative or absolute), but
they are resolved relative to the current working directory of the running
process. A bare folder name like "custom_templates" only works if the server
happens to be started from a directory that contains that folder — otherwise
the custom templates are silently ignored and the built-ins are used.
So: don't rely on the folder name alone. Use the actual path. The safest
option is an absolute path derived from the module file location, so it works
no matter which directory uvicorn is started from:
from pathlib import Path
from fastapi_admin_kit import Admin
from fastapi_admin_kit.admin.admin_config import AdminConfig
# NOT reliable — a bare folder name resolves relative to the process CWD:
# admin = Admin(..., config=AdminConfig(template_dirs=["custom_templates"]))
# Reliable — the actual path, derived from this file's location:
custom_templates_dir = str(Path(__file__).resolve().parent / "custom_templates")
admin = Admin(
app=app,
engine=engine,
secret_key="...",
config=AdminConfig(template_dirs=[custom_templates_dir]),
)
print("Custom templates loaded from:", custom_templates_dir)
A fully hard-coded absolute path also works if you prefer:
Example lookup: for the repo's example, the custom template directory lives at
example/custom_templates/, and the example app registers
config=AdminConfig(template_dirs=[str(Path(__file__).resolve().parent / "custom_templates")])
(see example/example_custom_templates.py).
Files placed in the directory must mirror the built-in template structure, e.g.
admin/list.html for a global list override, or admin/<table_name>/list.html
for a per-model override.
Template hierarchy:
- Your custom templates (highest priority)
- Admin built-in templates
Next Steps¶
- Configuration — All configuration options
- Plugins — Extend via plugins