Skip to content

getURL returns URL object instead of string when basePath starts with 'http' #83

@masonjames

Description

@masonjames

Bug Description

getURL() in packages/better-fetch/src/url.ts has an inconsistent return type. When basePath does not start with "http", it returns a string. When basePath does start with "http", it returns a URL object. Downstream code expects a string and calls .indexOf() on the result, which throws:

TypeError: _url.indexOf is not a function

Reproduction

import { createFetch } from "@better-fetch/fetch";

const $fetch = createFetch({
  baseURL: "https://example.com/api",
});

// This triggers the error because getURL returns a URL object
// and internal code calls .indexOf() on it
await $fetch("/some-path");

This also affects better-auth when createAuthClient is given an absolute baseURL:

import { createAuthClient } from "better-auth/react";

// Any absolute URL triggers the bug
const authClient = createAuthClient({
  baseURL: "https://portal.example.com",
});

// Every operation that goes through $fetch will throw _url.indexOf errors:
// - Session auto-fetch on component mount
// - signIn.social()
// - signUp.email()
// - signOut()

Root Cause

In packages/better-fetch/src/url.ts, the getURL function:

// String branch (correct) — when basePath is relative
if (!basePath.startsWith("http")) {
    return `${basePath}${path}${queryParamString}`;  // returns string ✓
}

// URL object branch (incorrect) — when basePath is absolute
const _url = new URL(`${path}${queryParamString}`, basePath);
return _url;  // returns URL object ✗

Suggested Fix

Return _url.toString() or _url.href instead of the raw URL object:

 const _url = new URL(`${path}${queryParamString}`, basePath);
-return _url;
+return _url.toString();

Alternatively, the function's return type could be string | URL with all callers updated to handle both, but .toString() is simpler and maintains backward compatibility.

Impact

This bug affects anyone using better-auth's client with an absolute baseURL (the default when using window.location.origin or any full URL). The errors cascade because better-auth/react's session atoms auto-fetch on mount, focus, and visibility changes — creating a torrent of errors on every page load.

Current Workaround

Setting baseURL to an empty string "" or a relative path forces the string-concatenation branch:

const authClient = createAuthClient({
  baseURL: "",  // relative — browser resolves against current origin
});

Versions Affected

Confirmed present in: v1.1.18, v1.1.19, v1.1.21 (latest), and main branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions