-
dash/dash.py- MainDashapplication class (~2000 lines). Orchestrates the server backend, layout management, callback registration, routing, and asset serving. Key methods:layoutproperty,callback(),clientside_callback(),run(). -
dash/backends/- Server backend implementations. See Server Backends section for details. -
dash/_callback.py- Callback registration and execution. Containscallback()decorator (usable as@dash.callbackwithout app instance),clientside_callback(), andregister_callback()which inserts callbacks into the callback map. -
dash/dependencies.py- Dependency classes for callbacks:Input- Triggers callback when value changesOutput- Component property to update (supportsallow_duplicate=True)State- Read value without triggering callbackClientsideFunction- Reference to JS function for clientside callbacks- Wildcards:
MATCH,ALL,ALLSMALLERfor pattern-matching IDs
-
dash/development/base_component.py-Componentbase class withComponentMetametaclass. All Dash components inherit from this. Components auto-register inComponentRegistryand serialize to JSON viato_plotly_json(). -
dash/_pages.py- Multi-page app support.PAGE_REGISTRYholds registered pages,register_page()decorator registers page modules with routes.
The layout defines the UI as a tree of components:
app.layout = html.Div([
dcc.Input(id='input', value='initial'),
html.Div(id='output')
])- Static layout: Assigned directly as a component tree
- Dynamic layout: Assigned as a function that returns components (called on each page load, useful for per-session state)
- Layout is serialized to JSON and sent to the React frontend via
/_dash-layout - Components can contain other components via
childrenprop - Component IDs can be strings or dicts (for pattern-matching callbacks)
@app.callback or @dash.callback:
@app.callback(Output('output', 'children'), Input('input', 'value'))
def update(value):
return f'You entered: {value}'Server-side Python function called when inputs change. Outputs update component properties.
app.clientside_callback:
app.clientside_callback(
"""function(value) { return 'You entered: ' + value; }""",
Output('output', 'children'),
Input('input', 'value')
)JavaScript function runs in browser. Faster for simple transformations, no server round-trip. Can reference window.dash_clientside.namespace.function_name or inline JS string.
background=True:
@app.callback(Output('output', 'children'), Input('btn', 'n_clicks'),
background=True, manager=diskcache_manager,
running=[(Output('btn', 'disabled'), True, False)],
progress=[Output('progress', 'value')])
def compute(set_progress, n_clicks):
for i in range(10):
set_progress(i * 10)
time.sleep(1)
return 'Done'Callbacks executed in separate process via Celery or Diskcache manager. Supports progress updates, running state changes, and cancel inputs. See Background Callbacks section for details.
@app.callback(
Output({'type': 'output', 'index': MATCH}, 'children'),
Input({'type': 'input', 'index': MATCH}, 'value')
)
def update(value):
return valueUse dict IDs with wildcards (MATCH, ALL, ALLSMALLER) to target dynamically-generated components.
/_dash-layout- Returns initial component tree as JSON/_dash-dependencies- Returns callback definitions/_dash-update-component- Executes callbacks, returns updated props/_dash-component-suites/<package>/<path>- Serves component JS/CSS assets/assets/<path>- Serves static assets from app's assets folder
Dash supports multiple web server backends. The backend abstraction is in dash/backends/.
| Backend | Type | Install | Use Case |
|---|---|---|---|
| Flask (default) | WSGI (sync) | pip install dash |
Standard deployments, simplicity |
| Quart | ASGI (async) | pip install dash[quart] |
Async callbacks, WebSocket support |
| FastAPI | ASGI (async) | pip install dash[fastapi] |
OpenAPI docs, async, modern Python |
Default (Flask):
from dash import Dash
app = Dash(__name__)With existing server instance:
from flask import Flask
from dash import Dash
server = Flask(__name__)
app = Dash(__name__, server=server)Quart backend:
from quart import Quart
from dash import Dash
server = Quart(__name__)
app = Dash(__name__, server=server)FastAPI backend:
from fastapi import FastAPI
from dash import Dash
server = FastAPI()
app = Dash(__name__, server=server)
# Run with: uvicorn module:app.server --reloadThe backend system uses an abstract interface:
-
BaseDashServer(dash/backends/base_server.py) - Abstract base class defining the server interface. All backends implement this. -
RequestAdapter- Normalizes HTTP request objects across frameworks. Provides unified access toargs,cookies,headers,get_json(), etc. -
ResponseAdapter- Normalizes response creation. Handlesset_cookie(),set_header(),set_response(). -
get_backend(name)- Factory function to get backend class by name ("flask","quart","fastapi"). -
get_server_type(server)- Auto-detects backend from a server instance.
Flask (dash/backends/_flask.py):
FlaskDashServer- Wraps Flask appFlaskRequestAdapter- Usesflask.requestproxyFlaskResponseAdapter- Usesflask.Response- Compression via
flask-compress
Quart (dash/backends/_quart.py):
QuartDashServer- Wraps Quart app (async Flask API)QuartRequestAdapter- Usesquart.requestproxyQuartResponseAdapter- Usesquart.Response- All route handlers are
async def - Compression via
quart-compress
FastAPI (dash/backends/_fastapi.py):
FastAPIDashServer- Wraps FastAPI appFastAPIRequestAdapter- Uses context variable for current requestFastAPIResponseAdapter- Uses Starlette responsesDashMiddleware- Consolidated ASGI middleware for request handling- Runs with uvicorn, supports hot reload
- Built-in GZip compression
All backends implement:
class BaseDashServer(ABC):
def create_app(name, config) -> server # Create new server
def add_url_rule(rule, view_func, ...) # Register routes
def before_request(func) # Request hooks
def after_request(func) # Response hooks
def run(dash_app, host, port, debug) # Start dev server
def make_response(data, mimetype, status) # Create response
def jsonify(obj) # JSON response
def setup_index(dash_app) # Register / route
def serve_callback(dash_app) # Callback endpoint
def setup_component_suites(dash_app) # JS/CSS servingapp = Dash(__name__)
# Get the underlying server
app.server # Flask/Quart/FastAPI instance
# Get the backend wrapper
app.backend # BaseDashServer subclass instance
app.backend.server_type # "flask", "quart", or "fastapi"
# Access request in callbacks
from dash import dash
dash.get_app().backend.request_adapter() # RequestAdapter instancedash/dash-renderer/src/ contains the TypeScript/React frontend. See RENDERER.md for detailed documentation on:
- Layout traversal (
crawlLayout) andchildren_props - Component resolution from
window[namespace][type] - Callback triggering via
setPropsandnotifyObservers - Redux store structure (layout, paths, callbacks, graphs)
- Observer system for callback processing
window.dash_clientsideAPIwindow.dash_component_apiAPI
Dash supports multiple React versions. Configured in dash/_dash_renderer.py.
Available versions: 18.3.1 (default), 18.2.0, 16.14.0
Set via environment variable (experimental):
REACT_VERSION=16.14.0 python app.pyOr programmatically before creating the app:
from dash._dash_renderer import _set_react_version
_set_react_version("16.14.0")
from dash import Dash
app = Dash(__name__)This is useful for compatibility with older component libraries that require React 16.
Multi-page apps use dash/_pages.py with automatic routing via dcc.Location.
Each page module calls register_page():
# pages/analytics.py
from dash import register_page, html
register_page(__name__) # infers path /analytics from module name
layout = html.Div("Analytics page")PAGE_REGISTRY-OrderedDictstoring all registered pages with metadataregister_page(module, path=None, ...)- Registers page with inferred or explicit path, title, description, image
When use_pages=True, Dash injects page_container as the layout (dash/dash.py:148-158):
page_container = html.Div([
dcc.Location(id="_pages_location", refresh="callback-nav"),
html.Div(id="_pages_content"), # current page layout injected here
dcc.Store(id="_pages_store"), # stores page title/metadata
])dcc.Locationtracks browser URL changes- Internal callback listens to
pathnameandsearchinputs _path_to_page()matches URL to registered page inPAGE_REGISTRY- Page layout injected into
_pages_contentdiv
Pages can capture URL variables:
register_page(__name__, path_template="/asset/<asset_id>")
def layout(asset_id=None):
return html.Div(f"Asset: {asset_id}")_parse_path_variables() extracts variables via regex and passes them as kwargs to the layout function.
_import_layouts_from_pages() walks the pages/ folder:
- Skips files starting with
_or. - Only imports
.pyfiles containingregister_page - Auto-assigns
layoutattribute from each module to the registry
Pages sorted by: numeric order → string order → no order → module name. Home page (/) defaults to order 0.
The assets/ folder is automatically scanned at startup (dash/dash.py:_walk_assets_directory):
.cssfiles → appended to stylesheets.jsfiles → appended to scriptsfavicon.ico→ used as app favicon- Files matching
assets_ignoreregex are skipped
Resources load in this order (dash/dash.py:1127-1165):
- React dependencies (from dash-renderer)
- Component library scripts (dash-html-components, dash-core-components, etc.)
- External scripts (
external_scriptsparameter) - Dash renderer bundle
- Clientside callback scripts (inline)
CSS follows similar ordering with external stylesheets first.
Component assets use fingerprinted URLs for cache busting (dash/fingerprint.py):
/_dash-component-suites/dash_core_components/dash_core_components.v2_14_0m1699900000.min.js
- Fingerprinted resources: 1-year cache header
- Non-fingerprinted: ETag validation
- Asset files: query string
?m={modification_time}
Dash(
assets_folder='assets', # path to assets directory
assets_url_path='assets', # URL path segment
assets_ignore='.*ignored.*', # regex to skip files
assets_external_path=None, # CDN base URL for assets
serve_locally=True, # True=local files, False=CDN
external_scripts=[], # additional JS URLs
external_stylesheets=[], # additional CSS URLs
)app.get_asset_url(path) returns the correct URL accounting for requests_pathname_prefix (important for Dash Enterprise deployments where apps have URL prefixes).
Debug mode enables developer tools (dash/dash.py:_setup_dev_tools):
app.run(debug=True)
# Or via environment: DASH_DEBUG=trueapp.enable_dev_tools(
dev_tools_ui=True, # show error UI overlay
dev_tools_props_check=True, # validate component prop types
dev_tools_serve_dev_bundles=True, # use development JS (better errors)
dev_tools_hot_reload=True, # auto-reload on file changes
dev_tools_prune_errors=True, # strip internal frames from tracebacks
)Environment variables: DASH_DEBUG, DASH_UI, DASH_PROPS_CHECK, DASH_HOT_RELOAD, etc.
PreventUpdate - Skip updating outputs without error:
from dash.exceptions import PreventUpdate
@app.callback(Output('out', 'children'), Input('in', 'value'))
def update(value):
if not value:
raise PreventUpdate
return valueno_update - Skip specific outputs in multi-output callbacks:
from dash import no_update
@app.callback(Output('a', 'children'), Output('b', 'children'), Input('in', 'value'))
def update(value):
return value, no_update # only updates 'a'Callbacks support on_error for custom error handling:
def handle_error(err):
logging.error(f"Callback failed: {err}")
return "Error occurred" # returned to output
@app.callback(Output('out', 'children'), Input('in', 'value'), on_error=handle_error)
def update(value):
return 1 / 0 # triggers error handlerApp-level error handler set via constructor.
- Layout validation: When
suppress_callback_exceptions=False(default), checks that callback IDs exist in layout - Callback validation:
dev_tools_validate_callbacks=Truechecks for circular dependencies - Props checking: Validates component prop types against schema in dev mode
When enabled, a watch thread monitors:
assets/folder for CSS/JS changes- Component package directories
Frontend polls /_reload-hash and triggers reload when hash changes. Configurable via hot_reload_interval (default 3s) and hot_reload_watch_interval (default 0.5s).
Background callbacks execute in separate processes, allowing the main server to remain responsive. Managed by dash/background_callback/managers/.
from dash import callback, Input, Output
from dash.background_callback import DiskcacheManager
cache_manager = DiskcacheManager()
@callback(
Output("result", "children"),
Input("button", "n_clicks"),
background=True,
manager=cache_manager,
interval=500, # polling interval in ms
)
def compute(n_clicks):
# Expensive computation
return resultDiskcacheManager (dash/background_callback/managers/diskcache_manager.py):
- Uses
diskcache.Cachefor persistent storage - Spawns
multiprocess.Processfor each job - Results stored on disk, survives server restarts
- Good for single-server deployments
CeleryManager (dash/background_callback/managers/celery_manager.py):
- Requires Celery app with result backend (Redis/RabbitMQ)
- Jobs distributed across Celery workers
- Supports horizontal scaling
- Good for production multi-worker deployments
from celery import Celery
from dash.background_callback import CeleryManager
celery_app = Celery(__name__, broker="redis://localhost:6379/0")
cache_manager = CeleryManager(celery_app)The progress parameter defines outputs updated during execution:
@callback(
Output("result", "children"),
Input("button", "n_clicks"),
progress=Output("progress-bar", "value"),
progress_default=0,
background=True,
manager=cache_manager,
)
def compute(set_progress, n_clicks):
for i in range(100):
set_progress(i)
time.sleep(0.1)
return "Complete"set_progressis injected as first argument whenprogressis specified- Can be single Output or list of Outputs
progress_defaultsets value when callback not running
The running parameter updates outputs while the job executes:
@callback(
Output("result", "children"),
Input("button", "n_clicks"),
running=[
(Output("button", "disabled"), True, False),
(Output("status", "children"), "Computing...", "Ready"),
],
background=True,
manager=cache_manager,
)
def compute(n_clicks):
time.sleep(5)
return "Done"Each tuple: (Output, value_while_running, value_when_complete)
The cancel parameter specifies inputs that abort the job:
@callback(
Output("result", "children"),
Input("start-btn", "n_clicks"),
cancel=[Input("cancel-btn", "n_clicks")],
background=True,
manager=cache_manager,
)
def compute(n_clicks):
# Job terminates if cancel-btn clicked
return resultManagers call terminate_job() which kills the process (Diskcache) or revokes the task (Celery).
Results can be cached to avoid recomputation:
def get_user_id():
return flask.session.get("user_id")
cache_manager = DiskcacheManager(
cache_by=[get_user_id], # cache key includes user ID
expire=3600, # TTL in seconds
)cache_by- List of functions whose return values are included in cache keyexpire- Time-to-live for cached resultscache_args_to_ignore- Argument indices to exclude from cache key
- Initial request: Frontend triggers callback, backend returns
cacheKeyandjobID - Polling: Frontend polls
/_dash-update-component?cacheKey=...&job=...at configured interval - Progress: Each poll returns current progress value if set
- Completion: When job finishes, poll returns final result
- Cleanup: Results cleared from cache (unless
cache_byspecified)
Cache key is SHA256 hash of: function source + arguments + triggered inputs + cache_by values.
dash/_callback.py:188-219- Background spec constructiondash/background_callback/managers/__init__.py-BaseBackgroundCallbackManagerabstract classdash/background_callback/managers/diskcache_manager.py- Diskcache implementationdash/background_callback/managers/celery_manager.py- Celery implementationdash/dash-renderer/src/actions/callbacks.ts:458-685- Frontend polling logic
Dash apps can run directly in Jupyter notebooks and JupyterLab. The integration is handled by dash/_jupyter.py.
app.run(
jupyter_mode="inline", # Display in notebook cell (default)
jupyter_width="100%", # IFrame width
jupyter_height=650, # IFrame height in pixels
)| Mode | Behavior |
|---|---|
"inline" |
App displays in notebook cell via IFrame |
"external" |
Prints URL, user opens in browser tab |
"jupyterlab" |
Opens in dedicated JupyterLab tab |
"tab" |
Auto-opens URL in new browser tab |
app.run()detects Jupyter environment viaget_ipython()- Server starts in background daemon thread
- Jupyter comm protocol negotiates proxy configuration
- App displays according to selected mode
app.run() in notebook
↓
Detect Jupyter → Start server in background thread
↓
Comm request → Extension responds with base_url
↓
Compute dashboard URL with proxy path
↓
Display: IFrame (inline) / URL (external) / Tab (jupyterlab)
Classic Jupyter notebooks use dash/nbextension/:
main.js- Registers "dash" comm targetdash.json- Extension loader configuration
The extension handles comm messages:
base_url_request→ responds with server URL and base path- Enables proper proxy routing in JupyterHub environments
JupyterLab uses @plotly/dash-jupyterlab/:
src/index.ts- TypeScript plugin implementingJupyterFrontEndPluginDashIFrameWidget- Lumino widget for rendering apps in tabs
Handles messages:
base_url_request→ responds with JupyterLab server configshow→ creates dedicated tab with IFrame widget
Compatible with JupyterLab 2.x, 3.x, and 4.x.
In JupyterHub/proxy environments, the extension negotiates requests_pathname_prefix:
# Computed from Jupyter base path
requests_pathname_prefix = "/user/username/proxy/8050/"This ensures callbacks route correctly through the Jupyter proxy.
Special handling for Colab:
- Uses
google.colab.output.serve_kernel_port_as_iframe()for inline - Uses
google.colab.output.serve_kernel_port_as_window()for external - Only supports "inline" and "external" modes
dash/_jupyter.py-JupyterDashclass, comm handling, server threaddash/nbextension/main.js- Classic notebook extension@plotly/dash-jupyterlab/src/index.ts- JupyterLab extension
Basic Setup:
name- Application name (default: infers from__name__)server- Server instance (Flask, Quart, or FastAPI) orTrueto create Flask (default:True)title- Browser tab title (default:"Dash")update_title- Title during callbacks (default:"Updating...")
Assets & Resources:
assets_folder- Path to assets directory (default:"assets")assets_url_path- URL path for assets (default:"assets")assets_ignore- Regex to exclude assets (default:"")serve_locally- Serve from local vs CDN (default:True)external_scripts- Additional JS URLsexternal_stylesheets- Additional CSS URLs
Routing:
url_base_pathname- Base URL prefix for entire apprequests_pathname_prefix- Prefix for AJAX requestsroutes_pathname_prefix- Prefix for API routes
Multi-Page:
use_pages- Enable pages system (default: auto-detect)pages_folder- Path to pages directory (default:"pages")
Behavior:
suppress_callback_exceptions- Skip callback validation (default:False)prevent_initial_callbacks- Skip callbacks on load (default:False)background_callback_manager- DiskcacheManager or CeleryManageron_error- Global callback error handler
WebSocket Callbacks:
websocket_callbacks- Enable WebSocket for all callbacks (default:False). Requires FastAPI backend.websocket_allowed_origins- List of allowed origins for WebSocket connectionswebsocket_inactivity_timeout- Disconnect WebSocket after inactivity period in ms (default:300000= 5 minutes). Set to0to disable.
host- Server IP (default:"127.0.0.1", env:HOST)port- Server port (default:8050, env:PORT)debug- Enable dev tools (default:False, env:DASH_DEBUG)jupyter_mode- Display mode:"inline","external","tab"
| Variable | Purpose |
|---|---|
DASH_DEBUG |
Enable debug mode |
DASH_URL_BASE_PATHNAME |
Base URL prefix |
DASH_SUPPRESS_CALLBACK_EXCEPTIONS |
Skip validation |
DASH_HOT_RELOAD |
Enable hot reload |
DASH_PROPS_CHECK |
Validate prop types |
DASH_PRUNE_ERRORS |
Simplify tracebacks |
HOST |
Server host |
PORT |
Server port |
Store data client-side with configurable persistence:
dcc.Store(id='my-store', storage_type='local', data={'key': 'value'})| Storage Type | Persists | Scope | Use Case |
|---|---|---|---|
'memory' |
Page view only | Tab | Temporary state, debugging |
'session' |
Browser session | Tab | Form state, filters |
'local' |
Forever | All tabs | User preferences, settings |
Usage pattern:
@app.callback(Output('output', 'children'), Input('store', 'data'))
def use_store(data):
return data['key']
@app.callback(Output('store', 'data'), Input('input', 'value'))
def update_store(value):
return {'key': value}Automatically persist user edits to component props:
dcc.Dropdown(
id='dropdown',
options=[...],
persistence=True, # Enable persistence
persistence_type='local', # local, session, or memory
persisted_props=['value'], # Props to persist (default varies by component)
)persistence-Trueor unique key to enablepersistence_type- Storage backend (default:'local')persisted_props- List of prop names to persist
Supported components: Input, Dropdown, Checklist, RadioItems, Slider, RangeSlider, DatePickerSingle, DatePickerRange, Textarea, Tabs, DataTable.
| Need | Solution |
|---|---|
| Server-controlled state | dcc.Store with callbacks |
| Remember user selections | Component persistence=True |
| Share state across tabs | dcc.Store with storage_type='local' |
| Session-only state | persistence_type='session' |
Dash supports async def callbacks for non-blocking execution.
With Flask backend:
pip install dash[async]Async is auto-enabled when asgiref is detected. Or explicitly:
app = Dash(__name__, use_async=True)With Quart or FastAPI backend: Async is native - no extra dependencies needed.
from fastapi import FastAPI
from dash import Dash
server = FastAPI()
app = Dash(__name__, server=server) # Async works automaticallyimport asyncio
@app.callback(Output('output', 'children'), Input('input', 'value'))
async def async_update(value):
await asyncio.sleep(1) # Non-blocking
return f"Processed: {value}"- Regular async callbacks are non-blocking - multiple can run concurrently
- Background callbacks also support
async def - Jupyter uses
nest_asynciofor event loop compatibility - With Flask backend: requires
dash[async], coroutines raise error without it - With Quart/FastAPI backends: async is native, no extra setup needed
@app.callback(
Output('result', 'children'),
Input('btn', 'n_clicks'),
background=True,
manager=diskcache_manager,
)
async def async_background(n_clicks):
await asyncio.sleep(5)
return "Done"Both DiskcacheManager and CeleryManager support async functions via asyncio.run().
WebSocket callbacks use a persistent WebSocket connection instead of HTTP POST for callback execution. This reduces latency and connection overhead for applications with frequent callbacks.
- FastAPI backend required: WebSocket callbacks only work with FastAPI
- SharedWorker support: Modern browsers (not IE)
Enable globally for all callbacks:
from fastapi import FastAPI
from dash import Dash
server = FastAPI()
app = Dash(__name__, server=server, websocket_callbacks=True)Enable per-callback:
@app.callback(
Output('output', 'children'),
Input('input', 'value'),
websocket=True # Use WebSocket for this callback only
)
def update(value):
return f"Value: {value}"app = Dash(
__name__,
server=server,
websocket_callbacks=True,
websocket_inactivity_timeout=300000, # 5 minutes (default)
websocket_allowed_origins=['https://example.com'],
)websocket_callbacks- Enable WebSocket for all callbacks (default:False)websocket_inactivity_timeout- Close WebSocket after period of inactivity in milliseconds (default:300000= 5 minutes). Heartbeats do not count as activity. Set to0to disable timeout. Connection automatically reconnects when needed.websocket_allowed_origins- List of allowed origins for WebSocket connections (security)
┌─────────────────────────────────────────────────────────────────────────┐
│ Browser Tab 1 Browser Tab 2 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Renderer │ │ Renderer │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ postMessage │ postMessage │
│ └────────────┬───────────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ SharedWorker │ (one per origin) │
│ │ dash-ws-worker │ │
│ └──────────┬──────────┘ │
└────────────────────│────────────────────────────────────────────────────┘
│ WebSocket
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Server (FastAPI) │
│ WebSocket Endpoint: /_dash-ws-callback │
└─────────────────────────────────────────────────────────────────────────┘
Connection & Reconnection Flow:
Renderer SharedWorker Server
│ │ │
│──[CONNECT]──────────────────>│ │
│ │──[WebSocket Connect]──>│
│<─[CONNECTED]─────────────────│<─[Connected]───────────│
│ │ │
│──[CALLBACK_REQUEST]─────────>│──[callback request]───>│
│<─[CALLBACK_RESPONSE]─────────│<─[callback response]───│
│ │ │
│ (inactivity) │ (heartbeat check) │
│ │──[close 4001]─────────>│
│<─[DISCONNECTED]──────────────│ │
│ │ │
│──[CALLBACK_REQUEST]─────────>│──[reconnect + send]───>│
│<─[CALLBACK_RESPONSE]─────────│<─[response]────────────│
- SharedWorker: Single WebSocket connection shared across browser tabs
- Heartbeat: Periodic ping/pong to detect dead connections (30s interval)
- Inactivity timeout: Closes connection after no actual callback activity (not heartbeats)
- Auto-reconnect: Reconnects automatically when a callback is triggered after timeout
WebSocket callbacks can stream updates to the client during execution using set_props() and read current component values using ctx.websocket:
import asyncio
from dash import callback, Output, Input, set_props, ctx
@callback(
Output('result', 'children'),
Input('start-btn', 'n_clicks'),
prevent_initial_call=True
)
async def long_running_task(n_clicks):
ws = ctx.websocket
if not ws:
return "WebSocket not available"
# Stream progress updates to the client
for i in range(100):
await asyncio.sleep(0.1)
set_props('progress-bar', {'value': i + 1})
set_props('status', {'children': f'Processing step {i + 1}/100...'})
# Read current value from another component
current_value = await ws.get_prop('input-field', 'value')
return f"Completed! Input was: {current_value}"API:
set_props(component_id, props_dict)- Stream prop updates immediately to clientctx.websocket- Get WebSocket interface (returnsNoneif not in WS context)await ws.get_prop(component_id, prop_name)- Read current prop value from clientawait ws.set_prop(component_id, prop_name, value)- Set single prop (async version)await ws.close(code, reason)- Close the WebSocket connection
Use hooks to validate connections and messages:
from dash import Dash, hooks
@hooks.websocket_connect()
async def validate_connection(websocket):
"""Validate WebSocket connection before accepting."""
session_id = websocket.cookies.get("session_id")
if not session_id:
return (4001, "No session cookie")
if not await is_valid_session(session_id):
return (4002, "Invalid session")
return True # Allow connection
@hooks.websocket_message()
async def validate_message(websocket, message):
"""Validate each WebSocket message."""
session_id = websocket.cookies.get("session_id")
if not await is_session_active(session_id):
return (4002, "Session expired")
return True # Allow messageHook Return Values:
True(or truthy) - Allow connection/messageFalse- Reject with default code (4001)(code, reason)- Reject with custom close code and reason
dash/dash.py- WebSocket config in_generate_config()dash/dash-renderer/src/utils/workerClient.ts- Browser-side SharedWorker client@plotly/dash-websocket-worker/src/WebSocketManager.ts- WebSocket connection management@plotly/dash-websocket-worker/src/worker.ts- SharedWorker entry pointdash/backends/_fastapi.py- Server-side WebSocket handler
Dash automatically sanitizes dangerous URLs in components:
- Blocked protocols:
javascript:,vbscript: - Protected attributes:
href,src,action,formAction - Dangerous URLs replaced with
about:blank
Components with URL sanitization: html.A, html.Form, html.Iframe, html.Embed, html.Object, html.Button.
Generate hashes for inline scripts to use with CSP middleware:
from flask_talisman import Talisman
Talisman(app.server, content_security_policy={
"default-src": "'self'",
"script-src": ["'self'"] + app.csp_hashes()
})app.csp_hashes(hash_algorithm='sha256') returns base64-encoded hashes.
suppress_callback_exceptions=False(default) - Validates all callback IDs exist in layoutprevent_initial_callbacks=True- Prevents callbacks firing on page load (can also set per-callback withprevent_initial_call)
Meta tag values are HTML-escaped to prevent injection:
app = Dash(__name__, meta_tags=[
{"name": "description", "content": "Safe <content>"}
])dash/dash-renderer/src/utils/clientsideFunctions.ts- URL sanitization (clean_url)dash/dash.py:csp_hashes()- CSP hash generationtests/integration/security/- Security test coverage