Invisible Character Detector
All processing happens in your browser. No text is sent to any server.
About this tool
Reading the revealed output
The Revealed panel is the fastest way to answer the question that actually matters: where exactly is the character. Each hidden character is swapped for a short bracketed token that names it, keeping the surrounding text intact so the position is obvious at a glance. [ZWS] is a zero-width space, [ZWJ] and [ZWNJ] are the joiner and non-joiner, [NBSP] is a non-breaking space, [SHY] is a soft hyphen, [BOM] is a byte order mark, [LRM] and [RLM] are the left-to-right and right-to-left marks, and the C0 control characters use their traditional abbreviations such as [NUL], [ESC], and [VT]. A code point with no conventional abbreviation falls back to its U+ notation. Line and paragraph separators show their token and still break the line, so the shape of the text you are inspecting is preserved rather than collapsed into one run. Ordinary tabs, newlines, and spaces are left exactly as they are, because tokenising them would drown the findings you came for in noise.
Fixing hidden characters in a codebase
Stripping the characters from one string fixes one symptom. To stop them coming back, deal with the source. Configure your editor to render invisible characters permanently: VS Code has editor.renderControlCharacters and editor.unicodeHighlight.invisibleCharacters, JetBrains IDEs have a Show Whitespace setting plus a dedicated inspection for bidirectional and invisible characters, and Vim has the list and listchars options. Add a pre-commit hook or a CI grep that fails on zero-width and bidirectional code points in source files, which is also a defence against the Trojan Source class of attack where bidirectional overrides make reviewed code execute differently from how it reads. Save files as UTF-8 without a byte order mark and enforce it in .editorconfig. Normalise text at trust boundaries, stripping formatting characters as data arrives from a form, an upload, or a third-party API rather than discovering them three systems downstream. When comparing user-supplied strings, apply Unicode normalisation and strip default-ignorable code points before the comparison rather than after a bug report.
Related tools: Unicode Text Inspector for a full character-by-character breakdown with blocks and UTF-8 bytes, Code Diff to compare two versions of a string, JSON Formatter to validate a document once it is clean.
Frequently Asked Questions
How do I find invisible characters in text?
Paste the text into the input panel on the left. Every hidden character is detected the moment you paste, and two things happen at once. The Revealed panel on the right shows your text with each hidden character replaced by a visible bracketed token, so [ZWS] marks a zero-width space, [BOM] marks a byte order mark, [NBSP] marks a non-breaking space, and [NUL] marks a null byte. Because the revealed text is plain text, you can copy it straight into a bug report, a commit message, or a chat thread and the person reading it will see exactly what you saw. Below that, the Findings table lists every distinct hidden code point once, with its official Unicode name, its code point in U+ notation, how many times it occurs, and the character offsets where it occurs. Click any row to jump the cursor to the first occurrence in the editor. When you just want the problem gone, the Clean and Copy button strips every invisible and control character and puts the cleaned text on your clipboard.
Which hidden characters does this detect?
The detector reports three families of character. Invisible formatting characters occupy no visual width but change how text is stored and compared: zero-width space (U+200B), zero-width joiner (U+200D), zero-width non-joiner (U+200C), word joiner (U+2060), non-breaking space (U+00A0), soft hyphen (U+00AD), byte order mark (U+FEFF), the bidirectional marks and overrides (U+200E, U+200F, U+202A through U+202E, U+2066 through U+2069), and the line and paragraph separators (U+2028, U+2029). Control characters cover the full C0 range from U+0000 to U+001F, the delete character U+007F, and the C1 range from U+0080 to U+009F, which are the non-printable characters that break JSON parsers, CSV importers, and shell scripts. Homoglyphs are the third family: visible characters such as Cyrillic а (U+0430) that are indistinguishable from an ASCII letter but are a different code point entirely. Ordinary tab, newline, carriage return, and space are treated as normal text and are never reported, because flagging them would bury the real findings.
Where do hidden characters come from?
Hidden characters are almost always inherited rather than typed. Copying from a web page brings zero-width spaces that content management systems insert as soft line-break hints. Copying from a PDF brings non-breaking spaces and soft hyphens left over from the typesetting. Word processors and rich text editors substitute non-breaking spaces for regular spaces to prevent awkward line wrapping. Files saved by Windows editors such as Notepad begin with a byte order mark, which is invisible in every editor but is a real byte that a shell script interpreter or a JSON parser will choke on. Slack, Teams, and Discord add directional formatting characters around mentions and emoji. Spreadsheet exports carry control characters from cells that once held pasted content. Machine translation and localisation pipelines add bidirectional marks around embedded Latin text. In each case the text looks correct in every editor, which is why the bug is reported as impossible before anyone thinks to check the bytes.
What breaks when text contains non-printable characters?
The failures are unusually hard to diagnose because the evidence is invisible. String equality returns false for two strings that look identical on screen, so dictionary lookups miss, cache keys diverge, and deduplication silently produces duplicates. Length checks and column limits are exceeded by text that visibly fits. Regular expressions stop matching because a zero-width character has been inserted between two characters the pattern expected to be adjacent. JSON and YAML parsers reject a document with a syntax error pointing at a line that is plainly valid, because a byte order mark sits before the opening brace or a control character sits inside a string. Shell scripts fail with a command not found error naming a command that is spelled correctly. Compilers report an undefined variable on a line where the variable is clearly defined, because the two spellings differ by one lookalike code point. Database unique constraints allow rows that should collide. Email addresses and usernames fail validation for no visible reason.