Skip to main content

Streamlit custom component for rendering and editing schema graphs

Project description

streamlit-schema-editor

streamlit-schema-editor is a Streamlit custom component for schema visualization, editing, ER-style canvases, and advanced lineage or mapping workflows built on React Flow and Streamlit Custom Components v2.

Light mode Dark mode
Schema editor preview in light mode Schema editor preview in dark mode

Features

  • Schema viewer and editable ER diagram canvas in one component
  • Table, column, relationship, and optional group-based lane model
  • Inline column create, update, and delete interactions
  • Relationship create/delete flows with duplicate and per-handle limit checks
  • Optional mid-edge action button for relationship inspection
  • Validation-aware styling for tables, columns, and relationships
  • Automatic group layout in manual, columns, or rows mode
  • Structured event and event_context payloads for Streamlit workflows
  • Metadata passthrough on groups, tables, columns, and relationships

Demo App

The deployable demo in demo/ is a getting-started sandbox for trying the component inside a regular Streamlit app. It starts with a relationship-free schema viewer preset, then lets you switch to an ER diagram or a grouped source-to-target example for richer mapping workflows.

  • demo/requirements.txt pins the published streamlit-schema-editor package used by the demo on Streamlit Community Cloud.
  • The demo imports that installed package instead of the repository checkout, so deployments do not need to build frontend assets on the Cloud machine.
  • Switch between schema viewer, editable ER diagram, and mapping canvases
  • Filter or reshape tables and relationships before passing them into streamlit_schema_editor(...)
  • Test editing, selection, validation styling, layout options, and edge actions
  • Compare the default relationship-free viewer with ER and mapping workflows

How it Works

The canvas layer is built on React Flow from xyflow, using the @xyflow/react library under the hood. streamlit-schema-editor wraps those primitives in a Streamlit-friendly API while still exposing familiar capabilities from the React Flow ecosystem.

Capability in streamlit-schema-editor React Flow primitive Docs
Pan/zoom canvas, selection, and viewport management ReactFlow API reference
Zoom controls and fit-view button Controls Component docs
Canvas dot grid Background Component docs
Column connection handles Handle Component docs
Relationship paths, markers, and custom edge actions BaseEdge and getBezierPath BaseEdge, getBezierPath

Install

With uv:

uv add streamlit-schema-editor

With pip:

pip install streamlit-schema-editor

Then open the demo app and use the controls to:

  • Create, inspect, and delete relationships between tables
  • Drag tables around the canvas and preserve updated positions
  • Toggle controls, grouping, validation display, and editability
  • Inspect the returned event and event_context values in Streamlit

Example

import streamlit as st

from streamlit_schema_editor import streamlit_schema_editor


if "tables" not in st.session_state:
    st.session_state.tables = [
        {
            "id": "customers",
            "label": "customers",
            "metadata": {"schema": "sales"},
            "columns": [
                {"id": "id", "name": "id", "data_type": "uuid"},
                {"id": "email", "name": "email", "data_type": "varchar"},
                {"id": "created_at", "name": "created_at", "data_type": "timestamp"},
            ],
        },
        {
            "id": "orders",
            "label": "orders",
            "metadata": {"schema": "sales"},
            "columns": [
                {"id": "id", "name": "id", "data_type": "uuid"},
                {"id": "customer_id", "name": "customer_id", "data_type": "uuid"},
                {"id": "status", "name": "status", "data_type": "varchar"},
                {"id": "ordered_at", "name": "ordered_at", "data_type": "timestamp"},
            ],
        },
        {
            "id": "order_items",
            "label": "order_items",
            "metadata": {"schema": "sales"},
            "columns": [
                {"id": "id", "name": "id", "data_type": "uuid"},
                {"id": "order_id", "name": "order_id", "data_type": "uuid"},
                {"id": "product_id", "name": "product_id", "data_type": "uuid"},
                {"id": "quantity", "name": "quantity", "data_type": "int4"},
            ],
        },
        {
            "id": "products",
            "label": "products",
            "metadata": {"schema": "catalog"},
            "columns": [
                {"id": "id", "name": "id", "data_type": "uuid"},
                {"id": "sku", "name": "sku", "data_type": "varchar"},
                {"id": "price", "name": "price", "data_type": "money"},
            ],
        },
    ]

if "relationships" not in st.session_state:
    st.session_state.relationships = [
        {
            # Relationship ids only need to be unique and stable. A structured
            # id like this makes generated events easy to trace back to columns.
            "id": "rel::orders::customer_id::customers::id",
            "source_table": "orders",
            "source_column": "customer_id",
            "target_table": "customers",
            "target_column": "id",
            "label": "belongs to",
            "validation": {
                "status": "success",
                "summary": "Foreign key exists in the warehouse.",
            },
            "metadata": {"constraint_name": "fk_orders_customer_id"},
        },
        {
            "id": "rel::order_items::order_id::orders::id",
            "source_table": "order_items",
            "source_column": "order_id",
            "target_table": "orders",
            "target_column": "id",
            "label": "part of",
            "metadata": {"constraint_name": "fk_order_items_order_id"},
        },
        {
            "id": "rel::order_items::product_id::products::id",
            "source_table": "order_items",
            "source_column": "product_id",
            "target_table": "products",
            "target_column": "id",
            "label": "references",
            "metadata": {"constraint_name": "fk_order_items_product_id"},
        },
    ]

value = streamlit_schema_editor(
    st.session_state.tables,
    st.session_state.relationships,
    height=700,
    show_controls=True,
    show_arrowheads=False,
    max_connections_per_handle=2,
    key="schema-editor",
)

st.session_state.tables = value["tables"]
st.session_state.relationships = value["relationships"]

st.write(value["event"])
st.json(value["event_context"])

More Examples

Interactive editor with inline type suggestions and relationship inspection:

value = streamlit_schema_editor(
    tables,
    relationships,
    editable=True,
    show_edge_button=True,
    show_column_count_badge=True,
    column_type_options=["string", "bigint", "timestamp", "json"],
    key="schema-editor-with-edge-actions",
)

if value["event"] == "edge_details_requested":
    relationship_id = (value["event_context"] or {}).get("relationship_id")
    st.write(f"Inspect relationship: {relationship_id}")

Viewer-style canvas with grouping and auto layout:

value = streamlit_schema_editor(
    tables,
    relationships,
    groups=[
        {"id": "source", "label": "Source"},
        {"id": "target", "label": "Target"},
    ],
    editable=False,
    connectable=False,
    deletable=False,
    draggable=True,
    show_groups=True,
    group_layout="columns",
    group_order=["source", "target"],
    table_layout_within_group="stack",
    show_controls=True,
    key="schema-viewer",
)

Hide validation visuals until the user opts in:

show_validation = st.toggle("Show validation", value=True)

value = streamlit_schema_editor(
    tables,
    relationships,
    show_validation=show_validation,
    key="schema-editor-validation-toggle",
)

Force validation visuals to refresh on demand:

if "validation_refresh_nonce" not in st.session_state:
    st.session_state.validation_refresh_nonce = 0

if st.button("Refresh validation visuals"):
    st.session_state.validation_refresh_nonce += 1

value = streamlit_schema_editor(
    tables,
    relationships,
    show_validation=True,
    validation_refresh_key=st.session_state.validation_refresh_nonce,
    key="schema-editor-validation-refresh",
)

Advanced source-to-target mapping with shared mapping_group_id metadata for multi-branch targets:

value = streamlit_schema_editor(
    groups=[
        {"id": "source", "label": "Source"},
        {"id": "target", "label": "Target"},
    ],
    tables=[
        {
            "id": "raw.customer_profile",
            "label": "raw.customer_profile",
            "group_id": "source",
            "columns": [
                {"id": "customer_id", "name": "customer_id", "data_type": "string"},
                {"id": "first_name", "name": "first_name", "data_type": "string"},
                {"id": "last_name", "name": "last_name", "data_type": "string"},
            ],
        },
        {
            "id": "curated.customer_dim",
            "label": "curated.customer_dim",
            "group_id": "target",
            "columns": [
                {"id": "customer_id", "name": "customer_id", "data_type": "string"},
                {
                    "id": "full_name",
                    "name": "full_name",
                    "data_type": "string",
                    "metadata": {
                        "mapping_group_id": "customer_full_name",
                        "expression": "concat_ws(' ', first_name, last_name)",
                    },
                },
            ],
        },
    ],
    relationships=[
        {
            "id": "rel::raw.customer_profile::first_name::curated.customer_dim::full_name",
            "source_table": "raw.customer_profile",
            "source_column": "first_name",
            "target_table": "curated.customer_dim",
            "target_column": "full_name",
            "label": "concat",
            "metadata": {
                "mapping_group_id": "customer_full_name",
                "branch_order": 1,
                "expression": "concat_ws(' ', first_name, last_name)",
            },
        },
        {
            "id": "rel::raw.customer_profile::last_name::curated.customer_dim::full_name",
            "source_table": "raw.customer_profile",
            "source_column": "last_name",
            "target_table": "curated.customer_dim",
            "target_column": "full_name",
            "label": "concat",
            "metadata": {
                "mapping_group_id": "customer_full_name",
                "branch_order": 2,
                "expression": "concat_ws(' ', first_name, last_name)",
            },
        },
    ],
    show_groups=True,
    group_layout="columns",
    table_layout_within_group="stack",
    show_edge_button=True,
    key="source-target-mapping",
)

