microsoft/playwright · error · Error
fs is not available in the browser
Error message
fs is not available in the browser
What it means
Thrown by the browser-bundle stub of Node's `fs` module: any access to `fs`, `fs.promises.*`, `fs.createReadStream`, or `fs.createWriteStream` invokes `notAvailable()` which throws. The `promises` object is a Proxy whose every property getter throws, so even `fs.promises.readFile` triggers it.
Source
Thrown at packages/playwright-client/src/nodeStubs/fs.ts:18
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function notAvailable(): never {
throw new Error('fs is not available in the browser');
}
export const promises: any = new Proxy({}, { get: () => notAvailable });
export const createReadStream: any = notAvailable;
export const createWriteStream: any = notAvailable;
export default { promises, createReadStream, createWriteStream };
View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Gate fs usage behind an environment check (`import.meta.env?.SSR`, `typeof window`) and only call it in Node.
- Move fs-dependent logic out of the browser bundle into a server/Node entry point.
- Configure the bundler to externalize `fs` for the Node target instead of aliasing the stub.
- Replace fs reads with the browser-friendly API (fetch, File API) where applicable.
Example fix
// before
import fs from 'fs';
const cfg = fs.readFileSync('./config.json', 'utf8'); // throws in browser bundle
// after
let cfg: string;
if (typeof window === 'undefined') {
const fs = await import('fs');
cfg = fs.readFileSync('./config.json', 'utf8');
} else {
const res = await fetch('/config.json');
cfg = await res.text();
} Defensive patterns
Strategy: type-guard
Validate before calling
const isBrowserBundle = typeof window !== 'undefined' && (import.meta as any).env?.SSR === false;
function safeReadFileSync(p: string): string {
if (isBrowserBundle) throw new Error('fs reads are disabled in the browser bundle');
return require('fs').readFileSync(p, 'utf8');
} Type guard
function fsAvailable(): boolean {
return typeof window === 'undefined' && typeof process !== 'undefined' && !!process.versions?.node;
} Try / catch
try {
return fs.readFileSync(p, 'utf8');
} catch (e) {
if (/fs is not available in the browser/.test(e.message)) {
return await (await fetch(p)).text();
}
throw e;
} Prevention
- Isolate fs imports in Node-only entry points.
- Mark `fs` external in Node bundler targets.
- Run a bundle analyzer to confirm the stub is not pulled into the browser build.
When it happens
Trigger: Code that imports/uses `fs` in a context resolved to `packages/playwright-client/src/nodeStubs/fs.ts` — i.e. the browser bundle of @playwright/client. Triggered by `import fs from 'fs'` then `fs.readFileSync(...)`, `fs.promises.readFile(...)`, or `fs.createReadStream(...)`.
Common situations: Pulling a Node-only utility into the browser client build; a dependency that lazily requires `fs`; bundler resolving `fs` to the stub instead of leaving it external; code shared between Node and browser that calls fs unconditionally.
Related errors
- path is not available in the browser
- No devices found
- Cannot launch Firefox with relative home directory. Did you
- Unable to open X display! ================================ M
- Unsupported platform: ${process.platform}
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/3196416c8612b330.
Report an issue: GitHub.