Pluggable Backends

Run this documentation site on Flask, FastAPI or Quart with one environment variable, and learn what each backend unlocks.

Pluggable Backends

Run this documentation site on Flask, FastAPI or Quart with one environment variable, and learn what each backend unlocks.

---

.. llms_copy::Pluggable Backends

.. toc::

Overview

Dash 4.1+ introduces a pluggable server backend. Instead of being hard-wired to Flask, a Dash app can now be created on top of FastAPI or Quart, unlocking native async, websocket callbacks, and a transport surface that works well with the new MCP tooling shipping in Dash 4.3.

This boilerplate exposes the choice through a single environment variable so you can flip backends without editing code:

```bash

Default — fully featured, all integrations on

DASH_BACKEND=flask python run.py

Async backends (install the extra first)

pip install "dash[fastapi]" DASH_BACKEND=fastapi python run.py

pip install "dash[quart]" DASH_BACKEND=quart python run.py ```

The badge in the header and the navbar reflects whichever backend booted, so you always know what's running.

---

How it's wired

The active backend is resolved once in lib/backend.py and threaded into both run.py and the UI components:

```python

File: lib/backend.py

""" Backend selection helper for the Dash documentation boilerplate.

Dash 4.1+ supports pluggable backends: app = Dash(backend="flask" | "fastapi" | "quart")

This module owns the single source of truth for which backend the app is running on, so both run.py and UI components (e.g. the navbar badge) stay in sync.

Backend is selected via the `DASH_BACKEND environment variable. Falls back to flask` if unset or invalid. """ from __future__ import annotations

import os from dataclasses import dataclass from typing import Literal

try: from dotenv import load_dotenv load_dotenv() except ImportError: pass

BackendName = Literal["flask", "fastapi", "quart"]

SUPPORTED_BACKENDS: tuple[BackendName, ...] = ("flask", "fastapi", "quart") DEFAULT_BACKEND: BackendName = "flask"

@dataclass(frozen=True) class BackendInfo: name: BackendName label: str color: str # DMC color name icon: str # iconify icon id description: str is_async: bool

_BACKEND_INFO: dict[str, BackendInfo] = { "flask": BackendInfo( name="flask", label="Flask", color="gray", icon="simple-icons:flask", description="WSGI backend. Default. Full feature parity with Dash 3.x.", is_async=False, ), "fastapi": BackendInfo( name="fastapi", label="FastAPI", color="teal", icon="simple-icons:fastapi", description="ASGI backend. Adds websocket callbacks, async support and MCP-friendly transport.", is_async=True, ), "quart": BackendInfo( name="quart", label="Quart", color="violet", icon="simple-icons:python", description="ASGI backend with a Flask-compatible API surface.", is_async=True, ), }

def resolve_backend(name: str | None = None) -> BackendName: """Resolve the backend to use.

Reads `DASH_BACKEND env var when name` is not given. Unknown values fall back to the default backend rather than raising — the docs site should always boot. """ raw = (name or os.environ.get("DASH_BACKEND") or DEFAULT_BACKEND).strip().lower() if raw not in SUPPORTED_BACKENDS: return DEFAULT_BACKEND return raw # type: ignore[return-value]

def get_backend_info(name: str | None = None) -> BackendInfo: return _BACKEND_INFO[resolve_backend(name)] ```

run.py then passes the resolved name straight to the Dash() constructor:

```python from lib.backend import resolve_backend, get_backend_info

BACKEND = resolve_backend() BACKEND_INFO = get_backend_info(BACKEND) IS_FLASK = BACKEND == "flask"

app = Dash( __name__, backend=BACKEND, # <-- new in Dash 4.1 suppress_callback_exceptions=True, use_pages=True, ... ) app._backend_info = BACKEND_INFO ```

The only remaining Flask-only integration is the before_request analytics hook, which the boilerplate transparently swaps for a Starlette BaseHTTPMiddleware when the backend is FastAPI. add_llms_routes(app) from dash-improve-my-llms 2.0 auto-detects the backend and mounts its own router under all three — no IS_FLASK gate is needed for the AI/LLM surfaces anymore.

---

What you get on each backend

| Feature | Flask | FastAPI | Quart | |--|--|--|--| | Synchronous callbacks | yes | yes | yes | | async def callbacks | n/a | yes | yes | | Websocket callbacks (ctx.websocket) | no | yes (Dash 4.2+) | yes (Dash 4.2+) | | Persistent callbacks (Dash 4.2 rc) | no | yes | yes | | MCP server / mcp_enabled decorator (Dash 4.3) | partial | yes | yes | | add_llms_routes (dash-improve-my-llms 2.0) | yes | yes | yes | | /llms.txt, /robots.txt, /sitemap.xml | yes | yes | yes | | gunicorn deploy | yes | n/a | n/a | | uvicorn deploy | n/a | yes | yes |

The AI/LLM surfaces (/llms.txt, /robots.txt, /sitemap.xml, bot middleware, MCP bridge) work identically under all three backends — pick the one that matches the rest of your stack.

---

Choosing a backend

Flask (default)

The safe choice. Everything in this boilerplate works without changes, including the analytics tracker, the dash-improve-my-llms integration, and the SEO routes.

Deploy with gunicorn run:server -b 0.0.0.0:8550 (already wired into the included Dockerfile).

FastAPI

Pick this when you want:

```python from fastapi import FastAPI from dash import Dash

api = FastAPI() api.add_api_route("/healthz", lambda: {"ok": True})

app = Dash(__name__, server=api) # Dash detects FastAPI automatically ```

Deploy with uvicorn run:server --host 0.0.0.0 --port 8550.

Quart

A drop-in option for teams already on Quart, or who want async with Flask-style ergonomics (@server.before_request becomes @server.before_request returning a coroutine).

```python from quart import Quart from dash import Dash

server = Quart(__name__) app = Dash(__name__, server=server) ```

Deploy with uvicorn run:server --host 0.0.0.0 --port 8550.

---

Custom backends

Dash(backend=...) also accepts a subclass of dash.backends.base_server.BaseDashServer plus matching request/response adapters, so internal frameworks (a hardened ASGI server, an in-process test harness, etc.) can be plugged in without forking Dash. See the dash.backends module for the protocol.

---

Migration checklist for your own apps

1. Bump dash>=4.1.0 (and dash-mantine-components>=2.7.0 if you use DMC). 2. Replace any app.run_server(...) calls with app.run(...). 3. Audit every @app.server.before_request / @app.server.route — Flask-only. Wrap them in an if backend == "flask": guard or rewrite as FastAPI/Quart middleware. 4. Install the extra: pip install "dash[fastapi]" or pip install "dash[quart]". 5. Pass backend="fastapi" (or "quart") to Dash(...), or set DASH_BACKEND if you're using this boilerplate's helper. 6. Swap your process manager: gunicornuvicorn (or hypercorn).

---

What this site does today

This page is itself rendered by whichever backend DASH_BACKEND points at. The header badge tells you which one — change the env var, restart run.py, and the badge updates to match.

---

*Source: /backends*

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: