os.userInfo()
Returns the effective user's username, uid, gid, shell and home directory from the operating system.
os.userInfo([options])- Added in Node.js v6.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$optionsoptional | Object | Options object. |
$options.encodingoptional | string= 'utf8' | Character encoding used to interpret the resulting strings. If set to 'buffer', the username, shell and homedir values are Buffer instances instead of strings. |
Return value
ObjectAn object with `username` (string), `uid` (number), `gid` (number), `shell` (string) and `homedir` (string). On Windows `uid` and `gid` are `-1` and `shell` is `null`. When `encoding` is `'buffer'`, `username`, `shell` and `homedir` are `Buffer` instances.
Errors
Throws a SystemError if the effective user has no username or homedir — typically because no passwd entry exists for the running uid.
os.userInfo() reads the effective user's entry from the system account database — on POSIX a subset of the passwd record, on Windows a partial equivalent where uid and gid are -1 and shell is null.
When to reach for it
When you need the account rather than the environment: printing who a CLI is running as, deciding whether you are root by testing uid === 0, choosing file ownership, or getting a home directory that $HOME cannot lie about. It is the authoritative counterpart to os.homedir(), which prefers environment variables.
What trips people up
It throws. In slim and distroless containers run with --user 1000:1000, or under an OpenShift-style random UID, there is often no passwd entry for that uid, and the call fails with a SystemError rather than returning partial data. Wrap it in try/catch anywhere it might run in a container. Note that os.homedir() keeps working in that situation because it reads $HOME.
The other surprise is that a real user name and shell do not imply a real login session — a daemon under systemd gets the same fields. And uid === 0 tells you the effective uid, not whether Linux capabilities have been dropped.
Example
When to use it
- Print the effective account in a CLI's diagnostic output
- Refuse to run as root by checking whether uid is 0
- Get a home directory that is not affected by a modified $HOME
Frequently asked questions
Why does os.userInfo() throw ERR_SYSTEM_ERROR in Docker?
Because the container is running under a uid with no matching entry in /etc/passwd, which is common in distroless and scratch images started with --user. Wrap the call in try/catch and fall back to process.getuid() and the USER environment variable, or add a passwd entry for the uid to the image.
How do I check if a Node.js process is running as root?
Test os.userInfo().uid === 0 on POSIX, or use process.getuid() === 0 which does not need an account database. Guard the check on Windows, where uid is always -1 and the concept does not apply; check for an elevated token there instead.
Why do os.userInfo().homedir and os.homedir() disagree?
Because os.userInfo() reads the account's home from the operating system while os.homedir() prefers the $HOME or %USERPROFILE% environment variable. Under sudo, inside systemd units and in containers where HOME is set explicitly, the environment wins and the two values diverge.
More examples
Call it safely where a passwd entry may be missing
On a normal machine the passwd branch runs and source reads "passwd". In a distroless image started with --user 1000:1000 the call throws and the fallback returns the uid from process.getuid(), which works without any account database — and os.homedir() keeps answering either way because it reads $HOME.
const os = require('node:os');
function whoAmI() {
try {
const u = os.userInfo();
return { username: u.username, uid: u.uid, gid: u.gid, source: 'passwd' };
} catch (err) {
// Slim containers run with --user often have no /etc/passwd entry.
return {
username: process.env.USER ?? process.env.USERNAME ?? 'unknown',
uid: typeof process.getuid === 'function' ? process.getuid() : -1,
gid: typeof process.getgid === 'function' ? process.getgid() : -1,
source: `fallback (${err.code ?? err.name})`
};
}
}
console.log(whoAmI());
console.log('homedir still available:', os.homedir().length > 0);Refuse to run as root
The Windows guard matters: uid is always -1 there, so an unguarded uid === 0 check is meaningless rather than wrong. The escape hatch is the other half — installers and container entrypoints legitimately run as root, so a hard refusal with no override just gets patched out downstream.
const os = require('node:os');
function rootCheck({ allowRoot = false } = {}) {
if (os.platform() === 'win32') return 'skipped: uid is not meaningful on Windows';
const { uid, username } = os.userInfo();
if (uid === 0 && !allowRoot) {
return `refusing to run as ${username} (uid 0) — re-run as a normal user or pass --allow-root`;
}
return `running as ${username} (uid ${uid})`;
}
console.log(rootCheck());
console.log(rootCheck({ allowRoot: true }));Buffer encoding for non-UTF-8 account names
Only username, shell and homedir become Buffers; uid and gid stay numbers. The byte-versus-character comparison is why the option exists: POSIX names are byte strings, so an account created with a non-UTF-8 locale would be mangled by the default decoding and the Buffer form preserves it exactly.
const os = require('node:os');
const asString = os.userInfo();
const asBuffer = os.userInfo({ encoding: 'buffer' });
console.log('username type (default):', typeof asString.username);
console.log('username is Buffer:', Buffer.isBuffer(asBuffer.username));
console.log('homedir is Buffer:', Buffer.isBuffer(asBuffer.homedir));
console.log('uid stays a number:', typeof asBuffer.uid);
console.log('round-trips to the same text:', asBuffer.username.toString('utf8') === asString.username);
console.log('raw bytes:', asBuffer.username.length, 'vs chars:', asString.username.length);
Discussion