readline.createInterface()

Creates an Interface that splits a readable stream into lines and optionally drives a prompt.

Signature
readline.createInterface(options)
  • Added in Node.js v0.1.98

Parameters

ParameterTypeDescription
$optionsObjectConfiguration for the interface. Only input is required.
$options.inputstream.ReadableThe readable stream to split into lines.
$options.outputoptionalstream.WritableWhere prompts, echoed keystrokes and terminal control codes are written. Omit it to read silently.
$options.completeroptionalFunctionTab-completion function; returns [matches, substringUsed] or calls a callback with it.
$options.terminaloptionalboolean= checks isTTY on outputWhether to write ANSI/VT100 escape codes and echo input.
$options.historyoptionalstring[]= []Initial history lines. Only used when terminal is true.
$options.historySizeoptionalnumber= 30Maximum retained history lines. 0 disables history.
$options.removeHistoryDuplicatesoptionalboolean= falseRemove the older duplicate when a repeated line is added to history.
$options.promptoptionalstring= '> 'The prompt string written by rl.prompt().
$options.crlfDelayoptionalnumber= 100Milliseconds within which \r followed by \n counts as one newline. Values below 100 are coerced to 100; Infinity always joins them.
$options.escapeCodeTimeoutoptionalnumber= 500Milliseconds to wait for an ambiguous key sequence to complete.
$options.tabSizeoptionalinteger= 8Columns a tab occupies. Minimum 1.
$options.signaloptionalAbortSignalAborting it closes the interface.

Return value

readline.InterfaceA new Interface bound to the given input (and output) streams. It begins consuming input as soon as it is created.

Errors

Throws ERR_INVALID_ARG_VALUE if completer is not a function, and TypeError if input is missing or is not a readable stream.

Changelog

  • PHP v15.14.0, v14.18.0The signal option is supported.
  • PHP v15.8.0, v14.18.0The history option is supported.
  • PHP v13.9.0The tabSize option is supported.
  • PHP v8.3.0, v6.11.4The maximum limit on crlfDelay was removed, so Infinity is accepted.
  • PHP v6.6.0The crlfDelay option is supported.
  • PHP v6.3.0The prompt option is supported.
  • PHP v6.0.0historySize can be 0.

createInterface() turns a byte stream into a line stream. It attaches to options.input immediately, buffers partial data, and emits a 'line' event every time it sees \n, \r or \r\n — including a final line that has no trailing newline.

When to reach for it

  • Reading a text file too large to readFileSync and split.
  • Building a REPL or a wizard that prompts on process.stdin.
  • Consuming line-delimited output — NDJSON, logs, CSV — from any readable stream.

What trips people up

Consumption starts at construction, not when you attach a listener. If you await something between createInterface() and your first rl.on('line'), early lines are gone. Attach the listener, or start the for await loop, in the same tick.

The terminal option is inferred from output.isTTY. Pass a file stream as output and you silently get a non-terminal interface with no echo and no history — usually what you want, but surprising if you expected a prompt.

For files, set crlfDelay: Infinity. The 100 ms default is a timing heuristic; on a fast local read a Windows \r\n that straddles two chunks can still be split into two lines.

Example

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

When to use it

  • Stream a multi-gigabyte log file one line at a time without loading it into memory
  • Prompt for input on process.stdin in a CLI installer
  • Parse NDJSON records arriving over a readable stream
  • Read a fixture file line by line inside a test

Frequently asked questions

How do I read a file line by line in Node.js?

Pass fs.createReadStream(path) as the input option to readline.createInterface() and iterate the interface with for await...of. This keeps memory flat regardless of file size, unlike reading the whole file and calling split('\n').

Why does readline.createInterface() miss the first lines of my file?

The interface starts consuming input as soon as it is constructed, so any line emitted before your listener is attached is lost. Attach the 'line' handler or enter the for await loop synchronously after createInterface(), not after an await.

What does crlfDelay: Infinity do?

It makes a carriage return always pair with a following line feed as a single newline, no matter how much time passes between the two chunks. Without it Node uses a 100 ms window, which can split a Windows CRLF into two lines when the chunk boundary falls between them.

More examples

A prompt loop on stdin

Because output is a TTY, terminal defaults to true: keystrokes are echoed, arrow keys walk the 100-entry history, and rl.prompt() redraws 'app> '. Ctrl+D or Ctrl+C ends the stream and fires 'close'.

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

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: 'app> ',
  historySize: 100,
});

rl.prompt();

rl.on('line', (line) => {
  switch (line.trim()) {
    case 'help':
      console.log('commands: help, quit');
      break;
    case 'quit':
      rl.close();
      return;
    default:
      console.log(`unknown command: ${line.trim()}`);
  }
  rl.prompt();
});

rl.on('close', () => {
  console.log('bye');
  process.exit(0);
});

Tab completion with a completer

Typing 'b' and pressing Tab returns ['build', 'bundle'] as hits, so readline prints both and completes the shared prefix. Returning an empty hits array falls back to listing every command.

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

const commands = ['build', 'bundle', 'deploy', 'destroy'];

function completer(line) {
  const hits = commands.filter((c) => c.startsWith(line));
  return [hits.length ? hits : commands, line];
}

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  completer,
});

rl.question('command: ', (answer) => {
  console.log(`running ${answer}`);
  rl.close();
});

Closing the interface with an AbortSignal

Aborting the signal closes the interface without touching the file stream directly. The 'close' handler runs once readline has relinquished the stream; a few already-buffered lines may still be delivered before it fires.

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

const ac = new AbortController();

const rl = readline.createInterface({
  input: fs.createReadStream('huge.log'),
  crlfDelay: Infinity,
  signal: ac.signal,
});

let matches = 0;
rl.on('line', (line) => {
  if (line.includes('FATAL')) {
    matches++;
    if (matches === 10) ac.abort();
  }
});

rl.on('close', () => console.log('stopped after', matches, 'matches'));

Discussion

  • Be the first to comment on this lesson.