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
- For remote content use fetch(url) and read the response body instead of fs APIs
- For local modules pass a file: URL derived from import.meta.url, e.g. new URL('./data.json', import.meta.url)
- 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
- Use fetch() for http/https/blob content; reserve fs APIs for file: URLs and path strings
- Derive local paths via new URL('./rel', import.meta.url) and check protocol === 'file:' first
- Type parameters as string | URL and validate the scheme before fs calls
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
- Host must be empty
- 'options' requires at least one option to be true
- invalid ${name}, must not be infinity or NaN
- Invalid file path '{}'
- Invalid deep-link scheme {scheme:?}: {reason}.
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/cf41febbc34bb366.
Report an issue: GitHub.