To split a string by new line in TypeScript, call split(/\r?\n/). The regular expression matches both Unix line breaks (\n) and Windows line breaks (\r\n), and you get back a string[] with one entry per line:
const lines: string[] = text.split(/\r?\n/);
Nothing here is made up: tsc compiled each file in strict mode (TypeScript 7.0.2 and Node.js v22.22.2), Node ran the output, and I copied what the console printed.
You’ll also see why split("
") falls short, how you drop blank lines, how you split a file, and which compiler errors you may hit.
Split a string by new line with split(/\r?\n/)
Here’s text with Windows line endings, like a file saved in Notepad. Compare a plain "
" split with the regex:
const fromWindows: string = "Austin, TX\r\nDenver, CO\r\nSeattle, WA";
const naive = fromWindows.split("\n");
console.log("split(\"\\n\"):", naive);
const lines: string[] = fromWindows.split(/\r?\n/);
console.log("split(/\\r?\\n/):", lines);
console.log("naive[0] is clean:", naive[0] === "Austin, TX");
console.log("lines[0] is clean:", lines[0] === "Austin, TX");
Output:
split("\n"): [ 'Austin, TX\r', 'Denver, CO\r', 'Seattle, WA' ]
split(/\r?\n/): [ 'Austin, TX', 'Denver, CO', 'Seattle, WA' ]
naive[0] is clean: false
lines[0] is clean: true

"\n", every line but the last keeps a hidden \r; the regex gives clean values.Those leftover
characters don’t show when you print one line, yet === comparisons fail. For other separators, such as commas, see converting a string to an array in TypeScript.
Why does split(“\n”) leave \r at the end of each line?
Windows ends lines with carriage return plus line feed (
); Linux and macOS use only
. Splitting on
leaves the
behind.
In /\r?\n/, the ? makes the \r optional, so one pattern covers both. Very old Mac files used a lone \r; if you might see those, use /\r\n|\r|\n/:
const mixed: string = "Line 1\nLine 2\r\nLine 3\rLine 4";
console.log(mixed.split(/\r?\n/));
console.log(mixed.split(/\r\n|\r|\n/));
Output:
[ 'Line 1', 'Line 2', 'Line 3\rLine 4' ]
[ 'Line 1', 'Line 2', 'Line 3', 'Line 4' ]
Line breaks typed inside a template literal are always stored as
, as I show in creating a multiline string in TypeScript, so this mostly matters for outside text.
Split by new line and remove empty lines
Text pasted into a <textarea> often has blank lines, stray spaces and a trailing newline:
const pasted: string = " Chicago, IL\n\nPhoenix, AZ \r\n\r\nBoston, MA\n";
console.log("raw: ", pasted.split(/\r?\n/));
console.log("collapsed:", pasted.split(/[\r\n]+/));
const clean = pasted
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line !== "");
console.log("clean: ", clean);
Output:
raw: [ ' Chicago, IL', '', 'Phoenix, AZ ', '', 'Boston, MA', '' ]
collapsed: [ ' Chicago, IL', 'Phoenix, AZ ', 'Boston, MA', '' ]
clean: [ 'Chicago, IL', 'Phoenix, AZ', 'Boston, MA' ]

