util.deprecate()

Wraps a function so it prints a one-time deprecation warning to stderr when first called.

Signature
util.deprecate(fn, msg[, code[, options]])
  • Added in Node.js v0.8.0

Parameters

ParameterTypeDescription
$fnFunctionThe function (or class) that is being deprecated.
$msgstringA warning message to display when the deprecated function is invoked.
$codeoptionalstringA deprecation code, e.g. 'DEP0001'. Warnings are emitted only once per code across the process.
$options.modifyPrototypeoptionalboolean= trueWhen 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-deprecation silences it, --trace-deprecation adds a stack trace, and --throw-deprecation turns 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

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

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.

Example · javascript
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.

Example · javascript
// 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 1

Handling 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.

Example · javascript
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

  • Be the first to comment on this lesson.