denoland/deno · error · TypeError

Must be a file URL

Error message

Must be a file URL

What it means

pathFromURL (ext/web/00_infra.js:376) accepts either a plain path string or a URL object; if given a URL, its protocol must be file:. Any other scheme (http:, https:, data:, blob:) has no filesystem path representation, so Deno throws TypeError('Must be a file URL'). Plain strings fall through unchanged and never hit this check.

Source

Thrown at ext/web/00_infra.js:376

// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
/**
 * @param {URL} url
 * @returns {string}
 */
function pathFromURLPosix(url) {
  if (url.hostname !== "") {
    throw new TypeError("Host must be empty");
  }

  return decodeURIComponent(
    StringPrototypeReplace(url.pathname, PERCENT_RE, "%25"),
  );
}

function pathFromURL(pathOrUrl) {
  if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) {
    if (pathOrUrl.protocol != "file:") {
      throw new TypeError("Must be a file URL");
    }

    return core.build.os == "windows"
      ? pathFromURLWin32(pathOrUrl)
      : pathFromURLPosix(pathOrUrl);
  }
  return pathOrUrl;
}

// NOTE(bartlomieju): this is exposed on `internals` so we can test
// it in unit tests
internals.pathFromURL = pathFromURL;

// deno-lint-ignore deno-internal/prefer-primordials
const SymbolMetadata = Symbol.metadata ?? Symbol("Symbol.metadata");

return {
  ASCII_ALPHA,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. For remote content use fetch(url) and read the response body instead of fs APIs
  2. For local modules pass a file: URL derived from import.meta.url, e.g. new URL('./data.json', import.meta.url)
  3. Or pass a plain filesystem path string, which pathFromURL passes through without validation

Example fix

// before
const text = await Deno.readTextFile(new URL('https://example.com/data.json'));

// after
const res = await fetch('https://example.com/data.json');
const text = await res.text();
Defensive patterns

Strategy: validation

Validate before calling

if (
  typeof target !== "string" &&
  !(target instanceof URL && target.protocol === "file:")
) {
  throw new TypeError(`expected a path or file: URL, got ${String(target)}`);
}

Type guard

function isPathOrFileUrl(
  v: string | URL,
): v is string | URL {
  return typeof v === "string" || v.protocol === "file:";
}

Try / catch

try {
  data = Deno.readTextFile(target);
} catch (e) {
  if (e instanceof TypeError && e.message === "Must be a file URL") {
    // remote or blob URL: fetch instead
    data = (await fetch(target)).text();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Deno.readTextFile(new URL('https://example.com/data.json')); passing import.meta.url of a remote (http/https) module to an fs API; passing a blob: or data: URL where a path or file URL is expected.

Common situations: Assuming import.meta.url always works with fs APIs (only true for locally loaded file: modules); a variable that is sometimes a remote URL and sometimes a path; fetch-then-read code passing the wrong variable to readFileSync.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/cf41febbc34bb366. Report an issue: GitHub.