#4252·dub

fix: getFileExtension returns raw URL path instead of null for URLs without file extensions

Author: cax6505Created Jul 31, 2026Updated Aug 19, 2026

Description

In @dub/utils, the getFileExtension(url) helper function incorrectly returns the full URL pathname (e.g. "/AVATAR" or "/API/V1/USERS") when given a URL that does not contain a file extension.

Reproduction

typescript
import { getFileExtension } from "@dub/utils";

console.log(getFileExtension("https://dub.co/avatar")); 
// Output: "/AVATAR" (Expected: null)

console.log(getFileExtension("https://dub.co/api/v1/users")); 
// Output: "/API/V1/USERS" (Expected: null)

Cause

In packages/utils/src/functions/urls.ts:

typescript
export const getFileExtension = (url: string): string | null => {
  try {
    const pathname = new URL(url).pathname;
    const extension = pathname.split(".").pop(); // When pathname has no ".", returns entire pathname
    return extension ? extension.toUpperCase() : null;
  } catch { ... }
};

When pathname has no dot (.), pathname.split(".") yields ["/avatar"]. Calling .pop() returns "/avatar", which is truthy, so it returns "/AVATAR".

Impact

In ResourceCard (resources/page.tsx), brand logos with extensionless asset URLs display badge labels like "/LOGO · 24 KB" instead of "Unknown · 24 KB".

Proposed Fix

Verify that the extracted filename actually contains a dot before returning the extension:

typescript
export const getFileExtension = (url: string): string | null => {
  try {
    const pathname = new URL(url).pathname;
    const filename = pathname.split("/").pop() || "";
    if (!filename.includes(".")) return null;
    const extension = filename.split(".").pop();
    return extension && extension !== filename ? extension.toUpperCase() : null;
  } catch {
    return null;
  }
};