mrkeyoor.com_
Wed 23 Sept 00:32 UTC
npmUtilsupdated 22 Sept 2026

write review

We installed write 2.0.0 as 3 small CommonJS packages occupying 1 MB, then traced its real value to one convenience: each file API creates missing parent directories before writing. The main function returns a Promise or accepts a callback, while `.sync` and `.stream` cover blocking and streaming work. Options can append one newline, reject replacement, or pick an incremented filename. Version 2.0.0 moved the async implementation to `fs.createWriteStream`, added `overwrite` and `increment`, removed the separate `.promise` method, and stopped accepting a custom newline string. It is Node filesystem glue, with no transactional or durability guarantee.

Verdict

write 2.0.0 installed in 1.2 seconds and used 1 MB with 0 audit findings in our sandbox, but it has had no release since 2019 and its overwrite protections are non-atomic. Keep it where old CommonJS generators already depend on all 3 API forms; new Node code should usually use the standard library or write-file-atomic.

We installed it

Lab card: what happened when we installed writeScreenshot of write documentation
Install✓ · 1.2s3 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does write install cleanly?

Yes. In a fresh container with an empty cache, npm install write finished in 1 seconds, leaving 3 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

Can write run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does write work with both ESM and CommonJS?

Yes. Both import 'write' and require('write') worked in Node 22 in our run. The package is published as CommonJS.

Does write include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

write or fs-extra: which should you use?

fs-extra: Choose it when the project also needs maintained copy, move, remove, JSON, and output-file helpers. write 2.0.0 installed in 1.2 seconds and used 1 MB with 0 audit findings in our sandbox, but it has had no release since 2019 and its overwrite protections are non-atomic.

When should you not use write?

You are writing new code on current Node. node:fs/promises.mkdir({ recursive: true }) plus writeFile is typed, built in, and makes each filesystem decision visible.

API stability4/5The 2.0.0 surface has stayed unchanged since September 2019: one async function plus `.sync`, `.stream`, and 3 convenience options. Existing callers therefore face little churn inside the current major. The 2.0.0 migration itself removed `.promise`, removed custom newline strings, and added overwrite and increment behavior, so code copied from version 1 requires adjustment. The undocumented `{ path, data }` reality also weakens confidence in relying on prose alone.
Docs3/5The README covers the Promise, callback, sync, and stream forms, shows all 3 custom options, lists accepted input types, and states the Node 10 floor. It misses concurrency races, direct-write partial files, stream completion, ignored mkdir errors, ESM and TypeScript packaging, and the synchronous throw path for `overwrite: false`. Its return section promises `contents`, while version 2.0.0 source and runtime return `data`, which is a concrete documentation defect.
Maintenance1/5npm published 2.0.0 on 2019-09-04, and GitHub records the repository's last push on the same date. The MIT repository remains unarchived with 82 stars and 4 open issues and pull requests, but nearly 7 years without a source update is a clear maintenance gap. The package has not added declarations, an exports map, current CI signals, or atomic filesystem behavior while Node's built-in Promise APIs have matured.
Ecosystem3/5The npm endpoint counted 3,813,373 downloads in the latest completed week, which points to substantial use inside existing dependency graphs despite only 82 GitHub stars. It accepts standard filesystem options and returns a normal WriteStream, so Node code can compose around it. The surrounding ecosystem is otherwise thin: one runtime dependency handles filename increments, there are no plugins or types, and most core behavior overlaps current `node:fs` APIs.

Use it if

  • An existing CommonJS generator repeatedly creates nested directories and files through the same helper.
  • One dependency must provide Promise, callback, synchronous, and writable-stream entry points.
  • Generated exports may keep both copies by selecting names such as `report (2).csv`.
  • Text generation needs an optional final line feed without adding duplicates.
Skip it if

Setup reality

We installed write 2.0.0 in a fresh unprivileged Node 22 Bookworm container. npm completed in 1.2 seconds, left 3 packages, and used 1 MB on disk. The package itself is 36 KB unpacked with 1 direct dependency, 0 peer dependencies, and 0 audit findings. It requires Node 10 or newer. Both require() and ESM import worked in our check, but the package is CommonJS with no exports map and no bundled TypeScript declarations.

No credentials, native builds, or config files are involved. All 3 entry styles create the destination's parent directory recursively. Directory creation errors are deliberately ignored in the source, so the following stream or file open reports the eventual failure. The default is replacement. overwrite: false first calls existsSync; it can throw before a callback or Promise is established, and it cannot protect against another writer appearing after the check.

The Promise result and .sync result use { path, data }, despite README text that calls the second property contents. With increment: true, always use the returned path because an existing report.csv can turn the destination into report (2).csv. The selection comes from a separate existence check and is not concurrency-safe. Version 2.0.0 also removed .promise; calling it from 1.x examples fails.

write.stream() creates directories synchronously, then returns an ordinary fs.WriteStream; wait for finish or use stream/promises.finished before consuming the file. The newline option touches only strings and Buffers and adds a line-feed byte when one is absent. Our esbuild browser attempt failed because the module imports Node fs and path, confirming that it belongs in server scripts and build tools, not client bundles.

Patterns

Create parents and write a file write-nested-file

const write = require('write');

const result = await write(
  'dist/reports/today.txt',
  'ready',
);
console.log(result.path, result.data);

Version 2.0.0 creates missing parent directories and resolves with `{ path, data }`. The README incorrectly names `contents` instead of `data`.

Use the callback entry write-with-callback

const write = require('write');

write('dist/status.txt', 'ok', (error, result) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(result.path);
});

Passing a callback suppresses the Promise return. `overwrite: false` can still throw synchronously before this callback is attached.

Write a Buffer unchanged write-buffer

const write = require('write');

const bytes = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
await write('dist/signature.bin', bytes);

Strings, Buffers, and Uint8Arrays are accepted in 2.0.0. Leave `newline` off for binary payloads.

Add one final line feed ensure-final-newline

const write = require('write');

await write('dist/generated.conf', 'enabled=true', {
  newline: true,
});

Version 2.0.0 adds ` ` only when a string or Buffer lacks it. Custom newline strings from 1.x are no longer supported.

Reject an existing file refuse-replacement

const write = require('write');

try {
  await write('exports/report.csv', csv, { overwrite: false });
} catch (error) {
  if (!String(error.message).startsWith('File already exists:')) {
    throw error;
  }
}

The 2.0.0 existence check and write are separate operations. This does not prevent another process from winning the path after the check.

Keep an existing file under its name increment-destination-name

const write = require('write');

const result = await write('exports/report.csv', csv, {
  increment: true,
});
console.log(result.path);

If `report.csv` exists, the helper may choose `report (2).csv`. Use the returned path, and do not rely on this for concurrent writers.

Write synchronously in a build script write-during-build

const write = require('write');

const result = write.sync(
  'dist/meta/version.txt',
  '2.4.0',
  { newline: true },
);
console.log(result.path);

`.sync` blocks while it creates directories and writes. Keep it outside request handlers and latency-sensitive loops.

Pipe into a nested destination pipe-readable-to-file

const fs = require('node:fs');
const write = require('write');

const input = fs.createReadStream('README.md');
const output = write.stream('backup/docs/README.md');
input.on('error', console.error);
output.on('error', console.error);
input.pipe(output);

`write.stream` makes parent directories synchronously and returns `fs.WriteStream`. Handle errors on both sides of the pipe.

Wait until streaming output finishes await-stream-completion

const { finished } = require('node:stream/promises');
const write = require('write');

const output = write.stream('dist/events.log');
output.end('started\n');
await finished(output);

Calling `end` starts completion; it does not prove the file is done. `finished` resolves after the stream completes or rejects on error.

Pass a mode to the filesystem set-file-mode

const write = require('write');

await write('dist/run.sh', '#!/bin/sh\necho ok\n', {
  mode: 0o755,
});

Options reach `fs.createWriteStream`. On an existing path, `mode` does not necessarily replace current permissions, so use chmod when that is required.

Write Latin-1 text choose-text-encoding

const write = require('write');

await write('dist/latin1.txt', 'café', {
  encoding: 'latin1',
});

The package defaults to UTF-8. An encoding option affects string conversion; Buffer and Uint8Array inputs already contain bytes.

Replace a generated JSON file replace-config-file

const write = require('write');

const json = JSON.stringify(payload, null, 2);
await write('dist/latest.json', json, {
  overwrite: true,
  newline: true,
});

Replacement is already the default in 2.0.0. A crash during this direct write can expose a partial destination to readers.

Alternatives

PackageRegistryPick it when
fs-extranpmChoose it when the project also needs maintained copy, move, remove, JSON, and output-file helpers.
write-file-atomicnpmChoose it when atomic replacement matters and readers must not see a partly written destination.
output-file-syncnpmChoose it for a narrowly synchronous create-parents-and-write operation.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.