Skip to content
Feeding a clanker? Grab this page as raw .md

Excalidraw API#

Bases: AnyWidget

An embedded Excalidraw whiteboard.

Draw shapes, arrows, text, and freehand sketches on an infinite canvas. The current drawing is kept in memory on the scene traitlet as an Excalidraw scene dict (elements / appState / files). Like the other drawing widgets, nothing is written to disk automatically — call :meth:save when you want to persist, and load with :meth:from_file.

Parameters:

Name Type Description Default
scene Optional[dict]

Optional Excalidraw scene dict to preload the canvas with.

None
height int

Canvas height in pixels.

DEFAULT_HEIGHT
sync_throttle_ms int

Minimum delay between syncing edits back to Python.

1000
theme str

"light" (default) or "dark". Set it to "" to instead follow the notebook's theme.

'light'
Example
import marimo as mo
from wigglystuff import Excalidraw

draw = mo.ui.anywidget(Excalidraw())
draw

After sketching something:

draw.save("diagram.excalidraw")          # write to disk
again = Excalidraw.from_file("diagram.excalidraw")  # load it back
again.save()                              # write back to diagram.excalidraw
again.save("other.excalidraw")            # save elsewhere (and remember it)
Source code in wigglystuff/excalidraw.py
def __init__(
    self,
    scene: Optional[dict] = None,
    height: int = DEFAULT_HEIGHT,
    sync_throttle_ms: int = 1000,
    theme: str = "light",
    **kwargs,
):
    super().__init__(**kwargs)
    self.height = height
    self.sync_throttle_ms = sync_throttle_ms
    self.theme = theme
    self.source_path: Optional[Path] = None
    if scene is not None:
        self.scene = scene

from_file classmethod #

from_file(path: Union[str, Path], **kwargs) -> Excalidraw

Create an :class:Excalidraw preloaded with the scene at path.

The path is remembered on source_path so you can call :meth:save with no argument to write edits back to the same file.

Source code in wigglystuff/excalidraw.py
@classmethod
def from_file(cls, path: Union[str, Path], **kwargs) -> "Excalidraw":
    """Create an :class:`Excalidraw` preloaded with the scene at ``path``.

    The path is remembered on ``source_path`` so you can call
    :meth:`save` with no argument to write edits back to the same file.
    """
    scene = json.loads(Path(path).read_text(encoding="utf-8"))
    widget = cls(scene=scene, **kwargs)
    widget.source_path = Path(path)
    return widget

get_image_base64 #

get_image_base64() -> str

Return the current drawing as a PNG data URL (empty if nothing drawn).

Source code in wigglystuff/excalidraw.py
def get_image_base64(self) -> str:
    """Return the current drawing as a PNG data URL (empty if nothing drawn)."""
    return self.image_base64

get_pil #

get_pil()

Return the current drawing as a PIL Image, or None if empty.

Handy for passing what you drew forward — e.g. into a multimodal model. The PNG is rendered in the browser and synced back, so it lags edits by up to sync_throttle_ms.

Source code in wigglystuff/excalidraw.py
def get_pil(self):
    """Return the current drawing as a PIL Image, or ``None`` if empty.

    Handy for passing what you drew forward — e.g. into a multimodal model.
    The PNG is rendered in the browser and synced back, so it lags edits by
    up to ``sync_throttle_ms``.
    """
    if not self.image_base64:
        return None
    import base64
    import io

    from PIL import Image

    payload = self.image_base64.split(",", 1)[-1]
    return Image.open(io.BytesIO(base64.b64decode(payload)))

get_scene #

get_scene() -> dict

Return the current Excalidraw scene as a dict.

Source code in wigglystuff/excalidraw.py
def get_scene(self) -> dict:
    """Return the current Excalidraw scene as a dict."""
    return dict(self.scene)

save #

save(path: Optional[Union[str, Path]] = None) -> Path

Write the current scene to a .excalidraw JSON file and return where.

Pass path to choose the destination; it is remembered on source_path, so later calls — and widgets created via :meth:from_file — can call save() with no argument to write back to the same file. This widget never writes on its own: in marimo, putting save() in its own cell makes it effectively autosave, because marimo (not this method) re-runs that cell whenever the widget changes, so the file tracks what you draw. The returned absolute path is shown as the cell output, so it is always clear which file was written.

Source code in wigglystuff/excalidraw.py
def save(self, path: Optional[Union[str, Path]] = None) -> Path:
    """Write the current scene to a ``.excalidraw`` JSON file and return where.

    Pass ``path`` to choose the destination; it is remembered on
    ``source_path``, so later calls — and widgets created via
    :meth:`from_file` — can call ``save()`` with no argument to write back
    to the same file. This widget never writes on its own: in marimo,
    putting ``save()`` in its own cell makes it *effectively* autosave,
    because marimo (not this method) re-runs that cell whenever the widget
    changes, so the file tracks what you draw. The returned absolute path is
    shown as the cell output, so it is always clear which file was written.
    """
    if path is not None:
        self.source_path = Path(path)
    if self.source_path is None:
        raise ValueError(
            "save() needs a path: either pass one, e.g. "
            'save("diagram.excalidraw"), or create the widget with '
            "Excalidraw.from_file(...) so the source path is known."
        )
    Path(self.source_path).write_text(self.to_json(), encoding="utf-8")
    return Path(self.source_path).resolve()

to_json #

to_json() -> str

Return the current scene serialized as a JSON string.

Source code in wigglystuff/excalidraw.py
def to_json(self) -> str:
    """Return the current scene serialized as a JSON string."""
    return json.dumps(self.scene)

Synced traitlets#

Traitlet Type Notes
scene dict Excalidraw scene (elements / appState / files).
image_base64 str PNG data URL of the drawing; read via get_pil().
theme str "light" (default) or "dark"; "" follows the notebook theme.
height int Canvas height in pixels.
sync_throttle_ms int Minimum delay between syncing edits back to Python.

Excalidraw itself is loaded from a CDN the first time the widget renders, so the widget needs network access and does not work fully offline.