Skip to main content

Streamlit - Drawable Canvas


This project is best effort. Every now and then I'll add something I need myself and let a coding agent do most of the typing, but I don't have the time to go through bigger issues or pull requests. If there's a larger feature you want, fork away!

Please add a thumbs up HERE if you wish to see a native implementation maintained by the Streamlit team.


Streamlit component which provides a sketching canvas using Fabric.js.

Streamlit App

PyPI PyPI - Downloads

Buy Me A Coffee

Features

  • Draw freely, lines, circles, boxes and polygons on the canvas, with options on stroke & fill
  • Rotate, skew, scale, move any object of the canvas on demand
  • Select a background color or image to draw on
  • Get image data and every drawn object properties back to Streamlit !
  • Choose to fetch back data in realtime or on demand with a button
  • Undo, Redo or Delete canvas contents
  • Save canvas data as JSON to reuse for another session

Installation

Requires Streamlit >= 1.53 and Python >= 3.10 (0.10.0 is built on Streamlit Components v2; see Upgrading from 0.9.x if you're on an older Streamlit).

pip install streamlit-drawable-canvas

Example Usage

Copy this code snippet:

import pandas as pd
from PIL import Image
import streamlit as st
from streamlit_drawable_canvas import st_canvas

# Specify canvas parameters in application
drawing_mode = st.sidebar.selectbox(
    "Drawing tool:", ("point", "freedraw", "line", "rect", "circle", "polygon", "text")
)

stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3)
if drawing_mode == "point":
    point_display_radius = st.sidebar.slider("Point display radius: ", 1, 25, 3)
stroke_color = st.sidebar.color_picker("Stroke color hex: ")
bg_color = st.sidebar.color_picker("Background color hex: ", "#eee")
bg_image = st.sidebar.file_uploader("Background image:", type=["png", "jpg"])

realtime_update = st.sidebar.checkbox("Update in realtime", True)


# Create a canvas component
canvas_result = st_canvas(
    fill_color="rgba(255, 165, 0, 0.3)",  # Fixed fill color with some opacity
    stroke_width=stroke_width,
    stroke_color=stroke_color,
    background_color=bg_color,
    background_image=Image.open(bg_image) if bg_image else None,
    update_streamlit=realtime_update,
    height=150,
    drawing_mode=drawing_mode,
    point_display_radius=point_display_radius if drawing_mode == "point" else 0,
    return_image_data=True,
    key="canvas",
)

# Do something interesting with the image data and paths
if canvas_result.image_data is not None:
    st.image(canvas_result.image_data)
if canvas_result.json_data is not None:
    objects = pd.json_normalize(
        canvas_result.json_data["objects"]
    )  # need to convert obj to str because PyArrow
    for col in objects.select_dtypes(include=["object"]).columns:
        objects[col] = objects[col].astype("str")
    st.dataframe(objects)

You will find more detailed examples on the demo app.

For reading the returned drawing -- what's in json_data, why a resized shape keeps its original width, how to map canvas coordinates back to your source image -- see FAQ.md.

API

st_canvas(
    fill_color: str
    stroke_width: int
    stroke_color: str
    background_color: str
    background_image: str | Path | bytes | Image
    update_streamlit: bool
    height: int
    width: int
    drawing_mode: str
    initial_drawing: dict
    point_display_radius: int
    return_image_data: bool
    key: str
    on_change: callable
    disabled: bool
    background_image_fit: str
    max_display_height: int
    font_size: int
    label: str
)
  • fill_color : Color of fill for Rect/Circle/Polygon in CSS color property. Defaults to "#eee". In drawing_mode="text", defaults to stroke_color instead -- "#eee" text on a default canvas would be all but invisible. Passing a value explicitly behaves identically in every mode.
  • stroke_width : Width of drawing brush in CSS color property. Defaults to 20.
  • stroke_color : Color of drawing brush in hex. Defaults to "black".
  • background_color : Color of canvas background in CSS color property. Defaults to "" which is transparent. Overriden by background_image. Changing background_color will reset the drawing.
  • background_image : Image to display behind canvas: an http(s) URL, a data: URI, a local file path, raw image bytes, or a Pillow Image. Automatically resized to canvas dimensions. Being behind the canvas, it is not sent back to Streamlit on mouse event. Overrides background_color. Changes to this will reset canvas contents.
  • update_streamlit : Whenever True, send canvas data to Streamlit when object/selection is updated or mouse up. Forced off for drawing_mode="polygon" -- an in-progress multi-click polygon isn't a meaningful intermediate value; the completed polygon still sends once closed. When nothing sends automatically, the toolbar stays pinned open instead of appearing on hover, because its send button is then the only discoverable way to commit a drawing. If what you want is "only give me the finished drawing", prefer an st.form over update_streamlit=False -- see FAQ.md.
  • height : Height of canvas in pixels. Defaults to 400.
  • width : Width of canvas in pixels. Defaults to 600.
  • drawing_mode : One of "freedraw", "line", "rect", "circle", "point", "polygon", "text", "labeled_rect". Text placement when "text", a labeled bounding box when "labeled_rect", otherwise create new objects with the rest. Defaults to "freedraw". Any other value raises ValueError.
    • On "polygon" mode, click to add a vertex; every vertex shows a handle. Click the first vertex's handle to close the shape; click any other handle to remove that vertex.
    • On "text" mode, clicking places an empty text object and starts editing it immediately; click elsewhere (or Escape/blur) to finish. Nothing is sent to Streamlit until editing ends.
    • On "labeled_rect" mode, click and drag to draw a box the same way "rect" does, stamped with the current label. See the label parameter below.
    • Editing (moving, scaling, rotating, click-to-edit existing text, click-to-relabel a box) isn't a drawing mode -- it's the toolbar's edit toggle, available regardless of drawing_mode. It has two levels: selecting an object lets you move/scale/rotate it as a whole (level 1); clicking an already-selected polygon, line, rect, circle or labeled box a second time re-enters it -- point editing for a shape, the label field for a box -- instead of acting on the whole object. See FAQ.md for the per-shape gesture table.
  • initial_drawing : Initialize canvas with drawings from here. Should be the json_data output from another canvas. Beware: if you try to import a drawing from a bigger/smaller canvas, no rescaling is done in the canvas and the import could fail.
  • point_display_radius : To make points visible on the canvas, they are drawn as circles. This parameter modifies the radius of the displayed circle.
  • return_image_data : If True, populate image_data (RGBA numpy array) and image_bytes (raw PNG bytes, for st.download_button) on the result. False by default -- it PNG-encodes the whole canvas on every send. Accessing either without it raises RuntimeError.
  • key : An optional string to use as the unique key for the widget. Assign a key so the component is not remounted on every rerun.
  • on_change : Optional callback invoked when the component sends a new drawing.
  • background_image_fit : One of "stretch" (default) or "contain". "stretch" scales each axis independently to fill the canvas exactly, distorting the image when the aspect ratios differ -- this is the historical behaviour. "contain" preserves the aspect ratio, fitting the image inside the canvas and centring it, so a canvas larger than its background image gets margins instead of a stretched image. Ignored when no background_image is set. Any other value raises ValueError.
  • disabled : If True, render the canvas read-only -- drawing, selection and transforms are all inert, nothing is sent back to Streamlit, and the toolbar is hidden. initial_drawing still renders, so this is how you show a drawing back to someone without letting them change it. Defaults to False.
  • max_display_height : Caps the canvas's displayed height in pixels and makes it scroll vertically inside that box. height, canvas pixel dimensions, and json_data coordinates are unaffected. None (the default) displays the canvas at its full height. Horizontal scrolling is always available, independent of this parameter.
  • font_size : Font size in pixels for text placed in drawing_mode="text", and for the label chip on boxes drawn in drawing_mode="labeled_rect". Defaults to 20. Ignored in every other mode.
  • label : The label stamped onto every box drawn in drawing_mode="labeled_rect". Defaults to "", which draws a box with no label chip. Raises ValueError if non-empty and drawing_mode is not "labeled_rect". Read labeled boxes back with CanvasResult.boxes, or relabel one on the canvas via the toolbar's edit toggle -- see FAQ.md.

Example:

import streamlit as st
from streamlit_drawable_canvas import st_canvas

canvas_result = st_canvas()
st_canvas(initial_drawing=canvas_result.json_data)

Upgrading from 0.9.x

0.10.0 is a breaking release (Streamlit Components v2, Fabric.js 7). If you're upgrading:

  • image_data raises RuntimeError -- it's now opt-in. Pass return_image_data=True to st_canvas().
  • Old Streamlit or Python -- 0.10.0 needs Streamlit >= 1.53 and Python >= 3.10. If you can't upgrade, pin streamlit-drawable-canvas==0.9.3.
  • Saved drawings from 0.9.x with Circle or Point objects render as a thin sliver, not the original shape, when fed back in via initial_drawing. Fabric 4 wrote Circle.startAngle/endAngle in radians; Fabric 7 reinterprets those same JSON keys as degrees, and loadFromJSON doesn't consult the JSON's version field to tell the difference. This is declared breaking, with no migration shim. Line, Rect, freedraw, Polygon, and Edit-mode objects are unaffected -- only objects from circle/point drawing modes carry startAngle/endAngle.

Development

Tasks are automated with just (see justfile) and uv. Run just (or just --list) to see every recipe.

Install

just setup        # uv sync + npm ci (frontend) + pre-commit install
just reinstall    # same, but wipes .venv / node_modules / build outputs first

Run the demo app

just demo   # uv run streamlit run demo_app.py

For frontend changes, run the Vite watch-rebuild alongside it in another terminal -- it rebuilds frontend/build on every save, which just demo's Streamlit process picks up on the next rerun:

just dev

Lint, format, test

just lint    # ruff check + tsc --noEmit + prettier check
just format  # ruff format + prettier write
just test    # pytest + Vitest

End-to-end tests (Playwright)

just e2e-setup   # one-time: install deps + browsers
just build       # E2E needs the built frontend
just e2e         # uv run pytest e2e_playwright -n auto

See the justfile (just --list) for the full recipe reference, including version bumps and publishing.

References

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

streamlit_drawable_canvas-0.13.0.tar.gz (131.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

streamlit_drawable_canvas-0.13.0-py3-none-any.whl (125.6 kB view details)

Uploaded Python 3

File details

Details for the file streamlit_drawable_canvas-0.13.0.tar.gz.

File metadata

  • Download URL: streamlit_drawable_canvas-0.13.0.tar.gz
  • Upload date:
  • Size: 131.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for streamlit_drawable_canvas-0.13.0.tar.gz
Algorithm Hash digest
SHA256 9e65b8abfc8b1fdd541425ac8ff24d320d5067b8646f58bb29e199188fbff3e8
MD5 0ca72868e6b244405eb709ff9261b3e7
BLAKE2b-256 d30457c835ba28a67f6f1dbd4c78d58dc333606ee2b958044bb554b73c0c7e23

See more details on using hashes here.

File details

Details for the file streamlit_drawable_canvas-0.13.0-py3-none-any.whl.

File metadata

  • Download URL: streamlit_drawable_canvas-0.13.0-py3-none-any.whl
  • Upload date:
  • Size: 125.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for streamlit_drawable_canvas-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3120cb0be0b499a456eed18a987b07a73e74952fd5911bd4cde04ceb96ed41b6
MD5 41e34264ba058c108f6912e98eaf6b10
BLAKE2b-256 6f400aff91c8f853dcb0ff75a6d46c37c67855162f42b59cc8492ad2c387e574

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.13.0 This release

2 files

0.12.0

2 files

0.10.0

2 files

0.9.3

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page