Library
Read and write the Excalidraw shape library from Python with lastLibraryChange and updateLibrary.
Overview
The library menu holds reusable shapes. lastLibraryChange fires whenever the user adds/removes an item; command: updateLibrary lets you push items in from Python.
Coming back from the public library
The sidebar's Browse Library button sends the user to Excalidraw's public library site. libraryReturnUrl is the URL that site sends them back to once they pick a shape:
DashExcalidraw(
id="canvas",
libraryReturnUrl="https://your-app.example.com/drawing",
)
It has no default of its own. Left unset, the return trip is whatever Excalidraw decides, which is not a URL your app chose — so set it to the page hosting the canvas whenever the library button is reachable, and the user comes back to you rather than wherever the fallback points.
Live demo
Source
# File: docs/library/library.py
"""Library: observe library items and drive updateLibrary."""
import uuid
import dash_mantine_components as dmc
from dash import Input, Output, callback
from dash_excalidraw import DashExcalidraw
from docs._shared import (
canvas_frame,
code_block,
json_panel,
sync_canvas_theme,
two_column,
)
sync_canvas_theme("library-canvas")
CODE = """
# observe
@callback(Output('out', 'children'),
Input('canvas', 'lastLibraryChange'))
def show(snapshot): ...
# drive (merge items from Python)
@callback(Output('canvas', 'command'),
Input('btn', 'n_clicks'),
prevent_initial_call=True)
def add_items(_):
return {'id': str(uuid.uuid4()),
'type': 'updateLibrary',
'payload': {
'libraryItems': [...],
'merge': True,
'openLibraryMenu': True,
}}
"""
component = dmc.Stack(
gap="md",
children=[
code_block(CODE),
two_column(
canvas_frame(
DashExcalidraw(
id="library-canvas",
height="560px",
),
min_height=560,
),
dmc.Stack(
gap="sm",
children=[
dmc.Button(
"Open library menu",
id="library-open-btn",
variant="light",
),
dmc.Paper(id="library-panel", withBorder=True, p="xs"),
],
),
),
],
)
@callback(
Output("library-panel", "children"),
Input("library-canvas", "lastLibraryChange"),
)
def _render_library(snapshot):
if not snapshot:
return dmc.Text("(no library changes yet)", c="dimmed", size="sm")
items = snapshot.get("items") or []
return dmc.Stack(
gap="xs",
children=[
dmc.Group(
[
dmc.Text("Library items", size="sm", fw=600),
dmc.Badge(len(items), variant="light"),
]
),
json_panel("Raw payload", snapshot, height=320),
],
)
@callback(
Output("library-canvas", "command"),
Input("library-open-btn", "n_clicks"),
prevent_initial_call=True,
)
def _open_library(_clicks):
return {
"id": f"libopen-{uuid.uuid4()}",
"type": "toggleSidebar",
# `name` is the SIDEBAR, `tab` is the panel inside it. Excalidraw's
# built-in sidebar is called "default" and "library" is one of its
# tabs — so `{"name": "library"}` addresses a sidebar that does not
# exist, and `toggleSidebar` answers False and does nothing at all.
# That is what made this button look dead while the canvas's own
# library button opened the very same panel.
"payload": {"name": "default", "tab": "library", "force": True},
}
:defaultExpanded: false :withExpandedButton: true
Source: /library
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:
- /library/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt