Skip to content

Bind widgets to query params - support empty values for clearable widgets#13815

Open
mayagbarnes wants to merge 4 commits intodevelopfrom
allow-empty
Open

Bind widgets to query params - support empty values for clearable widgets#13815
mayagbarnes wants to merge 4 commits intodevelopfrom
allow-empty

Conversation

@mayagbarnes
Copy link
Collaborator

@mayagbarnes mayagbarnes commented Feb 4, 2026

Describe your changes

Adds support for empty query parameter values (?foo=) in widget URL binding, allowing clearable widgets (multiselect, pills, etc.) to represent their cleared state in the URL.

Changes:

  • Add explicit empty-value handling for query param parsing and URL seeding
  • Introduce clearable flags in backend/frontend bindings to control empty state behavior

Note: We reserve the empty string ("") to represent the cleared state in query params, so a literal empty‑string option isn’t supported. This avoids ambiguity between ?foo= (cleared) and an empty option value

Testing Plan

  • JS & Python Unit Tests: ✅ Added
    (Will be able to more explicitly test this behavior in widget implementations to come)

@mayagbarnes mayagbarnes added security-assessment-completed Security assessment has been completed for PR change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 4, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

✅ PR preview is ready!

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

@snyk-io
Copy link
Contributor

snyk-io bot commented Feb 4, 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.

Copy link
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

This PR extends query-parameter bindings so that widgets can encode a cleared/empty state via empty URL values like ?foo=, with explicit backend/ frontend flags controlling when empties are treated as valid versus invalid. It also tightens the parsing and normalization of empty and partially-empty URL values across Python and TypeScript, and adds targeted unit tests to lock in the new behavior.

Changes:

  • Add allow_empty metadata on widgets (Python) and clearable bindings (TypeScript) to control whether an empty URL value is a valid state or should be cleared.
  • Introduce is_empty_url_value and augment parse_url_param to treat all-empty values ("", [""], ["", ""], etc.) as a special “cleared” representation, with type-specific handling for arrays vs scalars.
  • Update backend seeding and frontend URL sync logic (including URL-writing for empty arrays and nulls) plus comprehensive tests to ensure consistent behavior when widgets clear, reset to defaults, or differ from defaults.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
lib/tests/streamlit/runtime/state/session_state_test.py Adds tests around _seed_widget_from_url to confirm that empty URL values seed or clear widgets correctly based on the new allow_empty flag and clear the underlying query param when empties are not allowed.
lib/tests/streamlit/runtime/state/query_params_test.py Extends tests for parse_url_param to cover empty values for array and scalar types and adds coverage for the new is_empty_url_value helper.
lib/streamlit/runtime/state/widgets.py Extends register_widget to accept and document an allow_empty flag, passing it through into WidgetMetadata for later use in URL seeding.
lib/streamlit/runtime/state/session_state.py Imports is_empty_url_value and uses metadata.allow_empty in _seed_widget_from_url so that empty URL values are either accepted (and seeded) or treated as invalid and cleared.
lib/streamlit/runtime/state/query_params.py Adds is_empty_url_value and updates parse_url_param to treat all-empty values as a special cleared state, filter out empty strings from array types, and reserve "" as a non-option sentinel.
lib/streamlit/runtime/state/common.py Extends WidgetMetadata with an allow_empty flag, with documentation tying it to the widget’s UI clearing semantics.
frontend/lib/src/WidgetStateManager.ts Extends query param bindings with an optional clearable flag, adds isEmptyValueValid and enhanced shouldClearUrlParam logic, and tweaks URL update behavior to encode empty arrays as ?key= while still clearing when at default or non-clearable.
frontend/lib/src/WidgetStateManager.test.ts Adds a suite of tests validating URL sync for empty arrays, clearable vs non-clearable widgets, default-based “hide at default” behavior, and the new clearable / fallback semantics.

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

github-actions bot commented Feb 4, 2026

Summary

This PR adds support for empty query parameter values (?foo=) in widget URL binding, enabling clearable widgets (multiselect, pills, etc.) to represent their cleared state in the URL. The key changes include:

  • Adding an explicit clearable flag to both backend (WidgetMetadata) and frontend (QueryParamBinding) binding structures
  • Introducing is_empty_url_value() helper function to detect empty URL parameters
  • Updating _seed_widget_from_url() to handle empty values based on the clearable flag
  • Requiring clearable to be explicitly set when using bind='query-params'

