Skip to content

Bind widgets to query params - st.date_input#14034

Merged
mayagbarnes merged 6 commits intodevelopfrom
query-date
Feb 24, 2026
Merged

Bind widgets to query params - st.date_input#14034
mayagbarnes merged 6 commits intodevelopfrom
query-date

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 20, 2026

Describe your changes

Adds bind="query-params" to st.date_input for two-way sync between widget values and URL query parameters.

Key changes:

  • Wire format modernized to ISO 8601: The internal protobuf wire format for date_input is changed from YYYY/MM/DD to YYYY-MM-DD. This is an internal-only change — the user-facing display format (controlled by the format parameter) is unaffected. Both frontend and backend parsers already accepted ISO dates, so backward compatibility is maintained via fallback parsing.
  • Uses string_array_value for the proto value type. Single date: ?my_date=2025-01-15. Date range: ?my_date=2025-01-15&my_date=2025-01-31.
  • Clearability reflects widget behavior: clearable when value=None (empty URL param ?my_date= clears to None), non-clearable otherwise.
  • Invalid URL values (e.g. ?my_date=not-a-date) silently revert to the widget's default value, matching the pattern used by st.radio and st.selectbox.
  • Out-of-bounds URL values (e.g. a date outside min_value/max_value) are reset to the widget default, and the stale URL param is explicitly cleared.

Testing Plan

  • JS & Python Unit Tests: ✅ Added
  • E2E Tests: ✅ Added
  • Manual Testing: ✅

@mayagbarnes mayagbarnes added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 20, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 20, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 20, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14034/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14034.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds URL query-parameter binding support (bind="query-params") for st.date_input, enabling two-way sync between widget state and URL while modernizing the internal wire date format to ISO 8601.

Changes:

  • Add query_param_key to the DateInput proto and wire up backend + frontend query-param binding for st.date_input.
  • Modernize the date_input protobuf wire format from YYYY/MM/DD to ISO YYYY-MM-DD, with backend fallback parsing for legacy values.
  • Add/adjust Python, TypeScript, and Playwright E2E tests to cover query-param seeding, clearing, invalid values, and bounds behavior.

Reviewed changes

