fs.mkdir()
Creates a directory asynchronously, optionally creating every missing parent along the way.
fs.mkdir(path[, options], callback)- Added in Node.js v0.1.8
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | <string> | <Buffer> | <URL> | The directory to create. |
$optionsoptional | <Object> | <integer>= { recursive: false, mode: 0o777 } | An integer is treated as the mode. As an object: recursive <boolean> (default false) to create missing parents and to succeed when the directory already exists, and mode <string>|<integer> (default 0o777, not supported on Windows). |
$callback | <Function> | Called as (err, path). The path argument is present only when recursive is true and a directory was actually created; otherwise it is undefined. |
Return value
undefinedNothing. With recursive: true the callback's second argument is the first directory that was created, or undefined if nothing needed creating.
Errors
Reports EEXIST when the directory already exists and recursive is false, ENOENT when a parent is missing and recursive is false, ENOTDIR when a path component is a file, and EACCES on permission failures, all through the callback.
Changelog
- PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
- PHP v13.11.0, v12.17.0In recursive mode the callback now receives the first created path as its second argument.
- PHP v10.12.0The second argument may be an options object with recursive and mode properties; before this, recursive creation had to be written by hand.
- PHP v10.0.0The callback parameter is no longer optional; omitting it throws a TypeError at runtime.
- PHP v7.6.0path may be a WHATWG URL object using the file: protocol.
fs.mkdir() creates one directory. With { recursive: true } it becomes the equivalent of mkdir -p: every missing parent is created, and an already-existing directory is not an error. That combination is what you want almost every time you are preparing an output directory.
recursive changes the error behaviour, not just the depth
Without it, calling twice fails with EEXIST, and creating a/b/c when a does not exist fails with ENOENT. With it, both cases succeed silently. This is why recursive: true makes the “check whether it exists, then create it” dance unnecessary — and safer, since that check has a race window.
The callback's second argument
In recursive mode you also receive the first directory that had to be created — creating build/static/img from scratch gives you 'build'. When nothing needed creating it is undefined, which is a neat way to tell “I made this” from “it was already here” without an extra stat.
One caveat: recursive: true also swallows the case where the path already exists as a directory but you assumed you owned it. And mode is masked by the umask, so 0o777 typically lands as 0o755.
Example
When to use it
- Create a nested build output directory before writing generated files into it.
- Prepare a per-date log or upload folder on first use without checking whether it exists.
- Set up a temporary working directory at the start of a test and remove it afterwards.
Frequently asked questions
How do I do mkdir -p in Node.js?
Pass { recursive: true }: fs.mkdir('a/b/c', { recursive: true }, cb). It creates every missing parent and succeeds silently if the directory already exists, available since Node 10.12.0.
Why does fs.mkdir fail with EEXIST?
Because the directory is already there and recursive is false, which is the default. Add { recursive: true } instead of checking with existsSync first — it is both shorter and free of the check-then-act race.
How do I know whether the directory was created or already existed?
In recursive mode the callback's second argument is the first path that was actually created, and undefined when nothing needed creating. Compare against undefined rather than making an extra fs.stat() call.
More examples
Without recursive, a second call is an error
The second call reports EEXIST. Any code that creates a directory on every run and does not pass recursive: true has to catch and ignore that code explicitly, which is exactly the boilerplate recursive removes.
const fs = require('node:fs');
fs.mkdir('once-only', (err) => {
if (err) throw err;
console.log('created');
fs.mkdir('once-only', (err2) => {
console.log('again ->', err2 && err2.code);
fs.rmSync('once-only', { recursive: true, force: true });
});
});Missing parents need recursive too
The first attempt fails with ENOENT because 'nested' and 'nested/deep' do not exist — the plain form creates only the final component. The recursive attempt creates all three levels and reports a null error.
const fs = require('node:fs');
fs.mkdir('nested/deep/tree', (err) => {
console.log('without recursive:', err && err.code);
fs.mkdir('nested/deep/tree', { recursive: true }, (err2) => {
console.log('with recursive:', err2);
console.log('exists:', fs.existsSync('nested/deep/tree'));
fs.rmSync('nested', { recursive: true, force: true });
});
});Preparing an output directory before writing
This is the standard order of operations: create the directory, then write into it. Skipping the mkdir would make the writeFile fail with ENOENT, since writing never creates folders.
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import path from 'node:path';
async function main() {
const outDir = path.join('dist', 'reports');
await fsp.mkdir(outDir, { recursive: true });
await fsp.writeFile(path.join(outDir, 'summary.txt'), 'all green\n');
console.log('files:', await fsp.readdir(outDir));
console.log('content:', (await fsp.readFile(path.join(outDir, 'summary.txt'), 'utf8')).trim());
await fsp.rm('dist', { recursive: true, force: true });
console.log('cleaned:', fs.existsSync('dist'));
}
main();
Discussion