{"record":{"id":"cf41febbc34bb366","repo":"denoland/deno","slug":"must-be-a-file-url","errorCode":null,"errorMessage":"Must be a file URL","messagePattern":"Must be a file URL","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/web/00_infra.js","lineNumber":376,"sourceCode":"// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.\n/**\n * @param {URL} url\n * @returns {string}\n */\nfunction pathFromURLPosix(url) {\n  if (url.hostname !== \"\") {\n    throw new TypeError(\"Host must be empty\");\n  }\n\n  return decodeURIComponent(\n    StringPrototypeReplace(url.pathname, PERCENT_RE, \"%25\"),\n  );\n}\n\nfunction pathFromURL(pathOrUrl) {\n  if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) {\n    if (pathOrUrl.protocol != \"file:\") {\n      throw new TypeError(\"Must be a file URL\");\n    }\n\n    return core.build.os == \"windows\"\n      ? pathFromURLWin32(pathOrUrl)\n      : pathFromURLPosix(pathOrUrl);\n  }\n  return pathOrUrl;\n}\n\n// NOTE(bartlomieju): this is exposed on `internals` so we can test\n// it in unit tests\ninternals.pathFromURL = pathFromURL;\n\n// deno-lint-ignore deno-internal/prefer-primordials\nconst SymbolMetadata = Symbol.metadata ?? Symbol(\"Symbol.metadata\");\n\nreturn {\n  ASCII_ALPHA,","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/web/00_infra.js#L358-L394","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst text = await Deno.readTextFile(new URL('https://example.com/data.json'));\n\n// after\nconst res = await fetch('https://example.com/data.json');\nconst text = await res.text();","handlingStrategy":"validation","validationCode":"if (\n  typeof target !== \"string\" &&\n  !(target instanceof URL && target.protocol === \"file:\")\n) {\n  throw new TypeError(`expected a path or file: URL, got ${String(target)}`);\n}","typeGuard":"function isPathOrFileUrl(\n  v: string | URL,\n): v is string | URL {\n  return typeof v === \"string\" || v.protocol === \"file:\";\n}","tryCatchPattern":"try {\n  data = Deno.readTextFile(target);\n} catch (e) {\n  if (e instanceof TypeError && e.message === \"Must be a file URL\") {\n    // remote or blob URL: fetch instead\n    data = (await fetch(target)).text();\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["file-url","path","url-scheme","fs","validation"],"backgroundTag":"file-url-conversion","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}