Skip to content

Admin wiring

This page covers how the sidebar is built and attached to the admin: the context the template needs, the route map and page map the agent uses to navigate, and the two attachment paths.

The sidebar context

build_sidebar_context() assembles everything the sidebar template renders:

Key Source Used for
endpoint reverse("<namespace>:endpoint") The AG-UI endpoint the chat POSTs to.
title DJANGO_ADMIN_AGENT["TITLE"] The chat panel header.
auto_confirm DJANGO_ADMIN_AGENT["AUTO_CONFIRM"] Surfaced to the component as autoConfirm.
tool_display DJANGO_ADMIN_AGENT["TOOL_DISPLAY"] The data-tool-display attribute (minimal / compact / full).
theme, density, placement, text_animation the matching DJANGO_ADMIN_AGENT keys Themeable presentation attributes (theme / density / placement / data-text-animation), rendered only when set. See Configuration → Presentation.
theme_toggle DJANGO_ADMIN_AGENT["THEME_TOGGLE"] The data-theme-toggle attribute — opts into the built-in light⇄dark header toggle (off by default).
side DJANGO_ADMIN_AGENT["SIDE"] The data-side attribute for placement="sidebar" (left / right), rendered only when set.
icon_url DJANGO_ADMIN_AGENT["ICON_URL"] The data-icon-url attribute (header/launcher icon), rendered only when set.
strings_json DJANGO_ADMIN_AGENT["STRINGS"] Localized UI-string overrides serialized to JSON for the data-strings attribute, rendered only when set.
skills DJANGO_ADMIN_AGENT["SKILLS"] or build_skills() The skill catalog (chips + /-command palette), embedded as a json_script. See Configuration → Skills.
tools_url reverse("<namespace>:tools") (or None) URL of the server-tool label catalog, rendered as data-tools-url; the component fetches it to label server-tool cards. None when the server wasn't mounted.
threads_url reverse("<namespace>:threads") (or None) URL of the owner-scoped thread index, rendered as data-threads-url; the component's history drawer fetches it. None when no conversation store is configured (the sub-view is unmounted).
attachments_url reverse("<namespace>:attachments") (or None) URL of the upload endpoint, rendered as data-attachments-url; the composer posts files to it. None when no attachment store is configured (the sub-view is unmounted).
attachment_max_bytes, attachment_accept DJANGO_AG_UI upload guards When uploads are mounted, the server-side ATTACHMENT_MAX_BYTES / ATTACHMENT_ALLOWED_TYPES mirrored onto data-attachment-max-bytes / data-attachment-accept so the composer can reject files before upload. None (attribute omitted) when the corresponding limit is unset.
transcribe_url reverse("<namespace>:transcribe") (or None) URL of the transcription endpoint, rendered as data-transcribe-url; the mic button posts audio to it. None when no transcription backend is configured (the sub-view is unmounted).
bootstrap_url static("django_admin_agent/admin_agent.js") The ES-module entry point.
admin_base_url reverse("admin:index") (or /) Lets the frontend nav.* tools build changelist / changeform URLs without reversing named routes in the browser.
route_map build_route_map() The navigable-route manifest (see below).

The same helper backs both attachment paths, so the rendered sidebar is identical whichever you choose.

The sidebar template

templates/django_admin_agent/sidebar.html renders the <ag-ui-chat> custom element with the endpoint, title, auto-confirm, tool-display, and admin-base data attributes (plus data-slash-commands="true", and the optional theme / density / placement / data-text-animation attributes when their settings are set, plus data-tools-url when the catalog endpoint is mounted); embeds the route map and the skill catalog as two json_script blocks (#django-admin-agent-routes, #django-admin-agent-skills); and loads the bootstrap module. The bootstrap (static/django_admin_agent/admin_agent.js) then:

  1. Defines the <ag-ui-chat> custom element from the vendored bundle.
  2. Attaches the CSRF token as an X-CSRFToken header so the endpoint accepts POSTs under the logged-in admin session.
  3. Reads the auto-confirm flag and the route map off the element / page, setting el.routeMap.
  4. Reads the embedded skill catalog and calls el.setSkills(...), and sets el.skillContext to a provider that derives {path} / {selected_ids} placeholder values from the current page.
  5. Calls registerAdminTools(el) to register the frontend tools.

Server-tool card labels are not embedded — the component fetches them from data-tools-url (the <prefix>tools/ catalog endpoint, named <namespace>:tools), whose labels come from each tool's @tool(summary=).

The themeable attributes (theme / density / placement / data-text-animation) and data-tool-display are read by the Web Component itself; this template is the seam where the DJANGO_ADMIN_AGENT settings become element attributes.

The route map

build_route_map() walks admin.site._registry and emits, per registered model, a changelist route, an add route (when available), and a dynamic change route, each shaped for the Web Component's routeMap:

{ "id": "app.model.changelist", "path": "/admin/app/model/",
  "title": "Models", "group": "app" }

The change route's path is a :pk template, e.g. /admin/app/model/:pk/change/build_route_map reverses the change URL with a sentinel pk and swaps it for the :pk placeholder. When the agent calls navigate_to_route, the Web Component substitutes the :pk segment from the call's params, so the agent can edit a specific record by intent.

The agent calls the component's list_routes to discover destinations and navigate_to_route to jump to one, instead of guessing admin URL shapes. URLs are reverse-resolved (admin:<app>_<model>_changelist / _add / _change), so models without a resolvable route are simply skipped.

The page map

registerAdminTools also wires el.getPageMap — a per-run provider that returns a compact snapshot of the current page (field names/types/labels and button labels/handles, no values). The Web Component auto-injects it into each run's context, so the agent knows the page's surface without first calling get_form_state / get_visible_buttons. Exact live values are still fetched on demand via the ui_read.* tools.

Attaching the sidebar

Template tag

@register.inclusion_tag("django_admin_agent/sidebar.html")
def django_admin_agent_sidebar() -> dict[str, Any]: ...

{% load django_admin_agent %} then {% django_admin_agent_sidebar %} in your admin/base_site.html renders the sidebar. It is self-contained — it computes its own context via build_sidebar_context(), so the admin site does not need swapping. This is the common path.

SidebarAdminSite

SidebarAdminSite is a drop-in AdminSite whose each_context adds the sidebar context under the django_admin_agent key, so a base template can render the chat from {{ django_admin_agent }} without the tag. It calls super().each_context() first, so all standard admin context keys pass through unchanged. Use this when you already swap the admin site; otherwise prefer the template tag.

Unfold compatibility shim

The frontend tool handlers work against Django Unfold unchanged because Unfold preserves Django's structural DOM contracts. Two Unfold-only quirks are smoothed by static/django_admin_agent/unfold_shim.js, which no-ops on vanilla admin — see Unfold & vanilla support. There is no Python Unfold dependency: Unfold is detected at runtime in the browser, never imported at module load.