readline.createInterface()
Creates an Interface that splits a readable stream into lines and optionally drives a prompt.
readline.createInterface(options)- Added in Node.js v0.1.98
Parameters
| Parameter | Type | Description |
|---|---|---|
$options | Object | Configuration for the interface. Only input is required. |
$options.input | stream.Readable | The readable stream to split into lines. |
$options.outputoptional | stream.Writable | Where prompts, echoed keystrokes and terminal control codes are written. Omit it to read silently. |
$options.completeroptional | Function | Tab-completion function; returns [matches, substringUsed] or calls a callback with it. |
$options.terminaloptional | boolean= checks isTTY on output | Whether to write ANSI/VT100 escape codes and echo input. |
$options.historyoptional | string[]= [] | Initial history lines. Only used when terminal is true. |
$options.historySizeoptional | number= 30 | Maximum retained history lines. 0 disables history. |
$options.removeHistoryDuplicatesoptional | boolean= false | Remove the older duplicate when a repeated line is added to history. |
$options.promptoptional | string= '> ' | The prompt string written by rl.prompt(). |
$options.crlfDelayoptional | number= 100 | Milliseconds within which \r followed by \n counts as one newline. Values below 100 are coerced to 100; Infinity always joins them. |
$options.escapeCodeTimeoutoptional | number= 500 | Milliseconds to wait for an ambiguous key sequence to complete. |
$options.tabSizeoptional | integer= 8 | Columns a tab occupies. Minimum 1. |
$options.signaloptional | AbortSignal | Aborting 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
readFileSyncand 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
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'.
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.
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.
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