This error occurs when Node.js file system operations expect a directory path but receive a file path instead. It commonly appears with fs.readdir(), fs.scandir(), or file watching utilities.
The ENOTDIR (Error: NOT a DIRectory) error is a system-level error that occurs when a Node.js operation expects a directory but encounters a regular file instead. This typically happens with directory-reading operations like fs.readdir(), fs.readdirSync(), or fs.scandir() when they're passed a file path instead of a directory path. The error originates from the underlying operating system when attempting to list directory contents of something that isn't actually a directory. This is a protective mechanism that prevents incorrect file system operations. This error is particularly common in recursive file operations, file watching utilities (like Webpack's Watchpack, Nuxt, or the watch npm package), and dynamic command loaders where code automatically scans directories but mistakenly tries to process individual files as directories.
Examine the error message to identify which path is causing the issue. The error will show the exact path that was treated as a directory:
Error: ENOTDIR: not a directory, scandir '/path/to/file.txt'The path in the error message (e.g., file.txt) is a file, not a directory. Note the file extension or check if it's a known file in your project.
Add a check to ensure the path is a directory before attempting to read it. Use fs.statSync() or fs.lstatSync() to verify:
const fs = require('fs');
const path = require('path');
function readDirectorySafely(dirPath) {
try {
const stats = fs.statSync(dirPath);
if (!stats.isDirectory()) {
console.log(`Skipping file: ${dirPath}`);
return [];
}
return fs.readdirSync(dirPath);
} catch (error) {
console.error(`Error reading ${dirPath}:`, error.message);
return [];
}
}
// Usage
const items = readDirectorySafely('/path/to/check');This approach safely handles both files and directories.
If you're recursively traversing directories, ensure you only call directory operations on actual directories:
const fs = require('fs');
const path = require('path');
function traverseDirectory(dirPath) {
const items = fs.readdirSync(dirPath);
items.forEach(item => {
const fullPath = path.join(dirPath, item);
const stats = fs.statSync(fullPath);
if (stats.isDirectory()) {
// Recurse into subdirectories
traverseDirectory(fullPath);
} else if (stats.isFile()) {
// Process files
console.log('Processing file:', fullPath);
}
});
}
traverseDirectory('./my-directory');Always check stats.isDirectory() before making recursive calls.
If the error occurs with file watchers (Webpack, Vite, etc.), ensure your configuration points to directories, not files:
webpack.config.js:
module.exports = {
// Watch the directories, not individual files
watchOptions: {
ignored: /node_modules/,
// Don't watch individual files
},
// Ensure entry points and context are correct
context: path.resolve(__dirname, 'src'),
};For watch libraries:
const watch = require('watch');
// Watch a directory, not a file
watch.watchTree('./src', (f, curr, prev) => {
if (typeof f === "object" && prev === null && curr === null) {
// Finished walking the tree
} else {
console.log('File changed:', f);
}
});If you're dynamically loading commands or modules, ensure you're reading the directory, not including files in the path:
Incorrect (causes ENOTDIR):
const commandFiles = fs.readdirSync('./commands/ping.js'); // File path!Correct:
const path = require('path');
const fs = require('fs');
const commandsDir = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsDir)
.filter(file => file.endsWith('.js'));
commandFiles.forEach(file => {
const filePath = path.join(commandsDir, file);
const command = require(filePath);
console.log('Loaded command:', command);
});Always use the directory path for readdirSync(), then join individual file names.
Symbolic Links and Hard Links:
When dealing with symbolic links, use fs.lstatSync() instead of fs.statSync() if you want to check the link itself rather than the target. This can prevent ENOTDIR errors when symlinks point to files but are treated as directories.
Platform Differences:
On Windows, there's a historical inconsistency where fs.readdir() might not always report ENOTDIR errors correctly for certain edge cases. This has been fixed in modern Node.js versions, but if you're supporting older environments, add explicit isDirectory() checks.
Performance Considerations:
When scanning large directory trees, calling statSync() on every entry can impact performance. Consider using fs.readdir() with the withFileTypes option (Node.js 10.10+):
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
entries.forEach(entry => {
if (entry.isDirectory()) {
// Process directory without additional stat call
const subDir = path.join(dirPath, entry.name);
processDirectory(subDir);
}
});This avoids the overhead of separate statSync() calls for each entry.
TypeScript Type Guards:
When working with TypeScript, create type guards for directory operations:
import { Stats } from 'fs';
function isDirectory(stats: Stats): boolean {
return stats.isDirectory();
}
function safeReadDir(dirPath: string): string[] {
const stats = fs.statSync(dirPath);
if (!isDirectory(stats)) {
throw new TypeError(`Path is not a directory: ${dirPath}`);
}
return fs.readdirSync(dirPath);
}Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams