Repository: marimo-team/marimo
Pull request: fix: query parameters in ws
This fixes a regression that caused `mo.query_params()` to stop working. We need to send all query parameters to the backend, not just known ones, as part of the websocket URL.

Tested by playing with the query parameters smoke test (`_marimo/smoke_tests`).

Fixes #5376. 

Problem statement:
Marimo 0.14 breaks query params (at least locally)
### Describe the bug

import marimo as mo

query_params = mo.query_params()

query_params


The above 3 lines of code no longer show query params when you set them locally in 0.14 and it used to work in 0.13.15 and all versions before

### Will you submit a PR?

- [ ] Yes

### Environment

0.14

### Code to reproduce

import marimo as mo

query_params = mo.query_params()

query_params

Hints from issue discussion:
Thanks for filing, we'll look into this.

Commit message: [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Patch to review:
diff --git a/frontend/src/core/runtime/__tests__/runtime.test.ts b/frontend/src/core/runtime/__tests__/runtime.test.ts
index 9462c3e7686..b4ba97ce668 100644
--- a/frontend/src/core/runtime/__tests__/runtime.test.ts
+++ b/frontend/src/core/runtime/__tests__/runtime.test.ts
@@ -271,6 +271,46 @@ describe("RuntimeManager", () => {
       expect(aiUrl.pathname).toBe("/nested/path/api/ai/completion");
     });
 
+    it("should preserve all window-level query parameters in URLs", () => {
+      // Mock window.location.search with custom query parameters
+      const originalLocation = window.location;
+      Object.defineProperty(window, "location", {
+        value: {
+          ...originalLocation,
+          search: "?custom_param=value123&user_token=abc&theme=dark",
+        },
+        writable: true,
+      });
+
+      const runtime = new RuntimeManager({
+        url: "https://example.com/path?base_param=existing",
+      });
+
+      const wsUrl = runtime.getWsURL("test" as SessionId);
+      const httpUrl = runtime.formatHttpURL("api/test");
+
+      // Should preserve base URL query params
+      expect(wsUrl.searchParams.get("base_param")).toBe("existing");
+      expect(httpUrl.searchParams.get("base_param")).toBe("existing");
+
+      // Should preserve all window-level query params (including custom ones)
+      expect(wsUrl.searchParams.get("custom_param")).toBe("value123");
+      expect(wsUrl.searchParams.get("user_token")).toBe("abc");
+      expect(wsUrl.searchParams.get("theme")).toBe("dark");
+      expect(httpUrl.searchParams.get("custom_param")).toBe("value123");
+      expect(httpUrl.searchParams.get("user_token")).toBe("abc");
+      expect(httpUrl.searchParams.get("theme")).toBe("dark");
+
+      // Should also include session_id for WebSocket URLs
+      expect(wsUrl.searchParams.get("session_id")).toBe("test");
+
+      // Restore original location
+      Object.defineProperty(window, "location", {
+        value: originalLocation,
+        writable: true,
+      });
+    });
+
     it("should handle URLs with query parameters and fragments", () => {
       const runtime = new RuntimeManager({
         url: "https://example.com/path?existing=param#fragment",
diff --git a/frontend/src/core/runtime/runtime.ts b/frontend/src/core/runtime/runtime.ts
index 564df8b9c94..7d3dee1801d 100644
--- a/frontend/src/core/runtime/runtime.ts
+++ b/frontend/src/core/runtime/runtime.ts
@@ -52,14 +52,10 @@ export class RuntimeManager {
       }
     }
 
-    // Move over window level parameters to the WebSocket URL
-    // if they are "known" query params.
-    for (const lookup in KnownQueryParams) {
-      const key = KnownQueryParams[lookup as keyof typeof KnownQueryParams];
-      const value = currentParams.get(key);
-      if (value !== null) {
-        baseUrl.searchParams.set(key, value);
-      }
+    // Move over all window-level parameters to WebSocket URL.
+    // This is needed for user-defined query parameters.
+    for (const [key, value] of currentParams.entries()) {
+      baseUrl.searchParams.set(key, value);
     }
 
     const cleanPath = baseUrl.pathname.replace(/\/$/, "");


Code review comments:
diff_hunk: @@ -52,14 +52,10 @@ export class RuntimeManager {
       }
     }
 
-    // Move over window level parameters to the WebSocket URL
path: frontend/src/core/runtime/runtime.ts
text: @author:
@user1 Was there a reason for stripping unknown query parameters? This breaks `mo.query_params()`.

Was this code too aggressively refactored, i.e. is there a codepath that relies on query parameter stripping?

@user1:
A little too conservative - with the refactor query params were not passed down at all. Thanks for the general fix