The SmallStack dashboard at /smallstack/ is a grid of at-a-glance cards. Each card is a widget — a small piece of Python that returns a headline, a detail line, and a link. You can add your own without editing base code.
If the widget is about data in a Django model you've registered with Explorer, add it to the admin class:
# apps/myapp/explorer.py
from apps.explorer.registry import explorer
from apps.smallstack.displays import DashboardWidget
from .admin import OrderAdmin
from .models import Order
class OrdersWidget(DashboardWidget):
title = "Orders"
icon = '<svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor"><path d="..."/></svg>'
order = 25
def get_data(self, model_class=None):
total = model_class.objects.count()
open_count = model_class.objects.filter(status="open").count()
return {
"headline": f"{total:,} orders",
"detail": f"{open_count} open",
}
OrderAdmin.explorer_dashboard_widgets = [OrdersWidget()]
explorer.register(Order, OrderAdmin, group="Sales")
The card automatically links to the Explorer list page for your model. Override with url_name = "myapp:orders_dashboard" to point somewhere else.
For widgets that aren't about a Django model (file-based docs, external systems, meta-widgets):
# apps/myapp/apps.py
from django.apps import AppConfig
class MyappConfig(AppConfig):
name = "apps.myapp"
def ready(self):
from apps.smallstack import dashboard
from .widgets import ServerHealthWidget
dashboard.register(ServerHealthWidget())
| Attribute | Purpose |
|---|---|
title |
Label on the card |
icon |
Inline SVG markup |
order |
Lower numbers appear first (defaults to 50) |
url_name |
Optional Django URL name to link to |
on_dashboard |
Set False to hide from the main dashboard but show on scoped views |
span |
Grid span (1 = normal, 2 = wide) |
group |
For standalone widgets — lets filtered views find them |
get_data() Returns¶For the default card widget, return a dict with:
{
"headline": "42 requests", # big text
"detail": "12 in last 24h", # small grey text
"status": "ok", # optional — adds a badge
}
Any additional keys (like extra) are passed through to the REST API but ignored by the HTML template. This lets you expose richer machine-readable data to API consumers without cluttering the card UI.
First-party widgets use these orders — leave gaps so your widgets slot in naturally:
| Order | Widget |
|---|---|
| 10 | Status |
| 20 | Activity |
| 30 | Users |
| 40 | Backups |
| 50 | Help & Docs |
| 60 | Explorer |
| 70+ | Add-on packages |
Widgets are also exposed as JSON for SPAs and external dashboards:
GET /api/dashboard/widgets/
Authorization: Bearer <staff-token>
Optional query params: ?group=Monitoring, ?app=heartbeat, ?dashboard_only=1.
Each response item includes the title, icon, URL, order, group, and the full data dict (headline, detail, plus any API extras).
The dashboard page itself is just a view that consumes the widget registry. You can build your own:
from django.views.generic import TemplateView
from apps.smallstack.dashboard import DashboardWidgetsMixin
class OpsDashboard(DashboardWidgetsMixin, TemplateView):
template_name = "myapp/ops.html"
widget_group = "Monitoring"
The mixin adds a widgets list to the template context. Loop over it and render however you want.
Set on_dashboard = False when you want a widget to show up on filtered pages (per-group, per-app) but not crowd the main dashboard. Example: a low-priority "slow queries" widget that's useful on the Monitoring group page but would be noise on the main /smallstack/ dashboard.
Choose the color mode for your app.
The accent color for your app.
Choose the font family that fits your app.
Choose the gray shade for your app.
Choose the border radius factor for your app.
Choose the page layout for your app.