Remove Control Characters from Text
Paste any text below and click Clean. Every invisible control character in the range U+0000–U+001F and the delete character U+007F is stripped out. Tab, newline, and carriage return are preserved by default so your document structure stays intact.
Hidden character report
Paste text to scan
Where control characters come from in the first place
The most common source is copy-pasted terminal output. When you select text from a terminal emulator and paste it into a word processor or database field, the ANSI color codes that make your shell prompt look colorful travel along for the ride. They start with ESC (U+001B, also written as \x1B or \e), which is a control character. The ESC byte is invisible in most editors, so the text looks fine until it hits a parser or a column with a character-class constraint and the whole insert fails.
Mainframe and legacy enterprise system exports are a second major source. IBM EBCDIC encoding uses a different set of control characters than ASCII, and the conversion libraries that translate EBCDIC to UTF-8 sometimes map unmappable control bytes to their ASCII equivalents rather than dropping them. A payroll export from a 1980s mainframe application can arrive with ETX (U+0003, end of text) and EOT (U+0004, end of transmission) characters sprinkled through the CSV because those bytes were used as record delimiters in the original format. Modern CSV parsers choke on them.
PDF text extraction is another reliable producer of control characters. PDF internally stores text as a stream of bytes that map to glyphs in a font. When you extract that stream to a plain-text string, some PDF readers emit form feed (U+000C) at page boundaries, vertical tab (U+000B) at column breaks, and occasionally raw null bytes (U+0000) where the font encoding had gaps. The extracted text looks perfect in a monospace viewer, but paste it into an API request body and the null byte will silently truncate the payload or cause the server to return a 400 with a cryptic message about unexpected token.
Webhook payloads and third-party API responses are the fourth category that surprises people. Some systems — particularly those built on older Java or PHP stacks — encode newlines as \r\n (CRLF) inside JSON string values rather than as the escape sequence \\r\\n. Others embed a BEL character (U+0007) in error messages because the original programmer was using a terminal that would ring when the error fired. None of this is intentional on the sending side; it is almost always a serialisation bug that has existed for years without anyone noticing because clients were lenient.
The full ASCII control character table and what each one did
Understanding what these characters originally meant helps you recognise them when they appear in unexpected places. NUL (U+0000) was the null character, used as a no-op filler on paper tape. SOH (U+0001, start of heading) and STX (U+0002, start of text) marked the beginning of a transmission block header and the data that followed it. ETX (U+0003) and EOT (U+0004) ended the text and the entire transmission.
ENQ (U+0005) was a query — "are you there?" — and ACK (U+0006) was the affirmative reply. BEL (U+0007) triggered an audible alert. BS (U+0008) moved the print head one position backward, enabling overstrike — printing a character on top of the previous one to create bold text or strikethrough. HT (U+0009) is the horizontal tab, still in use. LF (U+000A) is the line feed / newline, still in use. VT (U+000B) was a vertical tab, advancing the paper by a fixed number of lines.
FF (U+000C, form feed) advanced the paper to the top of the next page — the origin of the "page break" concept. CR (U+000D) is the carriage return, still in use on Windows. SO (U+000E) and SI (U+000F) shifted between character sets, analogous to pressing Shift on a keyboard but for an entire data stream. DC1 through DC4 (U+0011– U+0014) were device control signals; DC1 is still used as XON (resume transmission) and DC3 as XOFF (pause transmission) in software flow control. NAK (U+0015) was a negative acknowledgement. SYN (U+0016) was a synchronisation pulse. ETB (U+0017) ended a transmission block. CAN (U+0018) cancelled the current operation. EM (U+0019) indicated end of medium. SUB (U+001A) was used as a substitute character — in Windows CMD it still signals end of file when typed as Ctrl+Z. ESC (U+001B) introduced an escape sequence. FS, GS, RS, US (U+001C–U+001F) were file, group, record, and unit separators, the ancestor of modern CSV delimiters. DEL (U+007F) erased a character on paper tape by punching all 7 bits.
Of these 33 characters, the only ones that survive into modern text with legitimate purpose are HT (tab), LF (newline), and CR (carriage return). Everything else is either completely obsolete or meaningful only to a terminal emulator that is actively rendering the stream. In stored text, database values, or document content, they are always noise.
Real breakage scenarios and how control characters cause them
A null byte (U+0000) in a C string terminates it at that position. If your application passes text through any C library — libpq for PostgreSQL, the SQLite C API, OpenSSL for HTTPS connections — a null byte embedded in a longer string will silently truncate it. The first 10 characters of a 500-character document will reach the server; the rest will be lost without an error. This is not a theoretical risk: it is a category of bug that shows up in production when users paste content from Microsoft Word, which occasionally inserts null bytes in template fields.
JSON serialisation fails on unescaped control characters below U+0020. RFC 8259 section 7 explicitly states that all characters from U+0000 through U+001F must be escaped as \\uXXXX sequences inside JSON strings. JavaScript's JSON.stringify handles this correctly — it will emit \\u0000 for a null byte. But JSON.parse is strict: if you receive a payload that contains a raw control character (because the sender used a broken serialiser), it throws a SyntaxError and the entire payload is lost. Stripping the control characters before passing the string to JSON.parse recovers the rest of the payload at the cost of the offending characters.
XML is equally strict. The XML 1.0 specification prohibits character references to U+0000, U+0001–U+0008, U+000B, U+000C, U+000E–U+001F, and U+007F. An XML document containing a bare U+0007 (BEL) is not well-formed and must be rejected by a conforming parser. An XSLT pipeline that receives such a document will throw and abort transformation. The fix is always the same: sanitise the input before it enters the XML pipeline.
PostgreSQL VARCHAR and TEXT columns will store control characters without complaint, but they will cause headaches at the application layer. A BEL character in a product description will cause some terminals to beep when a developer runs a SELECT and prints the results. A VT character in a customer note will misalign a column-formatted report. More seriously, some web frameworks HTML-escape printable characters but pass control characters through raw, creating opportunities for client-side misbehaviour when the content is rendered in a browser.
Removing control characters in Python, JavaScript, and shell
In Python 3, the cleanest approach uses the unicodedata module combined with a character category check. Control characters are assigned the Unicode general category Cc. The one-liner is: import unicodedata; cleaned = ''.join(c for c in text if unicodedata.category(c) != 'Cc'). This removes all Cc characters globally, including those above U+007F in the Latin-1 supplement. If you only want to target the ASCII control range and preserve tabs and newlines: import re; cleaned = re.sub (r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text). The regex explicitly excludes \x09 (tab), \x0a (LF), and \x0d (CR).
In JavaScript/Node.js: const cleaned = text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''). The same character class excludes the three whitespace control characters you almost always want to keep. For TypeScript the same regex works without modification because TypeScript compiles to JavaScript and inherits its regex engine. If you are processing large volumes of text in a performance-sensitive context, a single-pass regex replacement is faster than filtering character by character, because the regex engine runs in native code while a character-by-character loop runs in interpreted JavaScript.
On the command line, tr is the standard tool. tr -d '\000-\010\013\014\016-\037\177' < input.txt > output.txt removes all control characters except tab, LF, and CR from a file. The octal notation is required because tr does not understand \x hex escapes on all platforms. For ANSI escape sequence stripping specifically, sed -e 's/\x1b\[[0-9;]*[mGKHF]//g' removes the most common terminal color and cursor-movement sequences while leaving other content intact.
In Go, the strings.Map function is idiomatic: cleaned := strings.Map(func(r rune) rune { if r < 0x20 && r != '\t' && r != '\n' && r != '\r' { return -1 }; if r == 0x7f { return -1 }; return r }, text). Returning -1 from the mapping function drops the rune. This is efficient because strings.Map makes a single pass and builds the output string as it goes, without intermediate allocations for each character.
Control characters versus whitespace: knowing what to keep
Not every invisible character is a problem, and the distinction between control characters and whitespace matters when you are deciding what to strip. Tab (U+0009), line feed (U+000A), and carriage return (U+000D) are technically in the ASCII control range, but they carry structural meaning in almost every text format. Strip them and you merge all paragraphs into one line, destroy indentation in code snippets, and break structured data formats like TSV. This tool preserves them by default.
Form feed (U+000C) is more ambiguous. In plain text it signals a page break, and some text editors render it as such. In a database string or API payload it is almost always noise. The same goes for vertical tab (U+000B). This tool removes both of them. If your pipeline uses form feeds as intentional page separators, you will need a custom script that preserves U+000C while removing the remaining control characters.
Unicode also defines whitespace characters above U+007F that are not control characters but can cause similar parsing problems: no-break space (U+00A0), en space (U+2002), em space (U+2003), thin space (U+2009), hair space (U+200A), narrow no-break space (U+202F), and medium mathematical space (U+205F). These are not removed by this tool because they are in the Unicode "space separator" (Zs) category, not the control character (Cc) category. If you need to normalize all exotic whitespace to regular space characters, use a Unicode-aware whitespace normalizer rather than a control character remover.
Zero-width characters — zero-width space (U+200B), zero-width non-joiner (U+200C), zero-width joiner (U+200D) — are also not removed by this tool. They are in the Unicode format character (Cf) category and live at code points well above U+001F. For those, use the dedicated zero-width character remover. If you need to strip every invisible character in one pass — control characters, zero-width characters, and exotic whitespace — you need to combine multiple character class filters or use a tool that targets Unicode general categories Cc, Cf, and Zs simultaneously.
Security implications of unsanitised control characters
Control characters are not just annoying — in specific contexts they create security vulnerabilities. The most well-known is terminal injection, sometimes called "log injection with ANSI codes." If an application logs user-supplied text directly to a terminal or a log file that is viewed with a terminal pager like less, an attacker can inject ANSI escape sequences to overwrite log entries, change terminal colors, move the cursor, or in some terminal emulators execute commands via title-bar injection. A user who submits a username like alice\x1b[2K\x1b[1Aroot gets their username cleared from the visible log and replaced with "root" in the line above.
HTTP header injection uses CR (U+000D) and LF (U+000A). HTTP/1.1 headers are terminated by CRLF sequences. If a user-supplied value is placed into an HTTP response header without validation, an attacker can inject additional headers or even an entirely new HTTP response body by embedding CRLF pairs. This class of vulnerability was common in PHP applications in the early 2000s and is still occasionally found in frameworks that do not sanitise header values. Most modern HTTP libraries reject CRLF in header values, but the safe practice is to strip control characters from any text before placing it in an HTTP header.
Email header injection follows the same pattern. SMTP headers are also CRLF-delimited, and an attacker who can inject a newline into a "From" or "Subject" field can add arbitrary headers including "Bcc" to redirect a copy of the email to their own address. Many contact forms on websites were vulnerable to this in the 2010s. Again, stripping control characters from form field values before constructing email headers is the standard mitigation.
For routine text cleaning — removing junk from a pasted API response, sanitising a CSV column before import, or preparing text for a database insert — the security concerns are secondary to the parsing correctness concerns. But they are worth knowing about so that stripping control characters becomes a default hygiene step in any pipeline that handles user input, not an afterthought applied only when a bug is reported. For related Unicode sanitisation, see the non-ASCII character remover if you need the strictest possible 7-bit clean output, or the special character remover if you want to keep letters and digits while removing punctuation and symbols.
Frequently Asked Questions
▸What exactly are control characters?
Control characters are the first 32 code points in ASCII (U+0000 through U+001F) plus the delete character at U+007F. They were designed for teletype and printer control in the 1960s — BEL (U+0007) rang a bell, ESC (U+001B) started terminal escape sequences, FF (U+000C) advanced a printer to the next page. Modern software has no use for most of them, but they still appear in text copied from terminals, PDFs, legacy mainframe exports, and some API responses. A single null byte (U+0000) is enough to truncate a C string, break a JSON parser, or cause a database to reject an insert.
▸Will this tool remove newlines and tabs?
By default, no. Tab (U+0009), line feed (U+000A), and carriage return (U+000D) are control characters by strict definition, but they carry structure in most documents. The default mode of this tool skips those three and removes everything else in the range U+0000–U+001F plus U+007F. If you specifically need to collapse whitespace or unify line endings, use the dedicated line-break or extra-space remover tools on this site instead.
▸How do I detect control characters in a text file before cleaning?
On Unix/macOS, run: cat -v yourfile.txt. Control characters appear as caret notation — ^A for U+0001, ^M for carriage return, ^[ for ESC. To count them: grep -cP '[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]' yourfile.txt. In Python: import re; matches = re.findall(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', text). In Node.js: text.match(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g).
▸Why does my JSON break when it contains a null byte?
The JSON specification (RFC 8259) does not permit unescaped control characters inside string values. A null byte U+0000 must be written as the escape sequence \u0000 if it appears inside a JSON string — but most text editors and copy-paste operations will embed the raw byte instead of the escape. JSON.parse in Node.js throws SyntaxError for raw control characters below U+0020 unless they are escaped. Removing the raw control characters before serialising to JSON, or before parsing a received payload, is the standard workaround.
▸What are ANSI escape sequences and does this tool remove them?
ANSI escape sequences start with ESC (U+001B) followed by '[' and a series of numbers and letters — for example ESC[31m sets red text color in a terminal. They are used by command-line tools like grep, ls, and npm to add color to output. When you copy terminal output and paste it into a document or database, these sequences appear as junk like \e[0;32m. This tool removes the ESC character (U+001B), which breaks the sequences. To fully strip the entire ESC[...m pattern rather than just the ESC byte, you need a regex like /\x1b\[[0-9;]*m/g in addition to control-character removal.
▸Is the delete character (U+007F, DEL) considered a control character?
Yes. U+007F was used in paper-tape systems to overwrite a previously punched character — you could physically scratch out a mistake. It sits just above the printable ASCII range (which ends at U+007E, the tilde ~). Like the low control characters, it has no typographic meaning in plain text. It is removed by this tool. In practice it most often appears in text copied from a terminal session where the user pressed the Delete key and the raw keystroke was captured rather than its editing effect.