Skip to content

Bind widgets to query params - st.text_input & st.text_area#13901

Merged
mayagbarnes merged 5 commits intodevelopfrom
query-text
Feb 11, 2026
Merged

Bind widgets to query params - st.text_input & st.text_area#13901
mayagbarnes merged 5 commits intodevelopfrom
query-text

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 10, 2026

Describe your changes

Adds the bind parameter to st.text_input & st.text_area to enable two-way sync between widget values and URL query parameters.

  • Raises StreamlitAPIException when bind="query-params" is used with type="password" on text_input
  • TextArea consistency fix: Removed extra element.default fallback in getStateFromWidgetMgr to align with TextInput and fix URL-seeded values not populating the UI
  • Typing tests for public API signatures

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 10, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 10, 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 10, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13901/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13901.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 to Streamlit’s text widgets, enabling two-way sync between st.text_input / st.text_area values and the browser URL (consistent with existing query-param binding patterns used by other widgets like st.color_picker).

Changes:

  • Extend TextInput and TextArea protobuf messages with query_param_key.
  • Add bind="query-params" support in the Python widget implementations (including URL seeding behavior + max_chars truncation on deserialize, and a password safety guard for text_input).
  • Add frontend query-param binding registration via useBasicWidgetState, plus unit tests and Playwright E2E coverage.

Reviewed changes

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

Show a summary per file
File Description
proto/streamlit/proto/TextInput.proto Adds query_param_key field to support frontend query-param binding.
proto/streamlit/proto/TextArea.proto Adds query_param_key field to support frontend query-param binding.
lib/streamlit/elements/widgets/text_widgets.py Adds bind param plumbing, sets proto query key, enforces password restriction, and wires binding into widget registration.
lib/tests/streamlit/elements/text_input_test.py Adds unit tests validating bind behavior, errors, and proto wiring for st.text_input.
lib/tests/streamlit/elements/text_area_test.py Fixes an incorrect proto access and adds unit tests validating bind behavior for st.text_area.
frontend/lib/src/components/widgets/TextInput/TextInput.tsx Registers query-param binding when proto specifies queryParamKey.
frontend/lib/src/components/widgets/TextInput/TextInput.test.tsx Adds unit tests ensuring query-param binding registration/unregistration behavior.
frontend/lib/src/components/widgets/TextArea/TextArea.tsx Registers query-param binding when proto specifies queryParamKey; adjusts WidgetStateManager state getter behavior.
frontend/lib/src/components/widgets/TextArea/TextArea.test.tsx Adds unit tests ensuring query-param binding registration/unregistration behavior.
e2e_playwright/st_text_input.py Adds bound text input examples used by E2E tests.
e2e_playwright/st_text_input_test.py Adds E2E coverage for URL seeding, URL updates/removal, special chars, and max_chars truncation.
e2e_playwright/st_text_area.py Adds bound text area examples used by E2E tests.
e2e_playwright/st_text_area_test.py Adds E2E coverage for URL seeding, URL updates/removal, special chars, and max_chars truncation.

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

Summary

This PR adds bind="query-params" support to st.text_input and st.text_area, enabling two-way synchronization between widget values and URL query parameters. The changes span the full stack:

  • Protobuf: New query_param_key field added to both TextInput and TextArea messages.
  • Python backend: bind parameter added to both widget APIs, with serde truncation for max_chars, password-binding prevention, and validation.
  • Frontend: queryParamBinding configuration wired into the existing useBasicWidgetState hook for both components.
  • Tests: Python unit tests, frontend unit tests, and E2E tests added.
  • Bug fix: Pre-existing bug in text_area_test.py corrected — test_outside_form was incorrectly checking color_picker proto instead of text_area.

Code Quality

The implementation is clean, consistent, and follows the established patterns from the existing bind support in st.color_picker. Key observations:

  • The TextInputSerde and TextAreaSerde dataclasses were enhanced with max_chars for truncation during deserialization. This is important for handling URL-seeded values that may exceed max_chars. Well-designed approach.
  • The password/bind guard (text_widgets.py line 415-419) is a good security practice — passwords should never appear in URLs.
  • Frontend changes are minimal and leverage existing infrastructure (useBasicWidgetState hook with queryParamBinding config), following the same pattern as ColorPicker.tsx.
  • The clearable: true flag is correctly set for both widgets, since empty string is a valid value for text inputs/areas.
  • Docstrings are properly updated with Numpydoc style following existing patterns.

