A specifier is the string in require('...') or import '...'. Node sorts it into one of four kinds and resolves it to an absolute file URL before any code is read. Builtin specifiers (node:fs) never touch disk; the bare form fs can be shadowed by a node_modules/fs, the prefixed form cannot. Relative (./routes/users.js) and absolute (file:///srv/app/main.js) specifiers are joined against the importing file's URL. Bare specifiers (express, @shop/api/config) go through the node_modules search.

The node_modules walk is the part that surprises people. Starting from the directory of the importing file, Node appends node_modules and tries the package there, then repeats one directory up, to the filesystem root. There is no global path, and NODE_PATH is ignored for ES modules entirely.
import { createRequire } from 'node:module';
import { relative } from 'node:path';
import { fileURLToPath } from 'node:url';
const require = createRequire(import.meta.url);
const here = import.meta.dirname;
console.log(relative(here, fileURLToPath(import.meta.resolve('geo'))));
console.log(require.resolve.paths('node:fs'));
console.log(require.resolve.paths('geo').map((p) => relative(here, p)).slice(0, 3));node_modules\geo\index.js null [ 'node_modules', '..\\node_modules', '..\\..\\node_modules' ]
require.resolve.paths() returns null for builtins because they are not searched for, and the array it returns for a bare name is exactly the walk in the diagram. import.meta.resolve() is the ESM counterpart; it returns a URL string synchronously and is a release candidate as of Node 26.
Two rules catch newcomers. import requires the file extension for relative and absolute specifiers: ./routes/users does not resolve, and neither does a bare directory -- write ./routes/index.js. CommonJS still guesses, trying .js, .json, .node and then index.*, but the documentation marks that directory-as-module behavior legacy. And resolution runs on resolved symlinks, so an npm 2,036 linked package resolves inside its real location and picks up that location's package scope.