Basic Configuration¶
This guide covers the settings you need to get a CRUDAdmin instance running, and the ones to revisit before production.
Prerequisites¶
- A working FastAPI application
- SQLAlchemy models defined (see Quick Start)
- CRUDAdmin installed (
uv add crudadmin)
Creating Your CRUDAdmin Instance¶
Minimal Setup¶
Two parameters are required, the session dependency and the secret key:
from crudadmin import CRUDAdmin
admin = CRUDAdmin(
session=get_session,
SECRET_KEY="your-secret-key-here",
)
Common Configuration¶
import os
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from crudadmin import CRUDAdmin
engine = create_async_engine("sqlite+aiosqlite:///./app.db")
async def get_session():
async with AsyncSession(engine) as session:
yield session
admin = CRUDAdmin(
session=get_session,
SECRET_KEY=os.environ["ADMIN_SECRET_KEY"],
mount_path="/admin",
theme="dark-theme",
admin_db_path=None,
initial_admin={
"username": "admin",
"password": os.environ["ADMIN_INITIAL_PASSWORD"],
},
)
Every parameter except session and SECRET_KEY has a default. mount_path defaults to /admin, theme to dark-theme, and with admin_db_path=None the admin database is created at ./crudadmin_data/admin.db.
Security Best Practices
Database Security: When using SQLite, add *.db, *.sqlite and crudadmin_data/ to your .gitignore.
Production Security:
- Use a strong, randomly generated
SECRET_KEYfrom the environment, and keep it stable. - With more than one worker, use the
redisordatabasesession backend (see Session Backends). - Behind a reverse proxy, set
AccessConfig(trusted_proxy_hops=...). - Restrict access with
AccessConfig(allowed_ips=...)orallowed_networks, and serve the admin over HTTPS.
Parameter Details¶
session (Callable, required)¶
An async dependency that yields a SQLAlchemy AsyncSession for your application database:
async def get_session():
async with AsyncSession(engine) as session:
yield session
admin = CRUDAdmin(session=get_session, SECRET_KEY=secret_key)
SECRET_KEY (str, required)¶
Keys the stored session and CSRF identifiers, so a copy of the session store can't be turned into working sessions. CRUDAdmin refuses to start without it. Changing it signs every admin out, so keep it stable and load it from the environment:
Generate one with python -c "import secrets; print(secrets.token_urlsafe(32))".
mount_path (str, default: "/admin")¶
The URL path the admin is served under. If you mount it somewhere else, pass the same path here, so links and the session cookie's path match:
admin = CRUDAdmin(
session=get_session,
SECRET_KEY=key,
mount_path="/dashboard",
)
app.mount("/dashboard", admin.app)
theme (str, default: "dark-theme")¶
"dark-theme" or "light-theme".
admin_db_path / admin_db_url¶
Where the admin database lives: admin users, the event log and, with SessionConfig(backend="database"), sessions and lockout counters. By default it's SQLite at ./crudadmin_data/admin.db. Use admin_db_path for another SQLite file, or admin_db_url for any async SQLAlchemy URL:
admin = CRUDAdmin(
session=get_session,
SECRET_KEY=key,
admin_db_url="postgresql+asyncpg://user:pass@localhost/admin",
)
initial_admin (dict, default: None)¶
Creates a superuser during admin.initialize() if no admin exists yet. See Managing Admin Users.
sessions (SessionConfig)¶
How sessions are kept. See Session Backends.
from crudadmin import SessionConfig
sessions = SessionConfig(backend="database", timeout_minutes=60)
| Field | Default | Meaning |
|---|---|---|
backend |
"memory" |
"memory", "redis" or "database" |
redis |
None |
RedisConfig or dict for the redis backend |
timeout_minutes |
30 |
Idle time after which a session ends |
max_per_admin |
5 |
Sessions an admin may hold at once; the oldest ends past this |
cleanup_interval_minutes |
15 |
How often idle sessions are swept |
secure_cookies |
True |
Send session cookies over HTTPS only |
access (AccessConfig)¶
Who may reach the admin, and the login lockout.
from crudadmin import AccessConfig
access = AccessConfig(allowed_networks=["10.0.0.0/8"], enforce_https=True)
| Field | Default | Meaning |
|---|---|---|
allowed_ips / allowed_networks |
[] |
Only these addresses reach the admin, login page included |
enforce_https / https_port |
False / 443 |
Redirect HTTP requests to HTTPS |
trusted_proxy_hops |
0 |
Reverse proxies in front of the app; with 0, X-Forwarded-For is ignored |
lockout |
crudadmin's | A crudauth.ratelimit.LockoutConfig for the login lockout |
Login lockout defaults to 5 failures, a first lock of one minute, and at most five minutes. A successful login clears the counters. Anyone who knows an admin's username can trigger a lockout, which is why the cap is short and why allowed_ips is worth setting.
Before 0.7 each of these fields was a CRUDAdmin argument of its own (session_backend, redis_config, session_timeout_minutes, max_sessions_per_user, cleanup_interval_minutes, secure_cookies, allowed_ips, allowed_networks, enforce_https, https_port, trusted_proxy_hops, lockout). They still work, with a DeprecationWarning; passing one together with the config object it belongs to raises.
Requests that change data need the session's CSRF token in an X-CSRF-Token header. The interface's bundled admin.js sends it for forms, htmx requests and fetch calls, and logging out is a POST.
track_events (bool, default: False)¶
Records logins, failed logins, lockouts and every create, update and delete in the event log, visible to superusers.
FastAPI Integration¶
Call admin.initialize() on startup and admin.shutdown() on shutdown:
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await admin.initialize()
yield
await admin.shutdown()
app = FastAPI(lifespan=lifespan)
app.mount("/admin", admin.app)
initialize() creates the admin tables, adds columns introduced by newer versions to existing ones, opens the session store, and creates the initial admin. shutdown() closes the session store and any Redis client CRUDAdmin opened.
Development vs Production¶
Development¶
admin = CRUDAdmin(
session=get_session,
SECRET_KEY="dev-key-change-in-production",
sessions=SessionConfig(secure_cookies=False),
initial_admin={
"username": "admin",
"password": "admin123",
},
)
SessionConfig(secure_cookies=False) lets the session cookie work over plain HTTP. Chrome and Firefox already accept secure cookies on http://localhost; other hosts, and Safari, need this in development.
Production¶
admin = CRUDAdmin(
session=get_session,
SECRET_KEY=os.environ["ADMIN_SECRET_KEY"],
sessions=SessionConfig(backend="database"),
access=AccessConfig(
trusted_proxy_hops=1,
allowed_networks=["10.0.0.0/8"],
enforce_https=True,
),
track_events=True,
)
With the memory backend CRUDAdmin logs a warning at startup, because sessions and lockout counters aren't shared between workers.
Next Steps¶
- Add Models to create your admin interface
- Set up Admin Users for access control
- Learn the Interface to manage your data