How to Use writeFile in Node.js: A Complete Guide to Writing Files

Use fs.writeFile() for asynchronous file writing, fs.writeFileSync() for synchronous operations, or fs.promises.writeFile() for promise-based async workflows in Node.js.

The writefile in node js workflow is fundamental to server-side JavaScript development. According to the nodejs/node source code, the file system module provides three distinct APIs for writing data to disk—each optimized for different concurrency and error-handling patterns. This guide examines the actual implementation in lib/fs.js and lib/internal/fs/promises.js to show you exactly how to use each variant.

Understanding the writeFile API in Node.js

Node.js exposes file writing capabilities through three primary interfaces:

  • fs.writeFile(path, data, options, callback) – The traditional callback-based asynchronous method defined in lib/fs.js (lines 2326–2456)
  • fs.writeFileSync(path, data, options) – The synchronous blocking variant in lib/fs.js (lines 2378–2398)
  • fs.promises.writeFile(path, data, options) – The modern Promise-based API implemented in lib/internal/fs/promises.js (lines 1220–1244)

All three ultimately delegate to the same C++ bindings—binding.writeFileUtf8 for string data and binding.writeBuffer for Buffer/Uint8Array instances—located in src/node_file.cc.

How to Use writeFile in Node.js: Practical Examples

Asynchronous File Writing with Callbacks

The standard writeFile method is non-blocking and accepts a callback to handle completion or errors. According to the source in lib/fs.js, it validates the path using getValidatedPath, normalizes options via validateEncoding, and allocates an FSReqCallback object for the underlying libuv thread pool.

const { writeFile } = require('fs');

writeFile('example.txt', 'Hello, Node.js!', 'utf8', (err) => {
  if (err) throw err;
  console.log('File written successfully');
});

Synchronous File Writing

Use writeFileSync when you need to block the event loop until the operation completes. The implementation in lib/fs.js performs the same validation steps as the async version but invokes binding.writeFileUtf8 or binding.writeBuffer directly without creating an FSReqCallback object.

const { writeFileSync } = require('fs');

try {
  writeFileSync('sync.txt', Buffer.from([0x01, 0x02, 0x03]));
  console.log('Sync write done');
} catch (e) {
  console.error(e);
}

Promise-Based File Writing

Modern Node.js applications typically use the fs.promises API for cleaner async/await syntax. As implemented in lib/internal/fs/promises.js, this wrapper calls the callback-based writeFile and returns a Promise that resolves when the underlying FSReqCallback completes.

const { promises: fs } = require('fs');

async function save() {
  await fs.writeFile('promise.txt', 'Using promises');
  console.log('Promise write complete');
}
save().catch(console.error);

Writing Binary Data and Buffers

When writing binary data, Node.js bypasses string encoding entirely. If the data argument is a Buffer, Uint8Array, or similar ArrayBufferView, the source code routes the call to binding.writeBuffer instead of binding.writeFileUtf8.

const { writeFile } = require('fs');

const binary = Uint8Array.from([0xde, 0xad, 0xbe, 0xef]);
writeFile('binary.bin', binary, { encoding: 'binary' }, (err) => {
  if (err) throw err;
});

Core Implementation Details of writeFile in Node.js

Understanding the internal workflow helps debug permission errors or encoding issues. According to the nodejs/node source:

  1. Argument Validation – The API normalizes the options argument and validates the callback via validateFunction in lib/internal/fs/utils.js.
  2. Path Handling – The path passes through getValidatedPath (supporting strings, Buffers, or URLs) and checks the Permission Model via permission.has('fs.write', path) when enabled.
  3. Flag Selection – The default flag is 'w' (write and truncate). Explicit flags convert to numeric codes via stringToFlags in lib/internal/fs/utils.js.
  4. Encoding Resolution – String data uses validateEncoding to resolve the encoding (default 'utf8') before calling binding.writeFileUtf8.
  5. Binding Execution – The actual I/O occurs in src/node_file.cc via writeFileUtf8 or writeBuffer, executed either asynchronously through an FSReqCallback object or synchronously depending on the API variant.

Common Flags and Options for writeFile

The options object controls how writefile in node js behaves:

  • flag'w' (default) truncates the file or creates it if missing; 'a' appends to existing content; 'wx' fails if the file already exists.
  • encoding'utf8' (default), 'ascii', 'base64', 'hex', or 'binary' (deprecated alias for 'latin1').
  • mode – File permissions as an octal number (default 0o666), masked by process.umask().

Summary

  • fs.writeFile provides asynchronous, callback-based file writing implemented in lib/fs.js (lines 2326–2456).
  • fs.writeFileSync offers synchronous blocking writes in lib/fs.js (lines 2378–2398).
  • fs.promises.writeFile delivers modern Promise-based async/await support from lib/internal/fs/promises.js (lines 1220–1244).
  • All variants delegate to C++ bindings (binding.writeFileUtf8 or binding.writeBuffer) in src/node_file.cc after validating paths, flags, and encodings.
  • Use flag: 'a' to append rather than truncate, and prefer the Promise API for cleaner error handling in modern applications.

Frequently Asked Questions

What is the difference between writeFile and writeFileSync in Node.js?

writeFile executes asynchronously using the libuv thread pool and accepts a callback function to handle completion or errors, allowing the event loop to continue processing other tasks. writeFileSync blocks the entire Node.js process until the operating system confirms the write is complete, making it simpler for scripts but unsuitable for high-concurrency servers.

How do I append to a file instead of overwriting it using writeFile?

Pass { flag: 'a' } in the options argument. According to the source code in lib/fs.js, the stringToFlags utility converts 'a' to the O_APPEND flag, which instructs the C++ binding in src/node_file.cc to write at the end of the file rather than truncating existing content.

Can I use writeFile with promises instead of callbacks?

Yes. Import fs.promises or use require('fs').promises to access the Promise-based writeFile method implemented in lib/internal/fs/promises.js. This wrapper internally calls the callback-based version but returns a Promise that resolves when the underlying FSReqCallback completes, enabling cleaner async/await syntax.

What happens if the file doesn't exist when using writeFile?

If the file does not exist, writeFile creates it automatically provided the parent directory exists and the process has write permissions. The default flag: 'w' triggers this behavior in the C++ binding, which uses O_CREAT combined with O_TRUNC to ensure the file exists and is empty before writing. If the parent directory is missing, the operation throws ENOENT.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →