fsPromises.mkdir()

Creates a directory, optionally creating every missing parent along the path.

Signature
fsPromises.mkdir(path[, options])
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLDirectory to create.
$optionsoptionalObject | integerOptions object, or an integer taken as the mode.
$options.recursiveoptionalboolean= falseCreate missing parent directories, and do not error if the target already exists.
$options.modeoptionalinteger= 0o777Permission and sticky bits. Not supported on Windows.

Return value

Promise<string | undefined>Fulfills with undefined if recursive is false, or the first directory path created if recursive is true (undefined when nothing had to be created).

Errors

Rejects with EEXIST if the path already exists and recursive is false, ENOENT if a parent directory is missing and recursive is false, and ENOTDIR if a path component is a file.

Changelog

  • PHP v13.11.0, v12.16.0With recursive: true, the promise now fulfills with the first directory path created instead of undefined.
  • PHP v10.12.0The recursive option was added to fs.mkdir.

fsPromises.mkdir() creates one directory. With recursive: true it behaves like mkdir -p: it creates every missing parent, and — crucially — it resolves quietly instead of throwing EEXIST when the directory is already there.

When to reach for it

Almost every write-side filesystem task starts here, because writeFile(), copyFile() and open() all refuse to create folders. The idiomatic prelude is await fs.mkdir(dir, { recursive: true }) with no existence check at all — that is the check.

What trips people up

  • The return value differs by mode. Without recursive you always get undefined. With it you get the topmost directory that was created, or undefined if the whole path already existed — a handy way to tell "I made this" from "it was already there".
  • mode is masked by the process umask, so 0o777 typically lands as 0o755. It is ignored entirely on Windows.
  • recursive: true swallows EEXIST even when the existing entry is a directory you did not expect, but still rejects with EEXIST/ENOTDIR if it is a plain file.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Ensure an output or cache directory exists before writing into it.
  • Create a dated folder tree such as build/2026/08 in one call.
  • Make a temporary working directory for a task, then rm it afterwards.
  • Detect whether you were the one that created a directory, from the resolved value.

Frequently asked questions

What is the Node equivalent of mkdir -p?

fsPromises.mkdir(path, { recursive: true }) — it creates every missing parent and resolves without error if the directory already exists.

Why does mkdir throw EEXIST and how do I ignore it?

Without recursive: true, creating a directory that already exists is an error. Pass { recursive: true } rather than catching and swallowing EEXIST, which would also hide a file sitting at that path.

Why is the created directory 755 when I asked for 777?

The mode is masked by the process umask, which is typically 022, turning 0o777 into 0o755. Change process.umask() before the call, or chmod afterwards. On Windows the mode is ignored.

More examples

The ensure-directory prelude before a write

Prints all good then updated. Wrapping mkdir + writeFile like this removes the ENOENT class of bug entirely, and the second call proves recursive: true is idempotent.

Example · javascript
import fs from 'node:fs/promises';
import path from 'node:path';

async function writeDeep(file, contents) {
  await fs.mkdir(path.dirname(file), { recursive: true });
  await fs.writeFile(file, contents);
}

await writeDeep('out/reports/q3/summary.txt', 'all good');
console.log(await fs.readFile('out/reports/q3/summary.txt', 'utf8'));

// Safe to call repeatedly.
await writeDeep('out/reports/q3/summary.txt', 'updated');
console.log(await fs.readFile('out/reports/q3/summary.txt', 'utf8'));

await fs.rm('out', { recursive: true });

Using the return value to know if you created it

Prints first run created: cache, second run created: undefined, then the message. Because the first call had to create 'cache' as well as 'cache/tiles', it reports the topmost new directory rather than the one you asked for.

Example · javascript
import fs from 'node:fs/promises';

const first = await fs.mkdir('cache/tiles', { recursive: true });
console.log('first run created:', first);

const second = await fs.mkdir('cache/tiles', { recursive: true });
console.log('second run created:', second);

if (second === undefined) console.log('cache was already warm');

await fs.rm('cache', { recursive: true });

When a file blocks the path

Prints target is a file: EEXIST and parent is a file: ENOTDIR. recursive: true tolerates an existing directory but never an existing file, so it is not a blanket ignore-all-errors switch.

Example · javascript
import fs from 'node:fs/promises';

await fs.writeFile('data', 'I am a file, not a folder');

try {
  await fs.mkdir('data', { recursive: true });
} catch (err) {
  console.log('target is a file:', err.code);
}

try {
  await fs.mkdir('data/nested', { recursive: true });
} catch (err) {
  console.log('parent is a file:', err.code);
}

await fs.rm('data');

Discussion

  • Be the first to comment on this lesson.