Routing & URLs
Goal
Switch UI inside one Streamlit script using Routes and/or keep session aligned
with query parameters (sync_query_value, set_query_value, clears).
Pick one primary navigation model
Streamlit’s native pages/ directory is a separate entrypoint per page. Routes
switches subtrees within one script using query params. Mixing both as “primary” nav
without discipline confuses users—pick one model per product (see Roadmap).
In-script routing with Routes
Full demo:
"""Multi-page demo: query-param routing + optional Pydantic JSON form."""
from __future__ import annotations
import json
from pydantic import BaseModel, ValidationError
from streamtree import component, render
from streamtree.elements import Button, Form, Markdown, Page, Routes, Text, TextInput, Title, VStack
from streamtree.forms import format_validation_errors, model_validate_json
from streamtree.routing import set_route
from streamtree.state import form_state, state
class Profile(BaseModel):
name: str
email: str
@component
def ProfileForm() -> object:
raw = form_state('{"name":"","email":""}', key="profile_json")
err = state("", key="profile_err")
def submit() -> None:
try:
model_validate_json(Profile, raw.edit_value())
err.set("")
raw.commit()
except (ValidationError, json.JSONDecodeError, TypeError) as e:
if isinstance(e, ValidationError):
err.set(format_validation_errors(e))
else:
err.set(str(e))
return Form(
Title("Profile (JSON)"),
TextInput("JSON payload", value=raw),
Markdown(err() or " "),
Button("Validate & commit", submit=True, on_click=submit),
form_key="profile_form",
)
@component
def Nav() -> object:
return VStack(
Text("Pages use query param `route`:"),
Button("Home", on_click=lambda: set_route("home")),
Button("Profile", on_click=lambda: set_route("profile")),
)
@component
def App() -> object:
return Page(
Nav(),
Routes(
routes=(
(
"home",
VStack(Title("Home"), Text("Open Profile to try Pydantic JSON validation.")),
),
("profile", ProfileForm()),
),
default="home",
),
)
if __name__ == "__main__":
render(App())
Run:
Query string filters
Use streamtree.routing.sync_query_value / set_query_value when a string param
should both appear in the URL and live in session_state. Batch updates and clears are
in update_query_params, clear_query_param, clear_route (see README and
streamtree.routing docstrings).
Recipe: deep-link a detail id
- Store
selected_id_from_query(or a thin wrapper) fromstreamtree.crudwhen you follow Phase 3 CRUD. - Render list vs detail subtree based on id presence.
- Bump a save-intent counter on “Save” so async work keys stay unique across logical saves.
See also
- Multipage navigation —
pages/helpers - Async & loading — URL-driven fetch patterns
- Examples —
routed_app.py, CRUD demos