Code Quality

The code is well-structured with clear function names and follows the Streamlit codebase conventions.

Frontend (WidgetStateManager.ts):

  • Clean implementation of empty value handling in convertToUrlValue() (lines 1214-1269)
  • The isEmptyValueValid() method is appropriately simplified to just return binding.clearable (lines 1277-1280)
  • Good edge case handling in isDefaultValue() for cleared scalar widgets with empty array defaults (lines 1427-1435)

Backend (query_params.py, session_state.py, widgets.py):

  • The is_empty_url_value() function correctly handles all edge cases including ["", ""] (lines 67-83 in query_params.py)
  • Clean integration in _seed_widget_from_url() with early return for non-clearable widgets with empty values (lines 1019-1023 in session_state.py)
  • Good validation in register_widget() requiring clearable when bind='query-params' (lines 143-148 in widgets.py)

Minor Observations:

  • The parameter order change in registerQueryParamBinding puts clearable before the optional urlFormat and options, which is the correct pattern for required parameters.

Test Coverage

The test coverage is comprehensive and follows best practices:

Python Tests:

  • test_parse_empty_string_values - Parameterized tests for all value types with empty string inputs
  • test_is_empty_url_value - Comprehensive edge case coverage including mixed values like ["a", ""]
  • test_seed_widget_with_empty_url_and_clearable_true/false - Both positive and negative assertions
  • test_seed_widget_with_empty_list_and_clearable_true/false - List variant testing
  • Tests include negative assertions (e.g., verifying URL is cleared when clearable=False)

TypeScript Tests:

  • it("preserves empty value in URL when clearable=true") - Multiple widget types tested
  • it("clears URL param when clearable=false") - Negative case covered
  • it("clears URL when empty array equals default") - Hide-at-default behavior
  • it("preserves empty array in URL when empty differs from default") - Differentiation from default
  • Tests use both positive and negative assertions appropriately

E2E Tests:

  • The PR notes E2E tests will be added when widget implementations use this feature, which is reasonable for infrastructure changes.

Backwards Compatibility

The changes maintain backwards compatibility:

  1. clearable defaults to False in WidgetMetadata (common.py line 167), preserving existing behavior for widgets that don't support clearing
  2. Explicit requirement for bind='query-params' - While clearable must now be explicitly set when using URL binding, this is an internal API that widget implementations control
  3. Empty string reservation - The PR notes that empty string "" is reserved to mean "cleared/empty" rather than a literal option value. This is a reasonable trade-off documented in the PR description

Note: Existing widget code using registerQueryParamBinding will need to add the clearable parameter, but this is intentional to force explicit handling.

Security & Risk

No security concerns identified:

  • No user input is executed or evaluated unsafely
  • URL parameter parsing has proper validation and error handling
  • Invalid values are cleared rather than causing errors

Low regression risk:

  • The default behavior (clearable=False) preserves existing widget behavior
  • Comprehensive test coverage reduces regression risk
  • Changes are additive to the existing query param binding infrastructure

Accessibility

Not applicable - this PR focuses on URL state synchronization infrastructure and does not introduce any UI changes or frontend components that would affect accessibility.

Recommendations

  1. Minor documentation suggestion: Consider adding a note to the widget development documentation explaining that "" (empty string) is reserved for representing the "cleared" state and cannot be used as a literal option value.

  2. Type annotation improvement (optional): The clearable: bool | None parameter in register_widget() could use an overload or more specific type to indicate it's required when bind='query-params', though the runtime validation is sufficient.

  3. Future consideration: When implementing this in specific widgets, ensure E2E tests cover the URL synchronization behavior comprehensively.

Verdict

APPROVED: This is a well-implemented infrastructure change with comprehensive test coverage. The design decision to require explicit clearable configuration is good API design that forces widget developers to consciously handle empty states. The code follows Streamlit conventions and includes appropriate documentation through docstrings.


This is an automated AI review using opus-4.5-thinking. Please verify the feedback and use your judgment.

@mayagbarnes mayagbarnes marked this pull request as ready for review February 4, 2026 20:23
@mayagbarnes mayagbarnes added impact:internal PR changes only affect internal code and removed impact:users PR changes affect end users labels Feb 4, 2026
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:internal PR changes only affect internal code security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant