Invisible Character Detector
Paste text to see every invisible character and every visible lookalike, each with its position, Unicode name and codepoint, then remove them one at a time or all at once. It also finds text hidden by markup and runs of zero-width characters that look like an encoded payload — the parts other tools skip. Everything runs in your browser.
What was found
| Remove | Position | Codepoint | Name | Type | Why it matters | Action |
|---|
Hidden content
Present in the text but never shown to a reader.
| Position | Type | Content | Why it matters | Action |
|---|
Before and after
- Words
- —
- Characters
- —
- Invisible removed
- —
- Lookalikes fixed
- —
- Comments removed
- —
- Payloads found
- —
Runs in your browser. Nothing uploaded.
How to use it
- Paste your text. The scan runs as you type.
- Read the table. Every hidden character and every lookalike is listed with its exact position, Unicode codepoint and official name.
- Choose what to remove. Each row has its own checkbox. Characters that are doing real work are highlighted and left unticked, with the reason shown.
- Copy the cleaned text. The counter tells you exactly how many characters were removed or replaced.
What an invisible character detector looks for
Unicode contains characters with no visual form. Some control line breaking, some join glyphs together, some mark text direction. They’re all legitimate, and they all cause trouble when they end up somewhere unexpected.
The one you’ll meet most is the zero-width space (U+200B). It occupies no width and looks like nothing at all. Paste a product code containing one into a database lookup and the query returns nothing, while the value on screen looks perfectly correct. That’s the whole problem in a sentence: the text looks right and behaves wrong.
Others show up constantly. The byte order mark (U+FEFF) prefixes files written by some editors and breaks the first column of a CSV or the opening brace of a JSON file. Non-breaking spaces (U+00A0) come from word processors and web pages and fail every trim() and every == " " comparison. Soft hyphens (U+00AD) mark optional break points and survive copy and paste invisibly.
Then there are bidirectional controls, which override text direction. Those matter for security: they can make a filename or a line of source code display in an order different from how it’s stored, which is the basis of the Trojan Source attack published in 2021.
Homoglyphs: the visible problem
Invisible characters are only half of it, and the other half gets much less attention.
A homoglyph is a character that looks like another but isn’t. Cyrillic а (U+0430) and Latin a (U+0061) are identical in most fonts. So are Greek ο and Latin o, Cyrillic е and Latin e, Greek Ρ and Latin P.
You can see these perfectly well. That’s exactly why they’re worse. An invisible character at least hints that something is odd when your cursor takes two presses to cross it. A homoglyph offers no signal at all.
They cause three distinct kinds of failure:
- Lookups that should match, don’t. A username, SKU or email with one Cyrillic letter is a completely different string to every database in the world.
- Code that looks correct, isn’t. A variable named with a Greek omicron is not the variable you think it is. It passes review because it reads correctly.
- Phishing. Lookalike domains have used this for years.
раypal.comwith Cyrillic letters is notpaypal.com, and in a browser address bar the two can be indistinguishable.
The other tools in this space handle invisible characters and stop there. This one flags homoglyphs too, tells you which ASCII character each is imitating, and can replace them with the real thing.
Typographic substitutions get the same treatment: smart quotes, en and em dashes, and fullwidth punctuation. Those are visible and often intentional in prose — but in code, a config file or a CSV they’re usually paste damage. Large language models produce them constantly, which is a common route into a document.
Hidden markup: text that is there but never shown
An invisible character is one problem. Text that is perfectly ordinary but hidden by the markup around it is the same problem with a different mechanism — and it’s the one people miss, because nothing about the characters themselves is unusual.
Paste HTML and the tool scans for content that a parser reads but a reader never sees:
| Pattern | What it does |
|---|---|
display: none |
Removes the element from the page entirely |
visibility: hidden |
Keeps the space, paints nothing |
opacity: 0 |
Renders the element fully transparent |
font-size: 0 |
Text occupies no height |
text-indent: -9999px |
Pushes the text off the canvas |
left / top: -9999px |
Positions it outside the viewport |
clip / clip-path |
Crops the element to nothing |
<!-- … --> |
HTML comments, which never render but travel with the file |
Why it matters depends on who you are. If you’re reviewing content someone sent you, hidden markup is where instructions to an AI, keyword stuffing, or an old draft nobody meant to ship tend to live. If you’re auditing your own pages, an HTML comment is a surprisingly common place for a staging URL or an internal note to escape into production.
The tool reports the hidden text itself, not just the fact that something is hidden. You can read what was in there.
Encoded payloads, and why a run is different from a stray
One zero-width space is an artifact. Four in a row is a message.
Zero-width steganography works by treating invisible characters as bits — a zero-width space for 0, a zero-width non-joiner for 1, say — and hiding a string inside ordinary-looking text. The result survives copy and paste, most editors, and every visual inspection, because there is genuinely nothing to see.
Reporting those the same way as a stray byte order mark would bury the signal in noise, so the tool separates them. A run of four or more zero-width characters is reported as one finding rather than as eight individual artifacts, and it tells you how many distinct symbols the run uses and roughly how many bits that could carry.
The symbol count is the part worth reading. A run alternating between two or more characters can encode data. A run of the same character repeated is still reported, but with an estimate of zero bits — repetition alone can’t encode anything, so it’s far more likely to be a formatting artifact than a message.
This is worth knowing if you handle text from outside your organisation. A payload can carry a tracking marker identifying who leaked a document, or instructions aimed at a language model reading the text later.
Before and after
Every removal shows you both versions. That matters more than it sounds — the whole difficulty with invisible characters is that the before and after look identical, so “trust me, I removed something” is not a satisfying answer.
The comparison shows exactly which positions changed and how many characters went, so you can confirm the tool did what you expected before you paste the result anywhere.
When you should not remove these
Stripping every invisible character is the obvious approach and it corrupts valid text. This tool refuses to do it silently.
Zero-width joiners build emoji. The family emoji 👨👩👧 is three separate people characters joined by two U+200D joiners. Remove them and you get three separate people. The joiner isn’t noise, it’s the whole glyph.
Zero-width non-joiners are grammar. In Persian, Arabic and several Indic scripts, U+200C prevents letters from joining where they shouldn’t. Removing it changes the word — sometimes into a different word. This isn’t formatting, it’s spelling.
So the tool checks context. A joiner sitting between two emoji, or adjacent to Arabic or Indic text, is marked unsafe, left unticked, and shown with an explanation. A zero-width space sitting between two Latin letters is marked safe. You can override either decision, but you’ll be told what you’re overriding.
Non-breaking spaces get replaced with ordinary spaces rather than deleted, because deleting them joins two words together.
Character reference
| Character | Codepoint | Visible? | Usually safe to remove? | Where it comes from |
|---|---|---|---|---|
| Zero Width Space | U+200B | No | Yes | Web pages, line-break control |
| Zero Width Non-Joiner | U+200C | No | No — required in some scripts | Persian, Arabic, Indic text |
| Zero Width Joiner | U+200D | No | No — builds emoji | Emoji sequences, Indic text |
| Byte Order Mark | U+FEFF | No | Yes | File encoding headers |
| Non-Breaking Space | U+00A0 | Looks like a space | Replace with a space | Word processors, HTML |
| Soft Hyphen | U+00AD | No | Yes | Justified text, PDFs |
| Right-To-Left Override | U+202E | No | Yes | Rarely legitimate; a security concern |
| Cyrillic Small A | U+0430 | Yes | Replace with a |
Mixed-script text, phishing |
| Right Single Quotation Mark | U+2019 | Yes | Replace with ' |
Word processors, LLM output |
| En Dash | U+2013 | Yes | Replace with - |
Typographic text, LLM output |
Removing them in Excel
Excel is where a lot of people hit this, usually after importing a CSV or pasting from a web page, and Excel’s obvious answer doesn’t work.
CLEAN() does not do what you need. It removes only the first 32 ASCII control characters. Zero-width spaces, non-breaking spaces and every other Unicode character listed above pass straight through it.
Use SUBSTITUTE with the specific codepoint instead:
- Zero-width space:
=SUBSTITUTE(A1, UNICHAR(8203), "") - Non-breaking space:
=SUBSTITUTE(A1, UNICHAR(160), " ") - Byte order mark:
=SUBSTITUTE(A1, UNICHAR(65279), "")
Nest them to handle several at once: =SUBSTITUTE(SUBSTITUTE(A1, UNICHAR(8203), ""), UNICHAR(160), " ").
To find out what you’re dealing with first, =UNICODE(MID(A1, n, 1)) gives the codepoint of the nth character. Tedious, but it settles the argument. Pasting the cell here is faster.
Finding them in VS Code
VS Code highlights ambiguous and invisible characters by default through its editor.unicodeHighlight settings. If yours doesn’t, check that editor.unicodeHighlight.invisibleCharacters and ambiguousCharacters are enabled — they’re worth leaving on permanently.
To find them by hand, open Find, turn on the regular-expression toggle, and search for the invisible ranges directly:
[-]
For homoglyphs, search the Cyrillic block: [Ѐ-ӿ]. In a codebase that should be entirely ASCII, any hit is worth looking at.
Removing them in code
| Language | Strip invisible characters |
|---|---|
| JavaScript | text.replace(/[-]/g, '') |
| Python | re.sub(r'[-]', '', text) |
| C# | Regex.Replace(text, @"[-]", "") |
| PHP | preg_replace('/[x{200B}-x{200D}x{FEFF}]/u', '', $text) |
Two cautions. These patterns include U+200C and U+200D, so they’ll break emoji and Persian text — narrow the range if your input might contain either. And Unicode normalisation (NFKC) is often suggested as a blanket fix; it does collapse many lookalikes, but it also rewrites ligatures, fullwidth forms and superscripts, so it changes more than you probably intend.
Common mistakes
Assuming trim() handles it. Most implementations trim ASCII whitespace only. A non-breaking space at the end of a string survives every one of them.
Comparing strings that look identical. If two values render the same but compare unequal, check for a homoglyph or a zero-width character before suspecting your comparison logic.
Stripping everything. Fine for a product code, destructive for anything with emoji or non-Latin script.
Trusting the character count. A string with three zero-width spaces has three more characters than it appears to. Length checks and database column limits both notice, and the error message never mentions it.
Only checking on input. These characters get introduced at every copy and paste. A one-off cleanup fixes today’s data; validating on the way in stops tomorrow’s.
How they get into your data
Knowing the usual routes makes them easier to prevent rather than repeatedly clean up.
Copy and paste from formatted sources. The biggest one by far. Word inserts non-breaking spaces around punctuation and soft hyphens at line breaks. PDFs carry layout characters that exist only to control rendering. Web pages use zero-width spaces to control where long strings wrap. All of it comes along with the text.
Spreadsheet exports. CSV files exported from Excel or Google Sheets frequently begin with a byte order mark. It breaks the first column header, which is why an import fails with a complaint about a column that’s clearly right there in the file.
Content management systems. Many convert straight quotes to curly ones and hyphens to en dashes automatically. Fine in prose, damaging in a code block or a product SKU.
Language model output. Text from an LLM tends toward typographic punctuation — em dashes, curly apostrophes, non-breaking spaces — because that’s what dominates its training data. Paste that into a config file or a shell command and it fails in ways that look impossible, because the character on screen looks exactly like the one you meant.
Deliberate insertion. Zero-width characters have been used to watermark documents, to fingerprint text so a leak can be traced back to a recipient, and to slip past keyword filters. If you find them in an unexpected place, that’s worth a moment’s thought.
The security angle
Two of these categories are genuine attack surface rather than just an annoyance.
Bidirectional overrides. The Trojan Source technique, published in 2021, uses characters like U+202E to make source code display in an order different from how a compiler reads it. A line that appears to be a comment can execute; a permission check that looks present can be inert. It passes human review precisely because reviewers read the rendered text. Most compilers and editors now warn about bidi characters in source, but the underlying trick still works anywhere that doesn’t.
Homoglyph domains. Registering a domain with Cyrillic letters that render identically to a well-known name is an old technique and still effective. Browsers defend against the obvious cases by showing punycode for mixed-script domains, but the protection is inconsistent across scripts and applications, and it doesn’t help at all inside email bodies, chat messages or documents.
Both are reasons to scan text you didn’t write yourself, particularly anything arriving from outside your organisation and heading into code, configuration or an access control list.
Related tools
Hidden characters throw off character counts against a strict limit — the word counter counts by code point, so an emoji is one character rather than two. If the text also needs its capitalisation fixed, the case converter leaves invisible characters untouched, so run this tool first. For statistical analysis of a passage rather than character-level cleaning, see the Claude watermark checker, which reuses this tool’s detection engine.
Frequently asked questions
What is a zero-width space?
A zero-width space (U+200B) is a Unicode character that takes up no visual room. It was designed as a line-break opportunity in scripts without spaces, but it turns up accidentally in text copied from web pages, PDFs and word processors — where it silently breaks string comparisons, database lookups and code.
How do I remove zero-width characters in Excel?
Excel's CLEAN function only strips the first 32 ASCII control characters, so it misses zero-width spaces and non-breaking spaces entirely. Use SUBSTITUTE with the specific character instead: =SUBSTITUTE(A1, UNICHAR(8203), "") for a zero-width space, and =SUBSTITUTE(A1, UNICHAR(160), " ") for a non-breaking space. Nest them to handle both.
How do I find them in VS Code?
Open Find, switch on the regular-expression toggle, and search for [- ]. VS Code also highlights suspicious invisible and ambiguous characters by default through the editor.unicodeHighlight settings, which is worth leaving switched on.
How do I strip them in JavaScript, Python or C#?
JavaScript: text.replace(/[-]/g, ""). Python: re.sub(r"[-]", "", text), or unicodedata.normalize("NFKC", text) for a broader cleanup. C#: Regex.Replace(text, @"[-]", ""). Be careful with blanket normalisation — it changes more than you might expect.
Why does my text have hidden characters at all?
Usually copy and paste. Word processors insert non-breaking spaces and soft hyphens, PDFs carry layout characters, and web pages use zero-width spaces to control line breaking. Some content management systems add byte order marks. Large language models also tend to produce typographic characters such as en dashes and smart quotes where you expected ASCII.
Are these characters ever legitimate?
Frequently, and that is why this tool does not strip everything by default. Zero-width joiners build multi-person emoji, and zero-width non-joiners are grammatically required in Persian, Arabic and several Indic scripts. Removing them there corrupts valid text, so the tool marks those instances as unsafe and explains why.
Can it find text hidden by CSS or HTML?
Yes. Paste the markup and it flags content that a parser reads but a reader never sees: display:none, visibility:hidden, opacity:0, font-size:0, large negative text-indent or offsets, clip and clip-path crops, and HTML comments. It reports the hidden text itself rather than just telling you something is hidden, so you can read what was in there.
What is zero-width steganography, and can you detect it?
It hides a message inside ordinary text by treating invisible characters as bits — one zero-width character for 0, another for 1. The result survives copy and paste and every visual check, because there is genuinely nothing to see. The tool flags a run of four or more zero-width characters using at least two distinct symbols as a possible payload, and reports it separately from stray artifacts so the signal is not buried.
Why does a single zero-width character not get flagged as a payload?
Because it almost certainly is not one. A lone zero-width space or byte order mark is an artifact of copy and paste, and it is still reported — just as an artifact rather than a payload. A payload needs a run of at least four. Runs are also reported with the number of distinct symbols they use and an estimate of the bits they could carry, so a run of one repeated character comes back as zero bits: repetition alone cannot encode anything.
What is a homoglyph, and how is it different?
A homoglyph is a character that looks like another but is not — Cyrillic а (U+0430) instead of Latin a (U+0061), for instance. Unlike invisible characters you can see them perfectly well; that is the problem. They break exact-match lookups, sneak past code review, and are the basis of lookalike domain phishing. Other tools in this space cover invisible characters but not these.
Guides that use this tool
Last updated: August 26, 2026