Streamlit custom component for rendering and editing schema graphs
Project description
streamlit-schema-editor
Streamlit custom component for viewing and editing schema, ER, and source-to-target mapping graphs with React Flow and Streamlit Custom Components v2.
Installation
uv pip install streamlit-schema-editor
What It Supports
- schema viewing with table and column metadata
- source-to-target mapping with interactive relationship creation and deletion
- inline column editing for
nameanddata_type - optional edge action button for relationship inspection or custom workflows
- read-only or editable canvases
- validation styling for tables, columns, and relationships
- metadata passthrough on tables, columns, and relationships
- structured
event+event_contextpayloads for Streamlit-side workflows
Usage
import streamlit as st
from streamlit_schema_editor import streamlit_schema_editor
if "tables" not in st.session_state:
st.session_state.tables = [
{
"id": "crm_customer",
"label": "crm_customer",
"metadata": {"system": "crm"},
"columns": [
{"id": "customer_id", "name": "customer_id", "data_type": "uuid"},
{"id": "cust_name", "name": "cust_name", "data_type": "varchar"},
],
},
{
"id": "dim_customer",
"label": "dim_customer",
"metadata": {"system": "warehouse"},
"columns": [
{"id": "customer_key", "name": "customer_key", "data_type": "bigint"},
{"id": "customer_name", "name": "customer_name", "data_type": "varchar"},
],
},
]
if "relationships" not in st.session_state:
st.session_state.relationships = [
{
"id": "rel::crm_customer::cust_name::dim_customer::customer_name",
"source_table": "crm_customer",
"source_column": "cust_name",
"target_table": "dim_customer",
"target_column": "customer_name",
"validation": {
"status": "loading",
"summary": "Awaiting warehouse validation for this mapping.",
},
"metadata": {
"rule_id": "MAP-1024",
"sql_expression": "UPPER(TRIM(cust_name))",
},
}
]
value = streamlit_schema_editor(
st.session_state.tables,
st.session_state.relationships,
height=700,
show_controls=True,
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"])
API
streamlit_schema_editor(
tables,
relationships,
*,
groups=None,
height=600,
fit_view=True,
editable=True,
connectable=None,
draggable=None,
deletable=None,
show_controls=False,
show_arrowheads=True,
show_edge_button=False,
show_column_count_badge=True,
show_groups=True,
group_layout="manual",
group_order=None,
table_layout_within_group="manual",
show_validation=True,
validation_refresh_key=None,
column_type_options=None,
allow_zoom=True,
allow_duplicate_edges=False,
max_connections_per_handle=None,
max_incoming_connections_per_handle=None,
max_outgoing_connections_per_handle=None,
key=None,
)
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/remove controls, 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.
show_edge_button is optional and defaults to False. Turn it on only when you
want a per-relationship action affordance in the middle of the edge.
Use groups plus per-table group_id when you want optional labeled containers
such as Source / Target, database lanes, or bronze/silver/gold layers.
show_groups controls whether those containers are currently rendered without
requiring you to change the underlying table or relationship payload.
Use group_layout="columns" or group_layout="rows" when you want the
component to place groups automatically without manually setting every group
position. Use group_order to control that automatic order, and
table_layout_within_group="stack" to vertically arrange grouped tables for
lane-style views. Automatic layout modes intentionally own placement, so they
are best paired with read-only or lightly interactive canvases.
show_validation controls whether validation-derived colors, badges, and
summaries are rendered. If you want to force those visuals to refresh without
changing the schema payload, change validation_refresh_key between reruns.
Edge Button
Enable the button when you want relationship inspection or a custom workflow trigger from the canvas:
value = streamlit_schema_editor(
tables,
relationships,
show_edge_button=True,
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}")
When enabled, the edge button can show:
relationship["label"]when presentias a compact fallback for inspection
This button is useful for cases like opening a details panel, surfacing lineage metadata, showing validation context, or launching a relationship editor. For pure ER or schema-viewing use cases, leave it disabled.
Return Value
tables: current table list with updated positionsgroups: current group list with updated positions and sizesrelationships: current relationships after connect/delete actionsselection: current selected table, column, and relationship idsevent: semantic event name orNoneevent_context: structured payload for the last event
Event Names
selection_changednode_movedtable_deletedcolumn_createdcolumn_updatedcolumn_deletedrelationship_createdrelationship_deletedrelationship_rejectededge_details_requested
edge_details_requested is only emitted when show_edge_button=True.
Schema Fields
Tables, columns, and relationships support generic metadata passthrough. Tables,
columns, and relationships can also opt into a top-level validation object.
Tables can optionally declare a single group_id, and groups are defined
separately with id, label, position, width, height, and metadata:
{
"validation": {
"status": "error",
"code": "missing_column",
"summary": "Column not found in upstream schema.",
"detail": "Latest introspection did not return crm_customer.region_code.",
}
}
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.
Inline Column Editing
When editable=True, table nodes support lightweight inline schema editing:
- click a column name to edit
column["name"] - click a data type to edit
column["data_type"] - use the placeholder row at the bottom of a table to add a column row
- use the
×control on a row to remove that column
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.
If you pass column_type_options, the inline data-type editor uses an in-node
combobox with suggested values while still allowing custom typed entries such as
varchar(255).
Common Recipes
Viewer-style canvas with dragging enabled but editing disabled:
value = streamlit_schema_editor(
tables,
relationships,
editable=False,
draggable=True,
connectable=False,
deletable=False,
show_controls=True,
key="schema-viewer",
)
Mapping editor with inline column editing and relationship inspection:
value = streamlit_schema_editor(
tables,
relationships,
editable=True,
show_edge_button=True,
show_controls=True,
key="mapping-editor",
)
Toggle labeled source / target lanes on and off at runtime:
groups = [
{"id": "source", "label": "Source", "width": 420, "height": 640},
{"id": "target", "label": "Target", "width": 380, "height": 640},
]
tables = [
{**source_table, "group_id": "source"},
{**target_table, "group_id": "target"},
]
show_groups = st.toggle("Show groups", value=True)
value = streamlit_schema_editor(
tables,
relationships,
groups=groups,
show_groups=show_groups,
group_layout="columns",
group_order=["source", "target"],
table_layout_within_group="stack",
key="schema-editor-groups",
)
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",
)
Use known type options while still allowing custom values:
value = streamlit_schema_editor(
tables,
relationships,
editable=True,
column_type_options=[
"uuid",
"bigint",
"integer",
"varchar",
"text",
"timestamp",
"json",
],
key="schema-editor-type-options",
)
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')}"
)
Examples
Run the examples from the project root:
uv run streamlit run example.py
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
example.py: full source-to-target mapping demoexamples/playground.py: interactive playground for toggling runtime options, grouping visibility, and validation liveexamples/schema_viewer.py: read-only schema browserexamples/er_diagram.py: ER-style relationship view with arrowheads hiddenexamples/databricks_mapping.py: Databricks-inspired source-to-target mapping demo with labeled group lanes
Development
Build the frontend first, then build the Python package:
cd streamlit_schema_editor/frontend
npm install
npm run build
cd ../..
uv build
Testing
Run the Python tests:
uv run pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamlit_schema_editor-0.1.0.tar.gz.
File metadata
- Download URL: streamlit_schema_editor-0.1.0.tar.gz
- Upload date:
- Size: 218.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6ee4b921fa948477ccab17512da2e8f978972fc4c512624d9193dc083891a6a
|
|
| MD5 |
598ab72911dec50194125609078cc3f7
|
|
| BLAKE2b-256 |
d9f0a29759982e15d6b4a4e3211b4daa230eb0a97341c2c2fc0c6c51287732bc
|
File details
Details for the file streamlit_schema_editor-0.1.0-py3-none-any.whl.
File metadata
- Download URL: streamlit_schema_editor-0.1.0-py3-none-any.whl
- Upload date:
- Size: 166.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
662cd43fc861860d54e3d71b8fcc357a726653b61915ce10f7f63c62f43a1b70
|
|
| MD5 |
943784066990b2adc8048a6d68575a0a
|
|
| BLAKE2b-256 |
73615decb7cff7f39d76b0b1b903a290beced6447ef2c544877e146b8d6fe6d5
|