Menu & Sidebar

The sidebar is driven entirely by the menu list in settings.ADMINLTE. Each item is a dict, and the package runs every item through a filter pipeline that resolves named routes to URLs, computes the active state for the current request, and hides items behind a permission or callable.

A real menu example

ADMINLTE = {
    "menu": [
        {"text": "Dashboard", "url": "/", "icon": "bi bi-speedometer"},

        {"header": "CONTENT"},

        {"text": "Posts", "icon": "bi bi-file-post", "submenu": [
            {"text": "All posts", "route": "posts:index", "icon": "bi bi-circle"},
            {"text": "New post", "route": "posts:create", "icon": "bi bi-circle",
             "can": "blog.add_post"},          # hidden unless the user has the perm
        ]},

        {"text": "Reports", "route": ["report", {"type": "sales"}],
         "icon": "bi bi-graph-up", "label": "3", "label_color": "danger"},

        {"text": "Docs", "url": "https://adminlte.io", "icon": "bi bi-book", "target": "_blank"},
    ],
}

Item keys

KeyPurpose
textThe visible label of the item.
headerRenders a non-clickable section header instead of a link.
routeA named Django route; resolved via reverse(). Pass [name, {kwargs}] or [name, [args]] for parameters.
urlA raw URL (relative paths are prefixed with /; external/mailto:/tel: pass through unchanged).
iconAn icon class, e.g. bi bi-people (Bootstrap Icons).
icon_colorIcon colour modifier.
label / label_colorA badge after the item, with its contextual colour.
targetLink target, e.g. _blank.
activeExplicit active patterns (string or list of * wildcards), or a literal boolean.
canPermission string, list of strings, or a callable — the item is dropped if denied.
can_paramsObject passed to user.has_perm(perm, obj).
submenuA nested list of items, rendered as a collapsible treeview.
topnav / topnav_rightPlace the item in the left / right navbar instead of the sidebar.

The filter pipeline

Every item flows through an ordered list of filters (ADMINLTE["filters"]), each a class with a transform(item) -> item | None method; returning None drops the item. The defaults run in order:

FilterDoes
GateFilterDrops items the current user may not see (the can key), recursing into submenus.
HrefFilterResolves each item's href from route or url (request-independent).
ActiveFilterMarks an item active when request.path matches its patterns; a branch is active if any child is.
SearchFilterNormalises navbar-search items (request-independent).

Per-request filter callables

The can key accepts a callable that receives the current request — ideal for logic that doesn't fit a Django permission string. Define it in your settings module and reference it directly:

def is_staff(request):
    return request.user.is_authenticated and request.user.is_staff

ADMINLTE = {
    "menu": [
        {"text": "Admin tools", "route": "tools", "icon": "bi bi-tools", "can": is_staff},
        {"text": "Billing", "route": "billing", "can": ["billing.view", "billing.manage"]},
    ],
}

You can also add your own filter to the pipeline. Subclass BaseFilter (custom filters default to per_request = True, which is always safe) and add its dotted path to filters:

ADMINLTE = {
    "filters": [
        "django_adminlte4.menu.filters.GateFilter",
        "django_adminlte4.menu.filters.HrefFilter",
        "django_adminlte4.menu.filters.ActiveFilter",
        "django_adminlte4.menu.filters.SearchFilter",
        "myproject.menu.BadgeCountFilter",          # your own
    ],
}

Active-state behaviour

  • If you set active to a string or list, those patterns (with * wildcards) are matched against request.path.
  • A literal boolean active is respected as-is.
  • Otherwise patterns are derived from the item's url / resolved href, so route: items get active detection automatically.
  • A parent with a submenu becomes active (and opens) when any child is active; / only matches the home path, never everything.

Request-independent filters (HrefFilter, SearchFilter) run once per process (keyed by language for i18n_patterns); only the request-dependent ones (gate, active) re-run per request — so a large menu stays cheap.


AdminLTE 4 · Django port Edit on GitHub