CodeFileLoader's import() rejects absolute Windows paths (ERR_UNSUPPORTED_ESM_URL_SCHEME)
envgap__ardatan__graphql-tools-8420
01 / FAILURE SIGNATURE
As reported upstream
Error: Unable to load from file "C:/.../schema.js": Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
89e78a56cab81a92b81cd36d419f0ba28d519bf5- Manifest
packages/loaders/code-file/package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / ORIGINAL ISSUE TEXT
ardatan/graphql-tools #8420 · read the original issue
**Describe the bug**
On native Windows (not WSL), `CodeFileLoader` calls dynamic `import()` directly on a raw absolute filesystem path in two places, and both fail:
1. `esm/load-from-module.js` → `tryToLoadFromExport()`:
```js
const mod = await import(filepath);
```
This runs whenever a schema/document pointer resolves to a `.js`/`.cjs`/`.mjs` file.
2. `esm/index.js` → the loader's own `require` option:
```js
await Promise.all(asArray(options.require).map(m => import(m)));
```
Node's `import()` always resolves its specifier through the ESM loader, regardless of whether the calling code is itself CJS or ESM. On Windows, an absolute path like `C:\Users\me\project\schema.js` gets misparsed as a URL with scheme `c:`, so it throws:
```
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol 'c:'
```
This affects anything that loads a schema or documents from a JS/CJS/MJS module file on Windows via `CodeFileLoader` — e.g. `@graphql-tools/load`, `graphql-config`, or `@graphql-codegen/cli`.
**To Reproduce** Steps to reproduce the behavior:
1. On Windows, create `schema.js`:
```js
const { buildSchema } = require('graphql');
module.exports = buildSchema(`
type Query {
id: ID!
}
`);
```
2. Load it through `CodeFileLoader` via `@graphql-tools/load`, e.g. `test.mts`:
```ts
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
import { loadSchema } from '@graphql-tools/load';
import * as path from 'node:path';
const filename = path.resolve('schema.js');
const schema = await loadSchema(filename, {
loaders: [new CodeFileLoader()],
});
console.log(schema);
```
Run with `pnpm tsx test.mts` (or `npx tsx test.mts`).
3. Observe the throw:
```
Error: Unable to load from file "C:/.../schema.js": Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'
at tryToLoadFromExport (.../code-file-loader/esm/load-from-module.js:12:15)
at async CodeFileLoader.handleSinglePath (.../code-file-loader/esm/index.js:186:32)
```
The same failure occurs passing an absolute path via the loader's `require` option.
> **Gotcha while reproducing:** if `schema.js`'s SDL is written as a `/* GraphQL */`-tagged template literal, e.g.:
> ```js
> module.exports = buildSchema(/* GraphQL */ `
> type Query { id: ID! }
> `);
> ```
> then `graphql-tag-pluck`'s static source-text extraction finds and returns it without ever importing the file, which silently masks this bug (the `!options.noPluck` branch in `handleSinglePath` succeeds first). To reliably hit the `import()` path, either drop that magic comment from the schema source (as shown in step 1 above) or pass `noPluck: true` to `CodeFileLoader`.
**Expected behavior**
The schema/document loads successfully — same behavior as with a relative path, or on POSIX.
**Environment:**
- OS: Windows (Windows 11, native)
- `@graphql-tools/code-file-loader`: 8.1.32
- NodeJS: verified reproducing on Node v24.16.0 (also affects Node 22, per `@graphql-codegen/cli`'s CI)
**Additional context**
Fix is small: convert absolute paths to `file://` URLs via `url.pathToFileURL()` before passing to `import()` (bare specifiers and already-valid URLs pass through unchanged) — `isAbsolute(m) ? pathToFileURL(m).href : m`.
We hit this while fixing Windows CI for `@graphql-codegen/cli`'s test suite and worked around it with a local `pnpm patch` on `code-file-loader@8.1.32` covering both call sites above:
https://github.com/dotansimha/graphql-code-generator/blob/master/patches/%40graphql-tools__code-file-loader%408.1.32.patch
Reference PR (has the patch + full context): https://github.com/dotansimha/graphql-code-generator/pull/10935
Happy to open a PR here with the same fix upstream if that's welcome.
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]