The component treats metadata as application-owned payload, so you can attach domain concepts like mapping_group_id, SQL expressions, generated-target markers, lineage ids, or transformation notes without the wrapper needing to know their schema.

Run the bundled examples from the project root:

uv run streamlit run examples/playground.py
uv run streamlit run examples/schema_viewer.py
uv run streamlit run examples/er_diagram.py
uv run streamlit run examples/databricks_mapping.py
  • demo/streamlit_app.py: deployable demo app for Streamlit Community Cloud. It uses demo/requirements.txt to install the published package.
  • examples/playground.py: interactive playground for runtime options, grouping visibility, and validation
  • examples/schema_viewer.py: read-only schema browser
  • examples/er_diagram.py: ER-style relationship view with arrowheads hidden
  • examples/databricks_mapping.py: Databricks-inspired source-to-target mapping demo with labeled group lanes

API

streamlit_schema_editor(...) accepts a domain-shaped API and returns the latest graph state plus a semantic event payload for the current rerun.

Component props

Prop Type Default Description
tables list[TableSpec] required Schema tables to render.
relationships list[RelationshipSpec] required Relationships between columns.
groups list[GroupSpec] | None None Optional group or lane containers.
height int 600 Canvas height in pixels.
fit_view bool True Auto-fit the visible graph on first render.
editable bool True Enable inline column editing and add/delete actions.
connectable bool | None editable Enable column handles and relationship creation.
draggable bool | None True Allow tables to be repositioned.
deletable bool | None editable Allow delete-key removal for nodes and edges.
show_controls bool False Show React Flow zoom and fit controls.
show_arrowheads bool True Draw arrow markers on relationships.
show_edge_button bool False Show the inline relationship action button.
show_column_count_badge bool True Show a column-count badge in each table header.
show_groups bool True Render group or lane panels when group data is present.
group_layout Literal["manual", "columns", "rows"] "manual" Group placement strategy.
group_order list[str] | None None Stable display ordering for groups.
table_layout_within_group Literal["manual", "stack"] "manual" Table placement strategy inside each group.
show_validation bool True Enable validation badges and status coloring.
validation_refresh_key str | int | float | None None Force validation UI refresh after upstream state changes.
column_type_options list[str] | None None Suggestions for the inline data type picker.
allow_zoom bool True Enable wheel, pinch, and double-click zoom.
allow_duplicate_edges bool False Allow multiple identical relationships.
max_connections_per_handle int | None None Apply the same incoming and outgoing connection limit to each handle.
max_incoming_connections_per_handle int | None None Cap incoming relationships per handle.
max_outgoing_connections_per_handle int | None None Cap outgoing relationships per handle.
max_incoming_per_target int | None None Legacy alias for max_incoming_connections_per_handle.
max_outgoing_per_source int | None None Legacy alias for max_outgoing_connections_per_handle.
key str | None None Optional Streamlit component key.

editable acts as the default for connectable and deletable. Dragging is enabled by default even for read-only canvases, and editable also enables inline column editing, row add controls, keyboard deletion, and column edit events. Override the interaction flags individually when you need a mixed mode.

Use show_arrowheads=False for ER-style views where you want the canvas to read more like an undirected diagram, and show_column_count_badge=False when long table names need the extra header space. The legacy aliases max_incoming_per_target and max_outgoing_per_source are still accepted for backward compatibility, but the generic connection-limit parameters are the preferred public API.

Return value

Key Type Description
groups list[GroupSpec] Current group list with updated positions and sizes.
tables list[TableSpec] Current table list with updated positions and inline edits.
relationships list[RelationshipSpec] Current relationships after connect/delete actions.
selection SelectionState Current selected table, column, and relationship ids.
event SchemaEditorEvent Semantic event name or None.
event_context dict | None Structured payload for the last event.

Key Classes

Type Required fields Optional fields Notes
GroupSpec id, label position, width, height, metadata Use for group or lane containers.
TableSpec id, label, columns position, group_id, validation, metadata group_id ties a table to a GroupSpec.
ColumnSpec id, name, data_type validation, metadata metadata is passed through untouched.
RelationshipSpec id, source_table, source_column, target_table, target_column label, validation, metadata id can be any unique, stable string. Structured ids are useful for deterministic event handling, debugging, and duplicate detection.
ValidationSpec none status, code, summary, detail status supports initial, success, loading, and error.
Position x, y none Used by groups, tables, and move events.

Use validation for generic UI state that the component understands, and keep app-specific semantics in metadata. For example, SQL expressions, lineage attributes, ownership, or workflow IDs should live in metadata, not as first-class component fields.

Events

Events are dispatched in response to actions. You can use these to define your own event handlers. The full list of supported events are described in this table:

Event Description
selection_changed Fires when table, column, or relationship selection changes.
node_moved Fires when a table position changes.
table_deleted Fires after tables are removed, including any deleted relationship ids.
column_created Fires after a column is added.
column_updated Fires after an inline column edit is saved.
column_deleted Fires after a column is removed, including any deleted relationship ids.
relationship_created Fires after a new relationship is created.
relationship_deleted Fires with the deleted relationship id.
relationship_rejected Fires when duplicate or handle-limit rules reject a relationship.
edge_details_requested Fires when the relationship action button is clicked.
history_undone Fires after the built-in undo control restores the previous graph state.
history_redone Fires after the built-in redo control reapplies a graph state.

edge_details_requested is only emitted when show_edge_button=True.

Handle inline column edit events in Streamlit:

value = streamlit_schema_editor(
    tables,
    relationships,
    editable=True,
    key="schema-editor-events",
)

if value["event"] == "column_created":
    st.success(f"Added column: {(value['event_context'] or {}).get('column_id')}")

if value["event"] == "column_updated":
    context = value["event_context"] or {}
    st.info(
        "Updated "
        f"{context.get('table_id')}.{context.get('column_id')} "
        f"fields={context.get('fields')}"
    )

if value["event"] == "column_deleted":
    context = value["event_context"] or {}
    st.warning(
        "Deleted "
        f"{context.get('table_id')}.{context.get('column_id')} "
        f"and removed relationships={context.get('deleted_relationship_ids')}"
    )

Usage Notes

  • Inline editing, hover state, and connection gestures stay local to the React component until they emit durable graph state back to Streamlit.
  • Arbitrary metadata is passed through untouched on groups, tables, columns, and relationships.
  • The component is designed for rerun-driven usage: persist returned tables and relationships in st.session_state when edits should survive subsequent Streamlit reruns.
  • Use groups plus per-table group_id for optional labeled containers such as Source / Target, database lanes, or bronze/silver/gold layers.
  • Automatic layout modes own placement, so group_layout="columns" or group_layout="rows" pair best with read-only or lightly interactive canvases.
  • Column ids remain stable and are not edited inline. If you delete a column, any attached relationships are removed from the graph at the same time.

Repository Structure

This repository is structured like a standard Streamlit custom component project:

  • streamlit_schema_editor/ contains the publishable Python package
  • streamlit_schema_editor/frontend/ contains the React Flow frontend component
  • demo/ contains the Streamlit Community Cloud demo app
  • examples/ contains smaller Streamlit examples for manual testing
  • tests/ contains Python-side and component regression coverage
  • pyproject.toml builds the package as streamlit-schema-editor

Local Validation

From the repository root:

uv sync --extra dev
cd streamlit_schema_editor/frontend
npm install
npm run build
cd ../..
uv build
uv run pytest

To run the primary demo app:

python -m pip install -r demo/requirements.txt
streamlit run demo/streamlit_app.py

Project details


Download files

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

Source Distribution

streamlit_schema_editor-0.2.0.tar.gz (232.4 kB view details)

Uploaded Source

Built Distribution

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

streamlit_schema_editor-0.2.0-py3-none-any.whl (170.5 kB view details)

Uploaded Python 3

File details

Details for the file streamlit_schema_editor-0.2.0.tar.gz.

File metadata

  • Download URL: streamlit_schema_editor-0.2.0.tar.gz
  • Upload date:
  • Size: 232.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for streamlit_schema_editor-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f8772da4a534d49f62f0ff4eb3629df94c1d3a7ce919381fc5687628daae8187
MD5 4642f8a85f6668331c42db91178402f7
BLAKE2b-256 fffe559df20454205ac8cb6b97501a90b10657bc88dfd48e7053faa2e91e679a

See more details on using hashes here.

File details

Details for the file streamlit_schema_editor-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: streamlit_schema_editor-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 170.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for streamlit_schema_editor-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 205551f1a4692d6ce24dc0be6c835bc63340d3a3899d1ad18fa4c34b0f63da63
MD5 8cefbb1a80683e5d78af5d5062a3bdf9
BLAKE2b-256 8609de25c7ee3880a9857074512a63943443e0c6f526e8e8b92fbf4838eed1e5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page