Bind widgets to query params - st.number_input#13917
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
This PR adds query parameter binding support to st.number_input, enabling two-way synchronization between the widget's value and URL query parameters. When bind="query-params" is specified with a key, the widget's value is synced with the URL—changing the widget updates the URL, and loading the page with a query parameter initializes the widget from it. Values from the URL are automatically clamped to the specified min_value and max_value constraints.
Changes:
- Added
bindparameter tost.number_inputAPI with query parameter support - Implemented URL value clamping in
NumberInputSerde.deserializefor out-of-range values - Added comprehensive test coverage (Python unit tests, TypeScript unit tests, and E2E tests)
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| proto/streamlit/proto/NumberInput.proto | Added optional query_param_key field to protobuf message |
| lib/streamlit/elements/widgets/number_input.py | Implemented bind parameter, updated NumberInputSerde to clamp values to min/max bounds, added documentation |
| lib/tests/streamlit/elements/number_input_test.py | Added unit tests for bind parameter validation and serde clamping logic |
| lib/tests/streamlit/typing/number_input_types.py | Added type tests for bind parameter |
| frontend/lib/src/components/widgets/NumberInput/NumberInput.tsx | Configured query param binding in useBasicWidgetState hook |
| frontend/lib/src/components/widgets/NumberInput/NumberInput.test.tsx | Added unit tests for query param binding registration/unregistration |
| e2e_playwright/st_number_input.py | Added test widgets with query param bindings (int, float, clamped) |
| e2e_playwright/st_number_input_test.py | Added E2E tests for seeding, URL updates, clamping, and invalid values |
SummaryThis PR adds
Code QualityThe code is well-structured and follows established patterns closely. The implementation mirrors how other widgets (e.g., Strengths:
Minor observations:
Test CoverageTest coverage is thorough and well-organized: Python unit tests (
Frontend unit tests (
Typing tests (
E2E tests (
The E2E tests follow best practices well: using Backwards CompatibilityThis change is fully backwards compatible:
Security & Risk
AccessibilityNo accessibility concerns. The frontend change is purely in the data/state layer (passing Recommendations
VerdictAPPROVED: Clean, well-tested implementation that follows established patterns for query param binding, is fully backwards compatible, and includes comprehensive test coverage across all layers. This is an automated AI review by |
| if val is not None: | ||
| if self.data_type == NumberInputProto.INT: | ||
| val = int(val) | ||
| # Clamp to [min_value, max_value]. Primarily needed for | ||
| # out-of-range values seeded from URL query params; a no-op | ||
| # for frontend values since the UI already enforces bounds. | ||
| # Note: This only runs on *serialized* values (URL seeding, | ||
| # fresh frontend submissions). Already-deserialized values | ||
| # from previous runs are handled by the bounds-reset check | ||
| # in _number_input (see "Validate the current value" block). | ||
| val = max(self.min_value, min(self.max_value, val)) |
There was a problem hiding this comment.
We already have a similar mechanism in number_input to handle out-of-range values e.g., when users set it via st.session_state or when min / max dynamically change when key is set. In those cases, the session_state, return value and frontend value just gets reset to the default. E.g. you can try it here:
import streamlit as st
if st.button("Set out of range value"):
st.session_state.number_input = 101
if st.button("Set valid value"):
st.session_state.number_input = 10
st.number_input("Number input", value=5, min_value=0, max_value=100, key="number_input")
st.write(st.session_state)This might work out of the box here as well, not sure. But it might be good to double-check whether you want a different behaviour when set via query parameters (cc @jrieke).
There was a problem hiding this comment.
Ooh interesting, I didn't know we have that behavior. Is that behavior specific to number input or also for other widgets, e.g. st.date_input? Agree that it would make sense to unify – either by clamping to the min/max or resetting to default in all cases. IMO clamping to min/max feels a bit more intuitive but I think I'd be fine with both. Do you two have opinions? And which one would be easier to implement?
There was a problem hiding this comment.
Its the default behaviour for all similar cases with widgets, e.g. also if the selectbox options list changes and the currently selected option isn't part of the new options list anymore. Resetting to default is something that can be applied consistently in all situations while something like clamping is specific to the type of parameter.
There was a problem hiding this comment.
K yeah that makes sense. Hm yeah then maybe easier to just go with resetting to default, wdyt @mayagbarnes? I mean in reality I think this case will happen very very rarely anyway...
There was a problem hiding this comment.
I did like the clamping over resetting to default, especially since we remove default from the URL (so ?num=150 with max 100 → URL cleared, which can be confusing because user may think its not working vs that its an invalid entry) but there is value to consistency and Lukas raises a good point in that clamping for number_input (and likely numeric sliders & date/time widgets) would not be consistent across widgets.
Considering its probably a very rare case anyway, perhaps going with revert to default is best.
80a4e0c to
8f8411e
Compare
Adds the bind parameter to `st.number_input` to enable two-way sync between widget values and URL query parameters. - Added `bind="query-params"` support to `st.number_input` - Invalid (out of bounds, non-numeric) URL values are caught during deserialization and the query param is removed, falling back to the widget default. - Clearability is tied to `value=None`: when the widget is registered with no default value, an empty URL param (`?key=`) clears the widget to `None`. When `value` is set, empty URL params are ignored.
Describe your changes
Adds the bind parameter to
st.number_inputto enable two-way sync between widget values and URL query parameters.Key changes:
bind="query-params"support tost.number_inputvalue=None: when the widget is registered with no default value, an empty URL param (?key=) clears the widget toNone. Whenvalueis set, empty URL params are ignored.Testing Plan