From d7d408ab6c8ccdcfb990231c53a9d0621bb75ce0 Mon Sep 17 00:00:00 2001 From: Shaurya Singh Date: Thu, 4 Jun 2026 16:30:49 -0700 Subject: [PATCH] fix(utils.url): delete existing key before appending array values in withQuery When withQuery merges a query object into a URL that already has an existing query string, scalar values are correctly replaced via searchParams.set(). Array values, however, were appended without first deleting the existing key for that name, so the old value would persist alongside the new ones. For example: withQuery('https://example.org/path?tag=old', { tag: ['a', 'b'] }) produced: ...?tag=old&tag=a&tag=b (wrong) now gives: ...?tag=a&tag=b (correct) Add searchParams.delete(key) before the append loop to match the replace semantics that searchParams.set() already provides for scalar values. --- src/utils.url.ts | 1 + test/index.test.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/utils.url.ts b/src/utils.url.ts index 5a725353..610ed852 100644 --- a/src/utils.url.ts +++ b/src/utils.url.ts @@ -90,6 +90,7 @@ export function withQuery(input: string, query?: QueryObject): string { if (value === undefined) { searchParams.delete(key); } else if (Array.isArray(value)) { + searchParams.delete(key); for (const item of value) { searchParams.append(key, normalizeQueryValue(item)); } diff --git a/test/index.test.ts b/test/index.test.ts index 5ac20b07..e8b86e3a 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -344,6 +344,20 @@ describe("ofetch", () => { }); }); + it("replaces existing array query params instead of accumulating", async () => { + // When the URL already carries a value for a key and the query option + // supplies an array for that same key, the existing value should be + // replaced, not accumulated alongside the new ones. The /url/** endpoint + // echoes the raw path+search so we can inspect all repeated values. + const result = await $fetch(getURL("url/check?tag=old&other=1"), { + query: { tag: ["a", "b"] }, + }); + // "tag=old" must not appear; only "tag=a" and "tag=b" should be present + expect(result).toContain("tag=a"); + expect(result).toContain("tag=b"); + expect(result).not.toContain("tag=old"); + }); + it("deep merges defaultOptions", async () => { const _customFetch = $fetch.create({ query: {