Skip to content

[feature] Add support for relative static file serving URLs in media elements#14317

Merged
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/relative-static-urls
Mar 12, 2026
Merged

[feature] Add support for relative static file serving URLs in media elements#14317
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/relative-static-urls

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Mar 10, 2026

Describe your changes

Adds support for relative static URLs (/app/static/) in media elements, allowing users to reference files from Streamlit's static file serving endpoint using relative paths instead of requiring absolute URLs or local file paths.

  • Adds is_relative_static_url() helper to url_util.py for detecting /app/static/ paths
  • Updates image_to_url() to pass through relative static URLs unchanged
  • Updates marshall_audio() and marshall_video() to recognize relative static URLs
  • Updates frontend buildMediaURL() to construct proper URLs for /app/static/ paths

Supported commands: st.image, st.audio, st.video, st.chat_message(avatar), st.set_page_config(page_icon), st.logo

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (Python and TypeScript)
  • E2E Tests (Playwright)

Test files:

  • lib/tests/streamlit/url_util_test.py — Tests is_relative_static_url() with valid/invalid inputs
  • lib/tests/streamlit/elements/image_test.py — Tests image_to_url() with static URLs
  • lib/tests/streamlit/elements/media_test.py — Tests marshall_audio/video with static URLs
  • frontend/connection/src/DefaultStreamlitEndpoints.test.ts — Tests buildMediaURL() with static paths
  • e2e_playwright/config_static_serving_test.py — E2E tests for all supported media elements
Agent metrics
Type Name Count
skill addressing-pr-review-comments 1
skill checking-changes 2
skill finalizing-pr 1
skill implementing-feature 1
skill updating-internal-docs 1
subagent Explore 1
subagent fixing-pr 3
subagent general-purpose 6
subagent reviewing-local-changes 1
subagent simplifying-local-changes 1

Enables using /app/static/ URLs for st.image, st.audio, st.video,
st.chat_message(avatar), st.set_page_config(page_icon), and st.logo.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copilot AI review requested due to automatic review settings March 10, 2026 22:02
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Mar 10, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Mar 10, 2026

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

Status Scan Engine 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 Mar 10, 2026

✅ PR preview is ready!

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

@lukasmasuch lukasmasuch changed the title [feature] Add support for relative static URLs in media elements [feature] Add support for relative static file serving URLs in media elements Mar 10, 2026
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 end-to-end support for using Streamlit’s static file serving endpoint (/app/static/...) as a relative URL input to multiple “media” surfaces, so users don’t need to construct absolute URLs or pass local file paths.

Changes:

  • Backend: introduce url_util.is_relative_static_url() and use it to bypass MediaFileManager for /app/static/... inputs in image_to_url, marshall_audio, and marshall_video.
  • Frontend: update DefaultStreamlitEndpoints.buildMediaURL() to construct a full server URL when the input starts with /app/static/.
  • Tests: add Python unit coverage, a frontend unit test for URL building, and a new Playwright E2E test app + suite.

Reviewed changes

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

Show a summary per file
File Description
lib/streamlit/url_util.py Adds a helper + constant for detecting /app/static/ relative URLs.
lib/streamlit/elements/lib/image_utils.py Passes /app/static/... through unchanged in image_to_url().
lib/streamlit/elements/media.py Treats /app/static/... as URL input for audio/video marshalling.
frontend/connection/src/DefaultStreamlitEndpoints.ts Builds absolute URLs for /app/static/... in buildMediaURL().
lib/tests/streamlit/url_util_test.py Unit tests for is_relative_static_url().
lib/tests/streamlit/elements/image_test.py Unit test ensuring image_to_url() passes static URLs through.
lib/tests/streamlit/elements/media_test.py Unit tests for audio/video accepting static URLs without MFM.
frontend/connection/src/DefaultStreamlitEndpoints.test.ts Adds coverage for /app/static/... URL building.
e2e_playwright/st_static_media_urls.py New E2E app exercising static URLs across elements.
e2e_playwright/st_static_media_urls_test.py New E2E tests validating rendered elements reference /app/static/....

lukasmasuch and others added 6 commits March 10, 2026 23:11
Move relative static URL tests from separate files into the existing
config_static_serving test suite for better organization.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Ensures that local files at /app/static/ paths are still loaded via
MediaFileManager when static serving is disabled, matching the behavior
of image_to_url().

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Document support for static file serving URLs in the image, audio,
and video parameter descriptions. Other commands (logo, chat_message)
reference st.image for supported types.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Reduces browser loads from 10 to 5 by combining all media element
static URL tests into one aggregated scenario test.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Clarify url_util.py constant comment about trailing slash difference
- Handle /app/static/ URLs in static connection mode (staticConfigUrl)
- Add test for static connection mode in DefaultStreamlitEndpoints
- Use expect().to_have_attribute() instead of get_attribute + assert in E2E tests
- Add st.set_page_config(page_icon) test with /app/static/ URL

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
lukasmasuch and others added 2 commits March 11, 2026 19:53
Clarify the static URL pattern in image, audio, and video docstrings.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Align naming with server.py's STATIC_SERVING_ENDPOINT constant.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds support for relative static file serving URLs (/app/static/) in Streamlit media elements (st.image, st.audio, st.video, st.chat_message, st.set_page_config, and st.logo). Previously, users could only reference static files via Markdown's ./app/static/ syntax or by providing absolute URLs / local file paths. Now these elements accept /app/static/ paths directly.

Key changes:

  • Backend: Adds is_relative_static_url() helper in url_util.py with a private _STATIC_SERVING_ENDPOINT constant. Updates image_to_url(), marshall_audio(), and marshall_video() to pass through /app/static/ URLs without loading them into the MediaFileManager.
  • Frontend: Updates buildMediaURL() in DefaultStreamlitEndpoints.ts to construct proper server URLs for /app/static/ paths (both normal and static-connection modes).
  • Docs: Adds documentation for the new URL pattern in st.image, st.audio, and st.video docstrings.
  • Tests: Comprehensive unit tests (Python and TypeScript) and E2E tests covering all supported elements.

Reviewer Consensus

All three reviewers (gemini-3.1-pro, gpt-5.3-codex-high, opus-4.6-thinking) unanimously approved this PR. There were no critical or blocking issues identified by any reviewer. The reviewers were in strong agreement across all major evaluation dimensions.

Code Quality

Consensus: High quality, no issues. All three reviewers agreed the code is clean, well-structured, and follows existing codebase patterns.

  • The is_relative_static_url() function is simple, well-documented, and centralizes the detection logic.
  • The _STATIC_SERVING_ENDPOINT constant uses proper private naming convention and Final typing.
  • The trailing-slash requirement (/app/static/ vs /app/static) is explicitly documented to avoid matching paths like /app/staticfile.png.
  • The os.path.isfile() check added to marshall_video() and marshall_audio() brings consistency with the pre-existing behavior in image_to_url().
  • Frontend changes extend the existing buildMediaURL() pattern rather than duplicating logic.

Test Coverage

Consensus: Thorough and comprehensive. All reviewers agreed that test coverage is strong across all layers.

  • Python unit tests: url_util_test.py (10 parameterized cases), image_test.py, and media_test.py cover the backend logic with good boundary coverage.
  • Frontend unit tests: DefaultStreamlitEndpoints.test.ts covers buildMediaURL with /app/static/ paths in both normal and static-connection modes.
  • E2E tests: config_static_serving_test.py covers all six supported elements in a single browser load, following E2E best practices.

Minor improvement suggestion (opus-4.6-thinking): The E2E test could benefit from an explicit negative assertion (e.g., verifying no stException elements appear), though the success message check partially covers this. Non-blocking.

Backwards Compatibility

Consensus: Fully backwards compatible. All reviewers confirmed no breaking changes:

  • Existing URL handling (HTTP/HTTPS/data URLs, local file paths) is unaffected.
  • The /app/static/ pattern was previously unsupported and would have raised errors, so this is purely additive.
  • No protobuf changes are required; URLs pass through existing string fields.
  • Frontend changes only apply to paths matching the /app/static/ prefix.

Security & Risk

Consensus: Low risk, no concerns. All reviewers agreed there are no significant security issues:

  • No new server endpoints are introduced; existing AppStaticFileHandler (with path traversal protections, containment checks, size limits, and nosniff headers) handles serving.
  • No new auth/session/cookie logic, dynamic code execution, or external dependencies.
  • CORS settings (Access-Control-Allow-Origin: *) on the static endpoint are unchanged.
  • The feature requires server.enableStaticServing = true; if disabled, /app/static/ URLs simply return 404s.

External Test Recommendation

Consensus: Yes, external testing recommended. All three reviewers recommended external tests.

  • Agreed category: Static and component asset serving (category 5).
  • Additional categories (gpt-5.3-codex-high): Routing and URL behavior (category 1), Cross-origin behavior (category 7).
  • Suggested focus areas (union of all reviewers):
    • Verify /app/static/ URLs resolve correctly behind reverse proxies with base paths.
    • Verify /app/static/ URLs work in iframe-embedded deployments (cross-origin static asset loading).
    • Verify S3 URL construction for /app/static/ paths in static-connection mode.
    • Validate staticConfigUrl/staticAppId behavior in externally hosted environments.
  • Confidence: High (gemini, gpt) / Medium (opus — noting S3 path construction is unit-tested but not validated against a real deployment).

Accessibility

Consensus: No concerns. All reviewers agreed there are no accessibility implications. Changes are backend URL routing and frontend URL construction logic with no new UI elements or visual changes.

Recommendations

All recommendations below are non-blocking and for future consideration:

  1. Add @pytest.mark.external_test coverage (gpt-5.3-codex-high): Add an external E2E scenario for /app/static/ media/favicon/logo rendering behind an external base path and iframe host.
  2. Document server.enableStaticServing prerequisite inline (opus-4.6-thinking): The docstring updates link to the static file serving docs, but noting the config requirement directly in the parameter description could help users avoid a navigation hop.
  3. Add negative E2E assertion (opus-4.6-thinking): Explicitly verify no stException elements appear in the E2E test for added robustness.

Verdict

APPROVED: All three reviewers unanimously approved. The implementation is clean, well-tested, and fully backwards compatible. No security, accessibility, or breaking-change concerns were identified. The feature follows existing codebase patterns and has comprehensive test coverage across Python unit tests, frontend unit tests, and E2E tests.


This is a consolidated AI review by opus-4.6-thinking, combining reviews from gemini-3.1-pro, gpt-5.3-codex-high, and opus-4.6-thinking.


📋 Review by `gemini-3.1-pro`

Summary

This PR introduces support for relative static URLs (/app/static/) in media elements (st.image, st.audio, st.video, st.chat_message, st.logo, and st.set_page_config). It achieves this by adding a helper function is_relative_static_url and updating the backend marshalling functions (image_to_url, marshall_audio, marshall_video) to pass these URLs through. The frontend is also updated to correctly build the full URLs for these static assets using buildMediaURL.

Code Quality

The code changes are clean, well-structured, and follow the established patterns in the codebase. The addition of is_relative_static_url in url_util.py centralizes the logic for detecting static URLs. The use of Final for _STATIC_SERVING_ENDPOINT is good practice. The frontend changes in DefaultStreamlitEndpoints.ts correctly mirror the existing logic for /media endpoints.

Test Coverage

The changes are thoroughly tested:

  • Unit Tests: New tests were added to lib/tests/streamlit/url_util_test.py, image_test.py, and media_test.py to verify the backend logic. The frontend logic is covered by new test cases in DefaultStreamlitEndpoints.test.ts.
  • E2E Tests: e2e_playwright/config_static_serving_test.py has been expanded to test all supported media elements with static URLs, ensuring end-to-end functionality.

Backwards Compatibility

This is a purely additive feature and does not introduce any breaking changes. Existing behavior for absolute URLs, local file paths, and /media URLs remains unaffected.

Security & Risk

No significant security concerns were identified. The backend static file serving is handled by Tornado's StaticFileHandler, which inherently protects against path traversal attacks. The changes merely allow the frontend to request these static assets through the existing, secure mechanism.

External test recommendation

  • Recommend external_test: Yes
  • Triggered categories: 5. Static and component asset serving
  • Evidence:
    • frontend/connection/src/DefaultStreamlitEndpoints.ts: Modifies buildMediaURL to handle STATIC_SERVING_ENDPOINT (/app/static/) and build static URLs if staticConfigUrl is set.
  • Suggested external_test focus areas:
    • External host iframe embedding + static asset loading (especially with staticConfigUrl set or in static connection mode).
  • Confidence: High
  • Assumptions and gaps: None

Accessibility

N/A - No UI changes were made that affect accessibility.

Recommendations

None. The implementation is solid and ready to be merged.

Verdict

APPROVED: The changes are well-implemented, thoroughly tested, and introduce a useful feature without regressions.


This is an automated AI review by gemini-3.1-pro.

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

Summary

This PR adds support for relative static URLs (/app/static/...) across media/image paths so users can reference files served by Streamlit static serving without converting them to absolute URLs or local file paths.
It updates backend URL handling (st.image, st.audio, st.video, plus page icon/logo/avatar call paths), frontend media URL resolution, and test coverage across Python unit tests, frontend unit tests, and Playwright e2e.

Code Quality

The implementation is coherent and follows existing Streamlit patterns:

  • Backend logic is centralized via url_util.is_relative_static_url() and reused in media/image marshalling (lib/streamlit/url_util.py, lib/streamlit/elements/lib/image_utils.py, lib/streamlit/elements/media.py).
  • Frontend endpoint handling extends existing buildMediaURL() behavior instead of duplicating path-assembly logic (frontend/connection/src/DefaultStreamlitEndpoints.ts).
  • Public API docs for st.image, st.audio, and st.video were updated consistently to document /app/static/ support (lib/streamlit/elements/image.py, lib/streamlit/elements/media.py).

No blocking code-quality issues found.

Test Coverage

Coverage is strong and layered:

  • Python unit tests:
    • URL helper validation for valid/invalid /app/static/ prefixes (lib/tests/streamlit/url_util_test.py).
    • image_to_url passthrough behavior for static URLs (lib/tests/streamlit/elements/image_test.py).
    • Audio/video URL passthrough behavior for static URLs (lib/tests/streamlit/elements/media_test.py).
  • Frontend unit tests:
    • buildMediaURL() handling for /app/static/ in normal and static-connection modes (frontend/connection/src/DefaultStreamlitEndpoints.test.ts).
  • E2E tests:
    • End-to-end verification for st.set_page_config(page_icon), st.image, st.audio, st.video, st.chat_message(avatar), and st.logo using /app/static/ URLs (e2e_playwright/config_static_serving.py, e2e_playwright/config_static_serving_test.py).

Given the scope, tests are adequate for merge.

Backwards Compatibility

No breaking API changes identified:

  • Existing absolute URL handling (http, https, data) remains intact.
  • Existing local file-path behavior is preserved by checking filesystem paths before treating strings as URLs in media/image handling.
  • The added behavior is additive (newly recognized /app/static/ input form).

Security & Risk

No new high-risk security behavior identified in this diff.

  • The PR does not introduce new server routes, auth/cookie/session logic, or dynamic code execution.
  • /app/static/ requests continue to flow through existing static-file serving handlers and existing path security controls.
  • No new dependency additions or external service integrations are introduced.

Primary residual risk is regression in externally hosted/proxied/embedded deployments where URL/base-path behavior can differ from local runs.

External test recommendation

  • Recommend external_test: Yes
  • Triggered categories: 1. Routing and URL behavior; 5. Static and component asset serving; 7. Cross-origin behavior and external networking
  • Evidence:
    • lib/streamlit/url_util.py: Adds new URL-classification path for /app/static/.
    • lib/streamlit/elements/lib/image_utils.py: Passes /app/static/ through as direct URL for image rendering.
    • lib/streamlit/elements/media.py: Passes /app/static/ through for audio/video URL resolution.
    • frontend/connection/src/DefaultStreamlitEndpoints.ts: Extends media URL rewriting to include /app/static/, including static-connection mode.
    • e2e_playwright/config_static_serving_test.py: Validates local e2e behavior but not externally hosted/embedded mode.
  • Suggested external_test focus areas:
    • Validate /app/static/ media, logo, avatar, and favicon rendering when app is served behind reverse proxies and non-root base paths.
    • Validate the same flows in embedded iframe-hosted deployments where origin and path rewriting differ.
    • Validate static-connection (staticConfigUrl/staticAppId) behavior for /app/static/ resources in externally hosted environments.
  • Confidence: High
  • Assumptions and gaps: Assessment is based on code review and existing test diffs only; no runtime execution was performed in external hosting/iframe environments.

Accessibility

No accessibility regressions identified from the frontend code changes in this PR:

  • Changes are URL-construction and transport-focused; they do not alter interactive semantics, keyboard behavior, ARIA labeling, or focus management.
  • Existing e2e selectors/assertions continue to target rendered media elements without introducing non-semantic interaction patterns.

Recommendations

  1. Consider adding one @pytest.mark.external_test scenario focused on /app/static/ media and favicon/logo rendering behind an external base path + iframe host to mitigate deployment-specific URL regressions.

Verdict

APPROVED: The implementation is well-scoped, test coverage is comprehensive for local behavior, and no blocking compatibility or security issues were 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 support for relative static file serving URLs (/app/static/) in Streamlit media elements. Previously, users could only reference static files via Markdown's ./app/static/ syntax or by providing absolute URLs / local file paths. Now, st.image, st.audio, st.video, st.chat_message(avatar), st.set_page_config(page_icon), and st.logo all accept /app/static/ paths directly.

Key changes:

  • Backend: Adds is_relative_static_url() helper in url_util.py, updates image_to_url(), marshall_audio(), and marshall_video() to pass through /app/static/ URLs without loading them into the MediaFileManager.
  • Frontend: Updates buildMediaURL() in DefaultStreamlitEndpoints.ts to construct proper server URLs for /app/static/ paths (both normal and static-connection modes).
  • Docs: Adds documentation for the new URL pattern in st.image, st.audio, and st.video docstrings.
  • Tests: Comprehensive unit tests (Python and TypeScript) and E2E tests covering all supported elements.

Code Quality

The code is clean, well-structured, and follows existing patterns in the codebase.

Strengths:

  • The is_relative_static_url() function is simple, well-documented, and uses a module-level _STATIC_SERVING_ENDPOINT constant (correctly prefixed with _ for private use).
  • The trailing-slash requirement in _STATIC_SERVING_ENDPOINT is explicitly documented in the comment, explaining why /app/static/ (with slash) is used instead of /app/static to avoid matching paths like /app/staticfile.png.
  • The os.path.isfile() check added to marshall_video() and marshall_audio() makes them consistent with the pre-existing behavior in image_to_url(), which already prioritized local files over URLs.
  • The frontend constant APP_STATIC_ENDPOINT is placed alongside other endpoint constants and follows the established pattern.
  • The buildMediaURL restructuring is logical—static connection mode checks are grouped together.

Minor observations:

  • In media.py, the new import os at line 18 is correctly placed at the module level alongside other standard library imports. Note that os was not previously imported in this module; the original code relied on url_util.is_url() alone without needing os.path.isfile(). The addition is justified since the PR adds the file-existence check for consistency with image_to_url().

Test Coverage

Test coverage is thorough and well-structured across all layers.

Python unit tests:

  • url_util_test.py: Tests is_relative_static_url() with 10 parameterized cases covering valid paths (simple, subdirectory, trailing slash, different extensions) and invalid paths (wrong prefix, missing leading slash, different endpoint, full URL, missing trailing slash, empty string). Good boundary coverage.
  • image_test.py: Tests image_to_url() with relative static URLs, verifying they pass through unchanged. Uses @pytest.mark.parametrize (modern style) with descriptive IDs.
  • media_test.py: Integrates static URL test cases into the existing parameterized test_add_bytes_and_filenames_to_mediafilemanager test, covering both audio and video. The is_url=True flag correctly asserts these URLs bypass the MediaFileManager.

Frontend unit tests:

  • DefaultStreamlitEndpoints.test.ts: Tests buildMediaURL with /app/static/ paths in both normal server mode (2 parameterized cases) and static-connection mode. Properly verifies URL construction.

E2E tests:

  • config_static_serving_test.py: A single aggregated test_static_urls_in_media_elements test covers all six supported elements in one browser load, following E2E best practices. Uses expect with regex patterns for robust attribute matching. The success message assertion at the end serves as a holistic "no errors" check.
  • Static test assets (cat-purr.mp3, sintel-short.webm, streamlit-mark.png, streamlit-logo-small.png, streamlit-logo.png) are all present in e2e_playwright/static/, and static serving is enabled for all E2E tests via conftest.py.

Potential improvement: The E2E test doesn't include a negative assertion (e.g., verifying no error alerts appear beyond the success message). However, the presence of the success message at the bottom of the script implicitly confirms no errors interrupted execution, which is a reasonable approach.

Backwards Compatibility

This change is fully backwards compatible:

  • No breaking changes: Existing URL handling (HTTP/HTTPS/data URLs, local file paths) is unaffected. The /app/static/ pattern was previously unsupported and would have raised errors (treated as a non-existent local file path), so this is purely additive.
  • Consistent os.path.isfile precedence: The addition of os.path.isfile() checks in marshall_video() and marshall_audio() ensures local files are prioritized over URL patterns, matching the existing behavior of image_to_url(). In the extremely unlikely case that a user has a local file literally named https://example.com/file.mp4, this adds consistency rather than introducing a regression.
  • No protobuf changes: The URL is simply passed as a string through the existing url field in the proto messages.
  • Frontend is additive: The buildMediaURL function's new /app/static/ handling only applies to paths matching that exact prefix. All other URLs continue through their existing code paths.

Security & Risk

Security assessment: Low risk.

  • Path traversal: No new path traversal risk. The backend passes the /app/static/ URL through to the frontend as-is. The actual file serving is handled by AppStaticFileHandler, which already has comprehensive protections: is_unsafe_path_pattern() validation, os.path.commonpath containment check, directory traversal prevention, file size limits, and X-Content-Type-Options: nosniff headers.
  • No new endpoints: No new server endpoints are introduced. The existing /app/static/ endpoint and its AppStaticFileHandler are unchanged.
  • No injection risk: The /app/static/ URLs are used in src attributes for media elements, not rendered as raw HTML. No new eval, exec, or dynamic code execution is introduced.
  • Static serving dependency: The feature works only when server.enableStaticServing is enabled. If disabled, /app/static/ URLs will simply result in 404 responses from the server. The PR doesn't validate this config on the backend side, which is consistent with how other URL types are handled (the backend doesn't validate that HTTP URLs are reachable either).
  • No new dependencies: No external dependencies are added.
  • CORS: The AppStaticFileHandler already sets Access-Control-Allow-Origin: * for the static endpoint, which is unchanged.

External test recommendation

  • Recommend external_test: Yes
  • Triggered categories: 5 (Static and component asset serving)
  • Evidence:
    • frontend/connection/src/DefaultStreamlitEndpoints.ts: Modified buildMediaURL() to handle /app/static/ paths, including the static-connection (S3) code path. This changes how asset URLs are constructed when the app is served externally.
    • lib/streamlit/elements/media.py: Backend changes affect how media URLs flow to the frontend, which could behave differently behind a proxy or with server.baseUrlPath configured.
  • Suggested external_test focus areas:
    • Verify /app/static/ URLs resolve correctly when the app is served behind a reverse proxy with a base path.
    • Verify /app/static/ URLs work when the app is embedded in an iframe (cross-origin static asset loading).
    • Verify the S3 URL construction for /app/static/ paths in static-connection mode (if applicable to the external hosting environment).
  • Confidence: Medium
  • Assumptions and gaps: The static-connection mode S3 URL construction (buildStaticUrl) is tested in unit tests but not verified against a real S3-backed deployment. The interaction with server.baseUrlPath is handled by buildHttpUri which is the same utility used for /media URLs, so it should work consistently, but external validation would add confidence.

Accessibility

No accessibility concerns. The changes are backend URL routing and frontend URL construction logic. No new UI elements, interactive controls, or visual changes are introduced. The media elements (<img>, <audio>, <video>) continue to use the same HTML structure with existing alt attributes, controls, and ARIA attributes.

Recommendations

  1. Consider adding a note about server.enableStaticServing: The docstring updates for st.image, st.audio, and st.video mention /app/static/ URLs and link to the static file serving docs, which is good. However, it might be helpful to note that this feature requires server.enableStaticServing = true in the parameter description itself, so users don't have to navigate to a separate page to learn this prerequisite. This is a minor documentation suggestion and not a blocker.

  2. Consider a negative E2E assertion: The E2E test test_static_urls_in_media_elements could benefit from one explicit negative assertion, such as verifying no stException elements appear on the page. The current success message check partially covers this, but an explicit check would be more robust. This is a minor test quality suggestion.

Verdict

APPROVED: Clean, well-tested feature addition that follows existing patterns and introduces no backwards compatibility or security concerns. The implementation is consistent across backend and frontend, with comprehensive test coverage at all levels.


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

Adds E2E coverage for static URL media elements (image, audio, video,
avatar, logo) when the app is externally hosted or iframe-embedded.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Comment on lines +141 to +146
it.each([
["/app/static/my_image.png", "my_image.png"],
["/app/static/images/subdir/file.mp4", "file with subdirectories"],
])(
"builds URL correctly for /app/static/ paths (%s)",
(inputPath, _description) => {
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.

nit: It looks like the 2nd param in the it.each each setup _description is never used. Either use it in the test or remove it for brevity.

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.

Removed 👍

Address PR review nit: simplify it.each to use plain string values
instead of tuples with unused description parameter.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch enabled auto-merge (squash) March 12, 2026 23:19
@lukasmasuch lukasmasuch merged commit aa09122 into develop Mar 12, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/relative-static-urls branch March 12, 2026 23:38
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.

static images served by streamlit should work on all image compatible features

3 participants