util.deprecate()
Wraps a function so it prints a one-time deprecation warning to stderr when first called.
util.deprecate(fn, msg[, code[, options]])- Added in Node.js v0.8.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$fn | Function | The function (or class) that is being deprecated. |
$msg | string | A warning message to display when the deprecated function is invoked. |
$codeoptional | string | A deprecation code, e.g. 'DEP0001'. Warnings are emitted only once per code across the process. |
$options.modifyPrototypeoptional | boolean= true | When false, the prototype of the object is not changed while emitting the deprecation warning. |
Return value
FunctionThe deprecated function, wrapped to emit a warning the first time it is called.
Errors
The wrapper throws instead of warning when the process runs with --throw-deprecation.
Changelog
- PHP v25.2.0, v24.12.0Added an options object with modifyPrototype, to conditionally leave the deprecated object's prototype alone.
- PHP v10.0.0Deprecation warnings are now emitted only once for each code, not once per wrapped function.
util.deprecate() wraps a function so that the first call emits a DeprecationWarning through the process 'warning' event, which Node prints to stderr. Every call, including the first, still runs the original function and returns its result — the wrapper changes nothing about behaviour.
When to reach for it
Use it in a library when you are removing an API in the next major version and want existing users to find out at runtime rather than from a changelog they will not read. Wrap the export at definition time and leave it in place for one major cycle.
What trips people up
- Once, not once per call. The warning fires on the first invocation only. If you supply a
code, it fires once per code for the whole process, so two wrapped functions sharing'DEP0001'produce a single warning between them. - It goes to stderr and is easily missed in a piped or redirected log.
- Users can change what it does.
--no-deprecationsilences it,--trace-deprecationadds a stack trace, and--throw-deprecationturns it into a thrown error — which is worth knowing before you deprecate something on a hot path. - Wrap once. Calling
deprecate()inside a function body creates a new wrapper each time and re-warns on every call.
Example
When to use it
- Warn users of a library that a function will be removed in the next major version.
- Mark a synchronous API as deprecated while steering callers to its async replacement.
- Fail CI on any use of a deprecated internal helper by running Node with --throw-deprecation.
Frequently asked questions
Why does util.deprecate only warn once?
By design — the wrapper emits its `DeprecationWarning` on the first invocation and then calls through silently. If you pass a `code`, deduplication is per code across the whole process (since v10.0.0), so several wrapped functions sharing one code produce a single warning between them.
Where does the deprecation warning get printed?
To stderr, via the process `'warning'` event. Redirecting only stdout hides it, which is why users often report never seeing one. Run with `--trace-deprecation` to add a stack trace showing the call site, or attach a `process.on('warning', ...)` listener to route it elsewhere.
How do I turn deprecation warnings into errors?
Run Node with `--throw-deprecation`; the wrapped function then throws instead of warning, which lets CI fail on any deprecated call. The opposites are `--no-deprecation`, which silences the warnings, and `--trace-deprecation`, which keeps them and adds a stack trace.
More examples
Deprecating a library export
Wrapping at module scope is the whole trick: the wrapper is created once, so the once-per-process guarantee holds. Namespacing the code (`MYLIB_DEP0001` rather than `DEP0001`) keeps your warnings distinct from Node's own numbered deprecations, and gives users something concrete to grep for or to filter on in a `'warning'` handler. Because `util.deprecate()` accepts a class as well as a function, `new LegacyClient()` warns on first construction.
const util = require('node:util');
function readConfigSync(path) {
return { path, loaded: true };
}
async function readConfig(path) {
return { path, loaded: true };
}
module.exports = {
readConfig,
// Wrap once, at module scope. Wrapping inside a function would create a
// fresh wrapper per call and warn every time.
readConfigSync: util.deprecate(
readConfigSync,
'readConfigSync() is deprecated and will be removed in v4. ' +
'Use await readConfig() instead.',
'MYLIB_DEP0001',
),
};
// Classes work the same way.
class LegacyClient {}
module.exports.LegacyClient = util.deprecate(
LegacyClient,
'LegacyClient is deprecated. Use Client instead.',
'MYLIB_DEP0002',
);What the user sees, and the flags they can use
The warning is written to stderr while the return value goes to stdout, so `node deprecated.js > out.log` hides the warning entirely — a common reason users report never seeing one. `--trace-deprecation` is what you tell someone to run when they know a warning fires but not from where. `--throw-deprecation` converts the warning into a thrown error, which teams enable in CI to fail a build on any deprecated call.
// deprecated.js
const util = require('node:util');
const legacy = util.deprecate(() => 'result', 'legacy() is deprecated', 'DEP9001');
console.log(legacy());
// $ node deprecated.js
// result
// (node:12345) [DEP9001] DeprecationWarning: legacy() is deprecated
// (Use `node --trace-deprecation ...` to show where the warning was created)
// $ node --no-deprecation deprecated.js
// result
// $ node --trace-deprecation deprecated.js
// result
// (node:12346) [DEP9001] DeprecationWarning: legacy() is deprecated
// at Object.<anonymous> (/app/deprecated.js:4:13)
// ...
// $ node --throw-deprecation deprecated.js
// .../deprecated.js:4
// DeprecationWarning: legacy() is deprecated
// ... process exits with code 1Handling warnings programmatically
Only three warnings arrive for six calls. `a()` and `b()` share the code `APP_DEP1`, so the second one is suppressed even though it is a different function — deduplication is keyed on the code, not the wrapper. `c()` and `d()` have no code, so each wrapper warns once on its own. Listening for the `'warning'` event is how a service turns these into metrics; note that adding a listener does not stop Node's default stderr printing unless you also remove the built-in listener.
const util = require('node:util');
const a = util.deprecate(() => 'a', 'a() is deprecated', 'APP_DEP1');
const b = util.deprecate(() => 'b', 'b() is deprecated', 'APP_DEP1'); // same code
const c = util.deprecate(() => 'c', 'c() is deprecated'); // no code
const d = util.deprecate(() => 'd', 'd() is deprecated'); // no code
const seen = [];
process.on('warning', (warning) => {
if (warning.name !== 'DeprecationWarning') return;
seen.push(warning.code ?? warning.message);
// Route it somewhere useful instead of stderr:
// telemetry.increment('deprecation', { code: warning.code });
});
a(); a(); b(); c(); c(); d();
process.on('exit', () => console.log(seen));
// [ 'APP_DEP1', 'c() is deprecated', 'd() is deprecated' ]
Discussion