Minor note on text_widgets.py: The clearable=True is now always passed to register_widget for both text_input and text_area, even when bind is None. Per the register_widget code, when bind is None, the clearable value defaults to False anyway and is only validated when bind="query-params". So passing clearable=True unconditionally is harmless, but slightly unnecessary when bind is not set. This follows the same pattern as color_picker.py though, so it's consistent.

Test Coverage

Test coverage is thorough across all three layers:

Python unit tests (text_input_test.py, text_area_test.py):

  • Tests for bind='query-params' setting query_param_key in proto.
  • Tests for missing key raising StreamlitAPIException.
  • Tests for invalid bind values raising StreamlitInvalidBindValueError.
  • Tests for bind with default value and max_chars.
  • Test for password+bind combination raising an exception (text_input only).
  • The test_outside_form bug fix (color_pickertext_area) is a welcome correction.

Frontend unit tests (TextInput.test.tsx, TextArea.test.tsx):

  • Query param binding registration/unregistration lifecycle tests.
  • Negative test for no registration when queryParamKey is absent.
  • Tests with custom default values.

E2E tests (st_text_input_test.py, st_text_area_test.py):

  • Seeding from URL query params.
  • URL updates on widget value change.
  • Default value override and param removal when resetting to default.
  • Special characters (URL encoding).
  • max_chars truncation of URL-seeded values.
  • Both positive and negative URL assertions.

One minor concern with E2E tests: The regex r"bound_text" (line 392, 410 of st_text_input_test.py) could theoretically match other params like bound_text_default. In practice this doesn't cause issues because those params are at their defaults and won't appear in the URL. Using a more precise regex like r"bound_text=" or r"[?&]bound_text=" would be slightly more robust, but this is a very minor nit.

Backwards Compatibility

This change is fully backwards compatible:

  • The bind parameter defaults to None for both widgets, preserving existing behavior.
  • The new protobuf fields (query_param_key) are optional, so older clients/servers handle them gracefully.
  • The TextInputSerde and TextAreaSerde max_chars field defaults to None, meaning existing usage without max_chars is unaffected.
  • No existing API signatures are changed — bind is added as a new keyword-only argument.

Security & Risk

  • Password protection: The explicit guard against bind='query-params' with type='password' is a strong security measure. Passwords in URLs would be visible in browser history, server logs, and referrer headers.
  • URL injection: Query param values go through standard URL encoding/decoding and the existing max_chars truncation, so there's no risk of injection.
  • No regression risk: The serde truncation change is additive — max_chars=None (the default) preserves the original behavior.

Accessibility

No accessibility concerns. The frontend changes are limited to wiring up the queryParamBinding configuration object, which doesn't alter the DOM structure, ARIA attributes, or keyboard interactions. The widgets continue to use their existing accessible labels, roles, and focus management.

Recommendations

  1. Minor (E2E regex precision): In st_text_input_test.py lines 392 and 410, consider using r"[?&]bound_text=" instead of r"bound_text" to avoid potential false matches with bound_text_default or bound_max. Same applies to st_text_area_test.py lines 390 and 408 with r"bound_text_area". This is a very minor robustness improvement and not a blocker.

  2. Consider adding typing test: Per the lib/streamlit/AGENTS.md guidelines, there are typing tests in lib/tests/streamlit/typing/ for public API. A typing test for the new bind parameter on text_input and text_area could be added to ensure the type signature stays correct. However, if bind is already covered by an existing shared type test, this may not be needed.

Verdict

APPROVED: Clean, well-tested implementation that follows established patterns from st.color_picker's bind support. The security guard for password inputs is a thoughtful addition. All layers (proto, backend, frontend, tests) are properly covered.


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

@mayagbarnes mayagbarnes marked this pull request as ready for review February 11, 2026 00:15
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 👍


def test_text_area_query_param_seeding(page: Page, app_port: int):
"""Test that text area value can be seeded from URL query params."""
page.goto(f"http://localhost:{app_port}/?bound_text_area=seeded_value")
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.

note: this will likely need an update once #13679 is merged. cc @sfc-gh-bnisco

widgetMgr: WidgetStateManager,
element: TextAreaProto
): TextAreaValue | undefined => {
return widgetMgr.getStringValue(element) ?? element.default ?? null
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.

question: Does widgetMgr.getStringValue return the default value, or was element.default not necessary here?

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.

element.default was not necessary and it was breaking URL-seeded values. When getStringValue returns undefined on first render, the element.default fallback produced a non-nullish value that short-circuited the hook's fallback to getDefaultState. That prevented useBasicWidgetClientState from ever checking element.setValue / element.value, so URL-seeded values never populated the UI. This also makes TextArea consistent with TextInput, which never had this fallback.

@mayagbarnes mayagbarnes merged commit f345cbe into develop Feb 11, 2026
43 checks passed
@mayagbarnes mayagbarnes deleted the query-text branch February 11, 2026 23:33
by22Jy added a commit to by22Jy/streamlit that referenced this pull request Mar 6, 2026
Fixes regression in st.text_area height calculation when using height="content".

The issue was introduced in PR streamlit#13901, which removed the element.default
fallback from getStateFromWidgetMgr. This caused the textarea to be empty
during initial render, leading to incorrect height calculation by
useTextInputAutoExpand.

The fix adds uiValue to the dependencies of useTextInputAutoExpand, ensuring
the height is recalculated when the value is updated.

Fixes streamlit#14222
lukasmasuch added a commit that referenced this pull request Mar 6, 2026
## Describe your changes

Fixes regression in `st.text_area` with `height="content"` introduced in
v1.55.0 (#14222).

- Added `uiValue` to `useTextInputAutoExpand` dependencies so height
recalculates when default value is set

**Root cause:** PR #13901 removed `element.default` fallback from
`getStateFromWidgetMgr`. The textarea starts empty when
`useLayoutEffect` calculates auto-expand height; the default value
arrives later via `useEffect` but height was never recalculated.

## GitHub Issue

Fixes #14222

## Testing Plan

- [x] Unit Tests
(`frontend/lib/src/components/widgets/TextArea/TextArea.test.tsx` — 28
tests pass)
- [x] E2E Tests
(`e2e_playwright/st_text_area_test.py::test_text_area_content_height_expansion`
— passes)
- [x] E2E Tests
(`e2e_playwright/st_text_area_test.py::test_text_area_content_height_default_value`
— new regression test)

<!-- agent-metrics-start -->
<details>
<summary>Agent metrics</summary>

| Type | Name | Count |
|------|------|------:|
| subagent | fixing-pr | 2 |

</details>
<!-- agent-metrics-end -->
lukasmasuch added a commit that referenced this pull request Mar 10, 2026
## Describe your changes

Fixes regression in `st.text_area` with `height="content"` introduced in
v1.55.0 (#14222).

- Added `uiValue` to `useTextInputAutoExpand` dependencies so height
recalculates when default value is set

**Root cause:** PR #13901 removed `element.default` fallback from
`getStateFromWidgetMgr`. The textarea starts empty when
`useLayoutEffect` calculates auto-expand height; the default value
arrives later via `useEffect` but height was never recalculated.

## GitHub Issue

Fixes #14222

## Testing Plan

- [x] Unit Tests
(`frontend/lib/src/components/widgets/TextArea/TextArea.test.tsx` — 28
tests pass)
- [x] E2E Tests
(`e2e_playwright/st_text_area_test.py::test_text_area_content_height_expansion`
— passes)
- [x] E2E Tests
(`e2e_playwright/st_text_area_test.py::test_text_area_content_height_default_value`
— new regression test)

<!-- agent-metrics-start -->
<details>
<summary>Agent metrics</summary>

| Type | Name | Count |
|------|------|------:|
| subagent | fixing-pr | 2 |

</details>
<!-- agent-metrics-end -->
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