Copilot reviewed 11 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
proto/streamlit/proto/DateInput.proto Adds query_param_key to support URL binding from backend to frontend.
lib/streamlit/elements/widgets/time_widgets.py Implements bind for st.date_input, adds ISO serialization, URL seeding behavior, and URL cleanup on bounds reset.
lib/streamlit/testing/v1/element_tree.py Updates date parsing for min/max to match ISO wire format in tests.
frontend/lib/src/components/widgets/DateInput/DateInput.tsx Switches date parsing/serialization to ISO and registers query param binding when queryParamKey is present.
frontend/lib/src/components/widgets/DateInput/DateInput.test.tsx Updates tests for ISO wire format and adds query-param binding unit tests.
frontend/lib/src/WidgetStateManager.ts Adds Date handling for default comparisons when deciding whether to clear URL params.
frontend/lib/src/WidgetStateManager.test.ts Adds unit tests verifying Date[] defaults compare correctly against URL string-array values.
lib/tests/streamlit/elements/date_input_test.py Updates expected wire-format strings and adds backend bind + serde parsing unit tests.
lib/tests/streamlit/typing/date_input_types.py Extends typing checks to cover the new bind parameter.
e2e_playwright/st_date_input.py Adds bound st.date_input widgets used by the new E2E scenarios.
e2e_playwright/st_date_input_test.py Adds E2E coverage for URL seeding/clearing/invalid/out-of-range behavior for date_input.
e2e_playwright/snapshots/linux/st_date_input_test/*.png Updates snapshots impacted by the additional widgets / rendering changes.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@mayagbarnes mayagbarnes added the ai-review If applied to PR or issue will run AI review workflow label Feb 20, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: PR #14034 — Bind widgets to query params - st.date_input

Summary

This PR adds bind="query-params" support to st.date_input, enabling two-way sync between the widget's value and URL query parameters. Changes span the full stack:

  • Protobuf: Added optional string query_param_key = 14 to DateInput.proto.
  • Backend (Python): Added bind parameter to all st.date_input overloads, wired through serde/registration. Modernized internal date wire format from YYYY/MM/DD to ISO 8601 YYYY-MM-DD, with backward-compatible fallback parsing. Added bounds validation in deserialize so out-of-range URL values revert to defaults.
  • Frontend (TypeScript/React): DateInput.tsx constructs queryParamBinding using string_array_value, with urlFormat: "repeated" for range mode and clearable derived from empty defaults. WidgetStateManager.toStringPrimitive now supports Date objects.
  • Tests: Comprehensive coverage across Python unit tests, frontend unit tests, E2E (Playwright), and type tests.

Code Quality

Both reviewers agree: The implementation is clean, well-structured, and follows established patterns from other bound widgets (slider, selectbox, text_input, etc.). No blocking code quality issues were found.

Minor pre-existing issue noted by opus-4.6-thinking: In DateInputSerde.serialize, the list comprehension variable v shadows the function parameter v. This is not introduced by this PR but could be improved in a follow-up.

Test Coverage

Both reviewers agree: Test coverage is thorough and appropriately distributed across all layers:

  • Python unit tests (date_input_test.py): Bind behavior, serde parsing/serialization, bounds reset, ISO and legacy format parsing, invalid input handling.
  • Frontend unit tests (DateInput.test.tsx, WidgetStateManager.test.ts): Query-param registration/unregistration, ISO wire values, seeded values, Date default-comparison for URL clearing.
  • E2E tests (st_date_input_test.py): URL seeding, invalid value revert, clearable behavior, range seeding, out-of-bounds reset, default-clearing behavior.
  • Type tests (date_input_types.py): assert_type checks for bind parameter with all return type variants.

Existing tests were properly updated to reflect the wire format change.

Backwards Compatibility

Both reviewers agree: Backwards compatibility is well-handled:

  • Dual-format parsing: Backend _parse_date accepts both %Y-%m-%d and %Y/%m/%d. Frontend moment() is lenient with separators. Mixed-version rolling deployments are safe.
  • Behavioral tightening: Out-of-range values in deserialize now revert to defaults. This is reasonable since out-of-range dates were already invalid at the widget validation level.
  • API compatibility: bind defaults to None, so existing code is unaffected. The parameter is keyword-only in all overloads.

Security & Risk

Both reviewers agree: No security concerns. URL query parameter values are validated and sanitized. Invalid formats revert to defaults, out-of-bounds values are rejected, and the bind value is validated against a known set.

Low-to-moderate regression risk, well-mitigated by cross-layer tests and fallback parsing.

Accessibility

Both reviewers agree: No accessibility changes introduced. The bind parameter is a backend/URL sync feature that doesn't alter the widget's DOM structure or interaction model.

Recommendations

No blocking issues. Minor suggestions for potential follow-ups (non-blocking):

  1. Legacy URL format regression test (gpt-5.3-codex-high): Consider adding an explicit E2E or unit test for URL seeding with YYYY/MM/DD (slash format) to directly validate the backward-compatible parsing path.
  2. Variable shadowing cleanup (opus-4.6-thinking): Rename the list comprehension variable v to d in DateInputSerde.serialize to avoid shadowing the function parameter.
  3. Year zero-padding (opus-4.6-thinking): getFullYear() in toStringPrimitive is not zero-padded to 4 digits. Extremely minor edge case (dates before year 1000), but .toString().padStart(4, "0") would ensure strict ISO 8601 compliance.

Reviewer Agreement

Area gpt-5.3-codex-high opus-4.6-thinking Consensus
Code Quality Clean, no blocking issues Clean, follows patterns Agree
Test Coverage Strong Thorough Agree
Backwards Compat Good Well-handled Agree
Security No concerns No concerns Agree
Accessibility No concerns No concerns Agree
Verdict APPROVED APPROVED APPROVED

No disagreements between reviewers. Both independently identified the same strengths and reached the same conclusion.

Verdict

APPROVED: Both reviewers unanimously approve. The feature is well-implemented across all layers, follows established patterns, has thorough test coverage, maintains backward compatibility through dual-format parsing, and correctly handles edge cases (invalid input, out-of-bounds values, clearable widgets). No blocking issues were identified.


Consolidated review by opus-4.6-thinking. Individual reviews below.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR adds bind="query-params" support to st.date_input, including backend/frontend wiring, protobuf support, and tests. It also updates the internal date wire format from YYYY/MM/DD to ISO YYYY-MM-DD, with backend fallback parsing for legacy values and URL reset behavior for invalid or out-of-bounds query params.

Code Quality

The implementation is cohesive across layers (proto, Python widget serde/registration, frontend widget state binding, and e2e coverage), and follows established query-param binding patterns used by other widgets.

No blocking code quality issues found.

Test Coverage

Coverage is strong and appropriately distributed:

  • Python unit tests cover bind behavior, serde parsing/serialization, and bounds reset behavior in lib/tests/streamlit/elements/date_input_test.py.
  • Frontend unit tests cover ISO wire values, query-param registration/unregistration, seeded values, and default-comparison behavior in frontend/lib/src/components/widgets/DateInput/DateInput.test.tsx and frontend/lib/src/WidgetStateManager.test.ts.
  • E2E tests cover URL seeding, invalid values, clearable behavior, range seeding, and out-of-range resets in e2e_playwright/st_date_input_test.py.
  • Typing coverage was updated in lib/tests/streamlit/typing/date_input_types.py.

Backwards Compatibility

Backwards compatibility looks good:

  • Backend deserialization accepts both ISO and legacy slash formats via DateInputSerde._parse_date in lib/streamlit/elements/widgets/time_widgets.py.
  • Frontend and backend now consistently serialize date-input wire values as ISO, reducing ambiguity and improving URL interoperability.
  • Query params are cleared when values are default or invalid/out-of-bounds, matching current widget binding behavior patterns.

Security & Risk

No direct security concerns identified.
Regression risk is low-to-moderate due to protocol/value-format touchpoints, but mitigated by cross-layer tests and explicit fallback parsing.

Accessibility

No new accessibility concerns identified.
The frontend changes do not introduce new interaction primitives; they reuse existing date input behavior.

Recommendations

  1. Add one explicit regression test for legacy URL seeding with YYYY/MM/DD (slash format) to directly validate the compatibility path claimed by DateInputSerde._parse_date.

Verdict

APPROVED: The feature is implemented cleanly across backend/frontend/proto with solid test coverage and no blocking defects found.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds bind="query-params" support to st.date_input, enabling two-way sync between widget values and URL query parameters. Key changes include:

  • Added a bind parameter to the st.date_input public API (all overloads).
  • Added optional string query_param_key = 14 to the DateInput protobuf message.
  • Modernized the internal wire format from YYYY/MM/DD to ISO 8601 YYYY-MM-DD, with backward-compatible fallback parsing on both backend (_parse_date tries both formats) and frontend (moment.js is lenient with separators by default).
  • Added bounds validation in DateInputSerde.deserialize so out-of-range URL values revert to the widget default.
  • Frontend DateInput.tsx constructs the queryParamBinding object using string_array_value, with urlFormat: "repeated" for range mode and clearable derived from empty defaults.
  • WidgetStateManager.toStringPrimitive now supports Date objects (formatted as YYYY-MM-DD) to enable correct default-comparison logic for URL parameter clearing.

Code Quality

The implementation is clean, well-structured, and follows existing patterns established by other bound widgets (slider, selectbox, text_input, etc.):

  • The queryParamBinding construction in DateInput.tsx (lines 102–109) mirrors the pattern used by Slider.tsx and other widgets.
  • The DateInputSerde._parse_date static method (lines 583–591 of time_widgets.py) provides clean backward-compatible parsing.
  • The out-of-bounds URL clearing via qp.remove_param (lines 1744–1746 of time_widgets.py) is a thoughtful touch that prevents stale URL params from persisting after reset.
  • The toStringPrimitive Date formatting (lines 1414–1418 of WidgetStateManager.ts) is straightforward and correct for the expected date range.

Minor note: In DateInputSerde.serialize (line 625 of time_widgets.py), the list comprehension variable v shadows the function parameter v. This is pre-existing and not introduced by this PR, but could be improved for clarity in a follow-up.

Test Coverage

Testing is thorough across all layers:

  • Python unit tests (date_input_test.py): 7 bind-specific tests covering proto field setting, missing key error, invalid bind value, explicit value, None value, and range mode. 11 serde tests covering ISO parsing, legacy fallback, invalid input, None, empty range, out-of-bounds (single and range), boundary values, and serialization.
  • Frontend unit tests (DateInput.test.tsx): 7 query-param binding tests covering registration, unregistration, no-binding case, clearable flag, range urlFormat, and URL-seeded values for both single and range modes.
  • WidgetStateManager tests: 3 tests specifically for Date object default comparison ensuring URL clearing works correctly when string array values match Date[] defaults.
  • E2E tests (st_date_input_test.py): 6 E2E tests covering query param seeding, clearable empty state, invalid value revert, range seeding, out-of-bounds reset, and default-clearing behavior.
  • Type tests (date_input_types.py): assert_type checks for bind parameter with all return type variants.

The existing tests were properly updated to reflect the wire format change (YYYY/MM/DDYYYY-MM-DD).

Backwards Compatibility

Wire format change: The internal protobuf format for date strings changed from YYYY/MM/DD to YYYY-MM-DD. This is the most significant compatibility consideration:

  • Backend → Frontend: DateInputSerde._parse_date accepts both %Y-%m-%d and %Y/%m/%d formats. Frontend moment(val, "YYYY-MM-DD") is lenient with separators by default.
  • Frontend → Backend: The frontend sends ISO format; the backend parser accepts both.
  • Rolling deployments: Both sides can handle either format, so mixed-version scenarios are safe.

Behavioral change in deserialize: The addition of bounds validation in DateInputSerde.deserialize means that out-of-range values (from any source, not just query params) now revert to the default. This is a tightening of behavior that could theoretically affect AppTest users who set dates outside computed bounds. The test change from date(2012, 1, 3) to date(2025, 6, 15) demonstrates this. This is a reasonable change since setting out-of-range dates was already invalid, and the separate _validate_date_value function already handled this at the widget level.

API compatibility: The bind parameter defaults to None, so existing code is unaffected. The parameter is keyword-only in all overloads.

Security & Risk

No security concerns identified:

  • URL query parameter values are validated and sanitized (invalid formats revert to defaults, out-of-bounds values are rejected).
  • No user input is used in string formatting or code execution paths.
  • The bind value is validated against a known set ("query-params" or None), with StreamlitInvalidBindValueError raised for invalid values.

Low regression risk due to backward-compatible parsing and comprehensive test coverage.

Accessibility

No accessibility changes are introduced by this PR. The DateInput component continues to use the BaseWeb Datepicker with the same ARIA labels and keyboard interactions. The bind parameter is a backend/URL sync feature that doesn't alter the widget's DOM structure or interaction model.

Recommendations

No blocking issues found. Minor suggestions for potential follow-ups:

  1. Variable shadowing in serialize: In DateInputSerde.serialize (line 625 of time_widgets.py), the list comprehension variable v shadows the function parameter v. Consider renaming to d for clarity in a future cleanup pass:

    return [date.strftime(d, "%Y-%m-%d") for d in to_serialize]
  2. Year padding in toStringPrimitive: The getFullYear() value is not zero-padded to 4 digits. While dates before year 1000 are extremely unlikely for date_input, adding .toString().padStart(4, "0") would make the function strictly ISO 8601 compliant. This is a very minor edge case.

Verdict

APPROVED: Well-implemented feature that follows established patterns, has thorough test coverage across all layers, maintains backward compatibility through dual-format parsing, and handles edge cases (invalid input, out-of-bounds values, clearable widgets) correctly.


This is an automated AI review by opus-4.6-thinking.

@mayagbarnes mayagbarnes marked this pull request as ready for review February 20, 2026 22:52
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Comment on lines +1414 to +1419
if (value instanceof Date) {
const y = value.getFullYear()
const m = String(value.getMonth() + 1).padStart(2, "0")
const d = String(value.getDate()).padStart(2, "0")
return `${y}-${m}-${d}`
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: maybe a slightly nicer way might be to use toISOString on date and take the first 10 chars.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understandable - toISOString().slice(0, 10) would definitely be cleaner but opted for this pattern because toISOString() converts to UTC, while moment creates these Date objects in local time.

@mayagbarnes mayagbarnes merged commit 98217fa into develop Feb 24, 2026
44 of 45 checks passed
@mayagbarnes mayagbarnes deleted the query-date branch February 24, 2026 07:45
mayagbarnes added a commit that referenced this pull request Feb 25, 2026
…14103)

Adds `bind="query-params"` to `st.datetime_input` and `st.time_input` for two-way sync between widget values and URL query parameters.

**Key changes:**
- **`st.datetime_input` wire format modernized to ISO 8601:** The internal protobuf wire format is changed from `YYYY/MM/DD, HH:mm` to `YYYY-MM-DDThh:mm` (e.g., `2025-01-15T16:45`). This is an internal-only change — the user-facing display format (controlled by the `format` parameter) is unaffected. Both frontend and backend parsers accept both formats, so backward compatibility is maintained via fallback parsing.
- **`st.datetime_input` binding:** Uses `string_array_value` for the proto value type. URL format: `?my_dt=2025-01-15T16:45`. Clearable when `value=None` (empty URL param `?my_dt=` clears to `None`), non-clearable otherwise.
- **`st.time_input` binding:** Uses `string_value` for the proto value type. URL format: `?my_time=14:30`. Clearable when `value=None`, non-clearable otherwise.
- Invalid URL values (e.g. `?my_dt=not-a-datetime`) silently revert to the widget's default value, matching the pattern used by `st.date_input`, `st.radio`, and `st.selectbox`.
- Out-of-bounds URL values (e.g. a datetime outside `min_value`/`max_value`) are reset to the widget default, and the stale URL param is explicitly cleared.
- Follows the same patterns established in #14034 for `st.date_input`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants