fs.access()

Tests whether the process can reach a path with the given permissions, failing with an error if not.

Signature
fs.access(path[, mode], callback)
  • Added in Node.js v0.11.15

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLThe file or directory to test. A URL must use the file: protocol.
$modeoptionalinteger= fs.constants.F_OKBitmask of accessibility checks: fs.constants.F_OK (existence), R_OK, W_OK, X_OK. Combine with bitwise OR, for example constants.R_OK | constants.W_OK.
$callbackFunctionCompletion callback, invoked as callback(err). Receives an Error if any requested check fails.

Return value

undefinedNothing. The callback receives null when every requested check passes, or an Error when any of them fails. The promise form, fsPromises.access(path[, mode]), fulfills with undefined.

Errors

Passing a non-function callback throws ERR_INVALID_ARG_TYPE synchronously, while the callback receives ENOENT when the path does not exist, EACCES when it exists but the requested mode is denied, and ENOTDIR when a component of the path is not a directory.

Changelog

  • PHP v25.0.0The constants fs.F_OK, fs.R_OK, fs.W_OK and fs.X_OK, which were present directly on fs, were removed. Use fs.constants instead.
  • PHP v20.8.0The bare fs.F_OK, fs.R_OK, fs.W_OK and fs.X_OK constants were deprecated.
  • PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
  • PHP v10.0.0fsPromises.access() was added.
  • PHP v7.6.0The path parameter can be a WHATWG URL object using the file: protocol.
  • PHP v6.3.0Constants such as fs.R_OK moved into fs.constants as a soft deprecation.

fs.access() asks the kernel one question: may this process, with its effective user and group, do these things to this path? The mode mask picks the question — F_OK for existence, and R_OK, W_OK and X_OK for read, write and execute (on a directory, execute means "search", i.e. the right to traverse into it). Combine them with bitwise OR.

The answer arrives as success or failure, never as a boolean. The callback gets null or an Error; the promise fulfills with undefined or rejects. ENOENT means the path is not there at all, EACCES means it is there but the requested mode is denied — two very different situations that a naive exists() helper flattens into one.

When not to use it

Do not call access() to decide whether a later open(), readFile() or writeFile() will succeed. Anything can change the file between the two calls, which is a textbook time-of-check-to-time-of-use race, and the official docs explicitly recommend against it. Perform the operation and branch on err.code instead.

access() earns its place when accessibility is itself the signal: a startup check that fails loudly when a data directory is unwritable, a health probe, or a preflight that produces a better error message than the raw failure would.

Example

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

When to use it

  • Fail fast at startup with a clear message when the configured upload directory is not writable.
  • Report in a health endpoint whether a mounted volume is still readable.
  • Distinguish 'the optional plugin directory is absent' from 'it exists but permissions are wrong'.
  • Build a boolean canRead() helper for a diagnostics command that lists which paths are usable.

Frequently asked questions

How do I check if a file exists in Node.js?

Call fs.access(path, fs.constants.F_OK) and treat a rejection as 'does not exist' — or use fs.existsSync() for synchronous startup code. In almost every case you should skip the check entirely and just open or read the file, handling err.code === 'ENOENT', because a separate existence check introduces a race.

Why does fs.access throw instead of returning true or false?

fs.access() reports through the error channel so it can tell you why the check failed, not merely that it did. ENOENT (missing) and EACCES (present but denied) need different handling, and a boolean would collapse them; wrap it yourself with .then(() => true, () => false) when you genuinely only want a flag.

What is the difference between fs.access and fs.stat?

fs.access() answers a permission question about the calling process and returns no data, while fs.stat() returns a Stats object with size, timestamps and type but does not tell you whether you may read or write the file. Use stat() when you need metadata, access() when you need to know if an operation is permitted.

More examples

Checking a directory, and telling ENOENT from EACCES

The first line confirms the directory can be both listed (R_OK) and entered (X_OK) — a directory that is readable but not executable lets you see names yet not stat anything inside, which is a common source of confusing EACCES errors. The second logs ENOENT with the '(absent)' branch, showing the distinction a boolean helper would throw away.

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

await fs.mkdir('logs', { recursive: true });

// On a directory, X_OK is the right to traverse into it.
await fs.access('logs', constants.R_OK | constants.X_OK);
console.log('logs is listable and searchable');

try {
  await fs.access('logs/app.log', constants.F_OK);
} catch (err) {
  // ENOENT = not there. EACCES would mean there but forbidden.
  console.log('logs/app.log ->', err.code, err.code === 'ENOENT' ? '(absent)' : '(forbidden)');
}

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

The race, and what to do instead

Both branches print an ENOENT-driven result here, but only the second is correct under concurrency. The first performs two syscalls with a gap between them, so another process can delete or chmod the file after the check passes and the readFile still fails — you have paid for an extra syscall and still need the error handler. The second does one operation and reads err.code, which is the pattern the Node docs recommend.

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

// NOT RECOMMENDED: the file can vanish between these two calls.
try {
  await fs.access('settings.json', constants.F_OK);
  console.log('checked first:', await fs.readFile('settings.json', 'utf8'));
} catch (err) {
  console.log('checked first ->', err.code);
}

// RECOMMENDED: just do it and handle the error code.
try {
  const data = await fs.readFile('settings.json', 'utf8');
  console.log('loaded', data);
} catch (err) {
  if (err.code === 'ENOENT') console.log('no settings file, using defaults');
  else throw err;
}

Discussion

  • Be the first to comment on this lesson.