util.stripVTControlCharacters()

Removes ANSI escape sequences from a string, leaving only the visible text.

Signature
util.stripVTControlCharacters(str)
  • Added in Node.js v16.11.0

Parameters

ParameterTypeDescription
$strstringThe input string, possibly containing ANSI escape sequences.

Return value

string`str` with any ANSI escape codes removed.

Errors

Throws ERR_INVALID_ARG_TYPE if `str` is not a string.

util.stripVTControlCharacters() is the inverse of util.styleText(): it removes the VT100/ANSI escape sequences that terminals interpret as colour, cursor movement and screen erasure, and returns just the characters a user would see.

When to reach for it

  • Writing coloured output to a file. Log files, JSON reports and test artefacts should hold text, not [31m noise.
  • Measuring width. A styled string's .length counts the escape bytes, so table and progress-bar alignment breaks. Strip first, then measure.
  • Asserting in tests. Capture a CLI's stdout, strip it, and compare against plain text so the assertion holds whether or not colour was enabled.
  • Sanitising untrusted text. Escape sequences in a string you print can move the cursor, clear the screen or overwrite earlier output. Strip anything that came from a network response or a filename before echoing it.

What it does not do

It removes escape sequences, not all control characters — a bare \r, \b or NUL byte passes straight through, so it is not a general-purpose sanitiser. It also does not measure display width: after stripping, wide CJK characters and emoji still occupy more than one column.

Example

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

When to use it

  • Write plain text to a log file while still colouring the same messages on the terminal.
  • Compute the printable width of styled cells so a CLI table stays aligned.
  • Assert on a CLI's captured stdout in a test without the assertion depending on colour support.

Frequently asked questions

How do I remove ANSI colour codes from a string in Node?

Call `util.stripVTControlCharacters(str)`. It is built in since v16.11.0 and removes colour, cursor-movement and erase sequences, returning just the visible characters — no `strip-ansi` dependency and no hand-written regex needed.

Why is the length of my coloured string wrong?

Because the ANSI escape bytes are ordinary characters in the string and `.length` counts them, so a four-character coloured word can report 14. Measure `util.stripVTControlCharacters(s).length` instead when padding or aligning terminal output.

Does stripVTControlCharacters remove all control characters?

No — it removes ANSI/VT escape sequences only. Bare control characters such as `\r`, `\b` and NUL pass through unchanged, so add a `replace(/[-]/g, '')` pass if you need full sanitisation of untrusted text.

More examples

Colour on the terminal, plain text in the log file

Stripping at the sink rather than the source is the pattern worth copying: the formatting decision is made once in `report()`, and each destination gets the form it can handle. Without the strip, `build.log` would be full of `` sequences that break `grep` patterns and render as garbage in any viewer that does not interpret ANSI.

Example · javascript
const util = require('node:util');
const fs = require('node:fs');

const logFile = fs.createWriteStream('build.log', { flags: 'a' });

function report(style, message) {
  const line = util.styleText(style, message);
  // The terminal gets whatever styleText decided was appropriate.
  console.log(line);
  // The file always gets plain text.
  logFile.write(`${util.stripVTControlCharacters(line)}\n`);
}

report('green', 'compiled 42 modules');
report(['bold', 'red'], 'failed: src/index.js');
report('dim', 'finished in 1.2s');

logFile.end();

// build.log contains:
// compiled 42 modules
// failed: src/index.js
// finished in 1.2s

Aligning a table of styled cells

`util.styleText('green', 'PASS').length` is 14 on a TTY, not 4, because the escape bytes are real characters in the string. Padding against that number pushes coloured cells out of alignment while uncoloured ones look fine — which is why the bug only shows up on a terminal and never in CI. The `width()` helper strips before measuring, so every column lines up regardless of whether colour was applied.

Example · javascript
const util = require('node:util');

const rows = [
  [util.styleText('green', 'PASS'), 'parser', '12ms'],
  [util.styleText('red', 'FAIL'), 'resolver', '340ms'],
  [util.styleText('yellow', 'SKIP'), 'network-integration', '-'],
];

// The printable width, not the raw string length.
const width = (s) => util.stripVTControlCharacters(s).length;

const columns = [0, 1, 2].map((i) => Math.max(...rows.map((r) => width(r[i]))));

for (const row of rows) {
  const line = row
    .map((cell, i) => cell + ' '.repeat(columns[i] - width(cell)))
    .join('  ');
  console.log(line);
}

// PASS  parser               12ms
// FAIL  resolver             340ms
// SKIP  network-integration  -

Stripping untrusted text before printing it

Escape sequences embedded in data are a real terminal-injection vector: `` erases the current line and returns the cursor to column 1, so an attacker-supplied string can overwrite text your program already printed. `stripVTControlCharacters()` neutralises that, but the second call shows its limit — `\r` and NUL survive, because they are control characters rather than escape sequences. The `sanitize()` helper covers both.

Example · javascript
const util = require('node:util');

// Text from a network response, a filename, or a git branch name may
// contain escape sequences that manipulate the terminal.
const ESC = String.fromCharCode(27);
const hostile = `harmless${ESC}[2K${ESC}[1G  ATTACKER CONTROLLED LINE`;

// Printing it directly lets it erase the line and rewrite from column 1.
// console.log(`branch: ${hostile}`);

// Strip first.
console.log(`branch: ${util.stripVTControlCharacters(hostile)}`);
// branch: harmless  ATTACKER CONTROLLED LINE

// It removes escape sequences, not every control character:
console.log(JSON.stringify(util.stripVTControlCharacters('a\rbc')));
// "a\rbc"

// So combine it with a control-character filter when you need one.
const sanitize = (s) =>
  util.stripVTControlCharacters(s).replace(/[--]/g, '');
console.log(JSON.stringify(sanitize('a\rbc'))); // "abc"

Discussion

  • Be the first to comment on this lesson.