# 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: - Async callbacks (`async def update_table(...)`) - Real-time websocket callbacks for streaming UI updates - A first-class MCP server using Dash 4.3's framework utilities - Easy interop with an existing FastAPI app — just pass it in: ```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: `gunicorn` → `uvicorn` (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*