/[\r\n]+/ still leaves spaces and a trailing "", and trim plus filter cleans it.A trailing newline always leaves a final empty string. I’d go with trim plus filter; more patterns are in filtering arrays in TypeScript.
Turn each line into a typed object
Splitting is usually step one. For a small CSV report, peel off the header, then split each row on commas:
interface Order {
city: string;
state: string;
total: number;
}
const report: string = `city,state,total
Austin,TX,1249.5
Denver,CO,980.25
Seattle,WA,1520
`;
const [header, ...rows] = report.trim().split(/\r?\n/);
console.log("columns:", header.split(","));
const orders: Order[] = rows.map((row) => {
const [city, state, total] = row.split(",");
return { city, state, total: Number(total) };
});
for (const o of orders) {
console.log(`${o.city}, ${o.state}: $${o.total.toFixed(2)}`);
}
Output:
columns: [ 'city', 'state', 'total' ]
Austin, TX: $1249.50
Denver, CO: $980.25
Seattle, WA: $1520.00
The Order interface catches typos like o.totl at compile time. For tougher number parsing, see converting a string to a double in TypeScript.
Split a text file by new line in Node.js
For a small file, use readFileSync and split. For a big log, stream it with readline instead:
import { createReadStream, readFileSync, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
writeFileSync("stores.txt", "Miami, FL\r\nPortland, OR\r\nNashville, TN\r\n");
// Small file: read it all, then split
const lines = readFileSync("stores.txt", "utf8").split(/\r?\n/).filter(Boolean);
console.log(lines.length, "lines:", lines);
// Large file: stream it one line at a time
async function main(): Promise<void> {
const rl = createInterface({ input: createReadStream("stores.txt"), crlfDelay: Infinity });
let n = 0;
for await (const line of rl) {
console.log(`readline ${++n}:`, JSON.stringify(line));
}
}
main();
Output:
3 lines: [ 'Miami, FL', 'Portland, OR', 'Nashville, TN' ]
readline 1: "Miami, FL"
readline 2: "Portland, OR"
readline 3: "Nashville, TN"

crlfDelay: Infinity treats \r\n as one break.TypeScript 7 doesn’t load @types/node automatically, so I compiled with --types node. Once you have the array, any loop from looping through arrays in TypeScript works.
Fix TypeScript errors after splitting a string into lines
Two common mistakes: storing the result in a string, and using a line unchecked when noUncheckedIndexedAccess is on:
const notes: string = "Call Dallas office\nSend Q4 invoice";
const lines: string = notes.split(/\r?\n/);
const first = notes.split(/\r?\n/)[0];
console.log(first.toUpperCase());
Output:
nl_errors.ts(3,7): error TS2322: Type 'string[]' is not assignable to type 'string'.
nl_errors.ts(6,13): error TS18048: 'first' is possibly 'undefined'.

split() always returns string[], and with noUncheckedIndexedAccess each index is string | undefined.Type the variable as string[], and check a line with if (first !== undefined) before calling methods on it.
Need just the final line? You can use lines.at(-1) with optional chaining; see getting the last element after a split in TypeScript.
Ways to split a string by new line compared
| Pattern | Handles | Use it when |
|---|---|---|
split("\n") | Unix line breaks only | You created the text yourself in code |
split(/\r?\n/) | Windows and Unix | Most input: files, uploads, APIs |
split(/\r\n|\r|\n/) | Windows, Unix, old Mac | Unknown or legacy sources |
split(/[\r\n]+/) | Any break, blank lines merged | Blank lines don’t matter |
readline + crlfDelay | Large files, streamed | Files too big to load at once |
These guides pick up where this one stops:
- Split an array into chunks in TypeScript
- Compare strings in TypeScript
- Map an array of objects in TypeScript
- Work with key-value pairs in TypeScript
Frequently asked questions
How do I split a string by new line in TypeScript?
Use text.split(/\r?\n/). It returns a string[] and handles both \n and \r\n line endings.
Why is the last element of my split an empty string?
The text ends with a newline, so there’s an empty piece after it. Call trim() before splitting or add .filter(Boolean).
How do I split by new line and skip blank lines?
Chain .map((line) => line.trim()).filter((line) => line !== "") after the split, or split on /[\r\n]+/ if spaces don’t matter.
Is there a splitlines() method in TypeScript?
No. Strings have no built-in splitlines() like Python; split(/\r?\n/) is the standard equivalent.
How do I join the lines back into one string?
Call lines.join("\n"), or join("\r\n") if the output needs Windows line endings.
Where is String.prototype.split() documented?
See String.prototype.split() on MDN.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP