New to Rust? Grab our free Rust for Beginners eBook Get it free →
fs.writeFile in Node.js: Writing Files with fs Module

Node.js ships with a built-in fs module that handles everything from reading config files to writing logs. fs.writeFile is the method you reach for when you need to write data to disk without blocking the rest of your app.
This guide covers how fs.writeFile works, when to use its synchronous counterpart fs.writeFileSync, how the promise-based API fits in, and what to do when you need to append rather than overwrite.
What is fs.writeFile in Node.js?
The fs module (short for file system) is a core Node.js module. No install needed, just require('fs'). It gives you methods to read, write, copy, append and delete files. fs.writeFile is the async method for writing data to a file. If the file exists, it overwrites it. If it doesn’t exist, Node creates it first.
const fs = require('fs');
If you’re on Node 12 or later you can also import it using the node: prefix, which makes it explicit that you’re pulling from Node’s built-in modules rather than an npm package:
const fs = require('node:fs');
Both forms work the same way. The node: prefix is a newer convention. If you hit a “Cannot find module ‘node:fs'” error, your Node version is likely below 12. Our guide on resolving the cannot find module node:fs error walks through that fix in detail.
For a wider look at what the fs module can do beyond writing (reading, deleting, checking paths), see our Node FS module overview.
fs.writeFile syntax and parameters
fs.writeFile(file, data, [options], callback)
Parameters:
file: a string path, Buffer, URL or file descriptor pointing to the target file.data: the content to write. Can be a string, Buffer, TypedArray or DataView.options(optional): a string encoding name like'utf8', or an object with three fields:encoding: default is'utf8'.flag: file system flag controlling how the file is opened. Default is'w'(write, overwrite).mode: file permission integer. Default is0o666.
callback: a function called when the write finishes. Receives one argument:err, which isnullon success or an Error object on failure.
Return value: none. Results are handled in the callback.
Basic example
const fs = require('fs');
const content = 'Hello from Codeforgeek!';
fs.writeFile('output.txt', content, (err) => {
if (err) {
console.error('Write failed:', err);
return;
}
console.log('File written successfully.');
});
Run this and output.txt appears in your working directory with the string you passed. If you run it again, the file is overwritten, not appended.

Specifying encoding
If you pass a string as the third argument, Node treats it as the encoding:
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) throw err;
console.log('Done.');
});
UTF-8 is the default so this is optional for most text files. You’d set it explicitly when working with 'latin1', 'ascii' or another encoding.
Using file flags
The flag option controls what happens when the file already exists. The most common ones:
| Flag | Behavior | Creates file if missing |
|---|---|---|
'w' | Write, overwrite existing content | Yes |
'a' | Append to end of file | Yes |
'r+' | Read and write, no overwrite | No |
'w+' | Read and write, overwrite | Yes |
'a+' | Read and append | Yes |
To append with fs.writeFile instead of overwriting, pass flag: 'a':
fs.writeFile('output.txt', '\nNew line added', { flag: 'a' }, (err) => {
if (err) throw err;
});
That said, if your goal is to append data rather than replace it, fs.appendFile is a cleaner choice. Its default behavior is appending, so you don’t have to remember the flag. See our guide on appending to files in Node.js for the full breakdown.
Writing a file synchronously with fs.writeFileSync
fs.writeFileSync blocks the event loop until the write finishes. Every other operation in your Node process waits. That sounds bad, but it makes sense in a few scenarios: startup scripts, CLI tools, or any situation where later code genuinely can’t run until the file is on disk.
Syntax
fs.writeFileSync(file, data, [options])
Same parameters as fs.writeFile, minus the callback.
Example
const fs = require('fs');
try {
fs.writeFileSync('config.json', JSON.stringify({ port: 3000 }, null, 2));
console.log('Config written.');
} catch (err) {
console.error('Write failed:', err);
}
Wrap writeFileSync in a try/catch. Since there’s no callback, errors throw synchronously and will crash your process if you don’t handle them.

When to use sync vs async
In a web server or any app handling multiple requests, writeFileSync blocks the entire process while waiting for disk. That kills throughput. Use fs.writeFile (async) for server-side code and fs.writeFileSync for one-off scripts or bootstrapping where you genuinely need sequential execution.
Writing files with the promise-based API
Callback-heavy code gets messy fast. The fs/promises sub-module gives you the same functionality with a promise-based API, so you can use async/await and keep your code linear.
const fs = require('fs/promises');
async function writeConfig() {
try {
await fs.writeFile('config.json', JSON.stringify({ port: 3000 }, null, 2));
console.log('Done.');
} catch (err) {
console.error(err);
}
}
writeConfig();

You can also reach it via fs.promises if you’ve already required the main fs module:
const { promises: fs } = require('fs');
The promise API was stabilized in Node 10 and works well alongside async/await. If you’re writing new code and your Node version supports it, this is the style to default to. It avoids callback nesting and plays well with Promise.all when you need to write multiple files in parallel.
const fs = require('fs/promises');
async function writeMultiple() {
try {
await Promise.all([
fs.writeFile('a.txt', 'File A content'),
fs.writeFile('b.txt', 'File B content'),
]);
console.log('Both files written.');
} catch (err) {
console.error(err);
}
}
writeMultiple();

For more on async patterns in Node.js, see our guide on asynchronous programming in Node.js.
Writing JSON data to a file
A very common use case: saving a JavaScript object to a JSON file. JSON.stringify handles the serialization, and fs.writeFile handles the disk write.
const fs = require('fs');
const user = {
id: 1,
name: 'Rahul',
email: '[email protected]',
};
fs.writeFile('user.json', JSON.stringify(user, null, 2), (err) => {
if (err) {
console.error('Failed to write JSON:', err);
return;
}
console.log('user.json saved.');
});
JSON.stringify(user, null, 2) pretty-prints the output with 2-space indentation. Without the third argument you get compact JSON on one line, which is fine for machine-readable files but harder to inspect manually.

If you’re saving configuration or state that might be invalid JSON later, consider wrapping your JSON.stringify call in a try/catch too. JSON.stringify throws on circular references, and you don’t want that to silence your file write without any error log.
To read the file back later, use fs.readFile and JSON.parse. See our Node.js readFile guide for examples.
Handling errors properly
The err argument in the callback is the primary error signal for fs.writeFile. Don’t just console-log it and move on. Handle it or rethrow it so calling code knows something went wrong.
const fs = require('fs');
fs.writeFile('/nonexistent-dir/output.txt', 'data', (err) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('Directory does not exist. Create it first.');
} else if (err.code === 'EACCES') {
console.error('Permission denied.');
} else {
console.error('Unexpected error:', err.message);
}
return;
}
console.log('Written.');
});
Common error codes you’ll hit:
ENOENT: one or more directories in the path don’t exist.fs.writeFilecreates the target file but not missing parent directories.EACCES: permission denied on the file or directory.ENOSPC: disk full.
To check whether a file exists before writing, take a look at our guide on how to check if a file exists in Node.js.
Using fs.createWriteStream for large files
fs.writeFile loads the entire data payload into memory before writing. For small files that’s fine. For large files (logs, exports, CSV dumps) it’s a problem because you’re holding everything in RAM at once.
fs.createWriteStream writes data in chunks instead:
const fs = require('fs');
const stream = fs.createWriteStream('large-output.txt');
stream.write('First chunk of data\n');
stream.write('Second chunk of data\n');
stream.end();
stream.on('finish', () => {
console.log('Write complete.');
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
Each stream.write() call flushes a chunk to disk without waiting for all data to be ready. Call stream.end() when you’re done. That flushes any remaining buffer and closes the file.
Use createWriteStream when: the data comes from another stream (an HTTP response, a database cursor), the file size could be large, or you’re writing data that arrives incrementally over time.
Comparing writeFile, writeFileSync and the promises API
| Method | Blocks event loop? | Error handling | Best for |
|---|---|---|---|
fs.writeFile | No | Callback err arg | Server code, concurrent writes |
fs.writeFileSync | Yes | try/catch | Scripts, CLI tools, bootstrapping |
fs/promises writeFile | No | try/catch with await | Modern async/await code |
fs.createWriteStream | No | 'error' event | Large files, streaming data |
If you’re reading files as often as you’re writing them, also see our article on how to get a list of file names in a folder in Node.js and how to copy a file in Node.js for related fs operations.
Key Takeaways
fs.writeFilewrites asynchronously and takes a callback for errors. It overwrites by default.fs.writeFileSyncis blocking. Use it only for scripts or startup code, not server handlers.- The
fs/promisesAPI lets you useasync/awaitinstead of callbacks for cleaner code. - Pass
flag: 'a'to append rather than overwrite, or just usefs.appendFile. fs.writeFiledoes not create missing parent directories. Only the final file is created.- For large files, use
fs.createWriteStreamto write data in chunks and avoid high memory use. - Wrap
writeFileSyncin try/catch and always check theerrargument in async callbacks.
FAQ
What does fs.writeFile do in Node.js?
fs.writeFile writes data to a file asynchronously. If the file exists it is overwritten. If not, Node creates it. The callback receives any error.
What is the difference between fs.writeFile and fs.writeFileSync?
fs.writeFile is non-blocking and uses a callback. fs.writeFileSync blocks the event loop until the write completes and throws on error. Use async in server code.
How do I write JSON to a file in Node.js?
Pass JSON.stringify(yourObject, null, 2) as the data argument to fs.writeFile or fs.writeFileSync. The third argument controls indentation for readable output.
Can fs.writeFile create a new file if it doesn’t exist?
Yes. fs.writeFile creates the file automatically if it is missing. It does not create missing parent directories. Those must already exist.
How do I append to a file instead of overwriting it?
Use fs.appendFile, or pass { flag: 'a' } as the options object to fs.writeFile. Both add content to the end of the file without removing existing data.
How do I handle errors from fs.writeFile?
Check the err argument in the callback. If err is not null, the write failed. Inspect err.code for values like ENOENT (missing directory) or EACCES (permission denied).
When should I use fs.createWriteStream instead of fs.writeFile?
Use createWriteStream when writing large files or when data arrives in chunks over time. It writes incrementally and keeps memory usage low rather than buffering the entire payload.
Conclusion
Writing files to disk is one of those tasks you set up once and then rely on constantly. Whether it’s a build script saving output, a server logging events, or a CLI tool generating a config, knowing when to reach for fs.writeFile, fs.writeFileSync, or the promise-based API means you can pick the right tool without second-guessing it each time.




