Skip to content
Ghostchars

Get the invisible junk out of your text.

Paste anything. We find zero-width spaces, smuggled tag characters, look-alike letters and other characters you can't see — and we don't wreck your emoji or your non-English text doing it.

  • Runs in your browser
  • No sign-up
  • Free, no limits
  • Open source
Nothing is uploaded. The cleaning happens on this page.

Your text is analysed in the browser. Press Clean text to see results below.

The defaults already remove every character that is invisible and doing no work: zero-width characters, byte-order marks, soft hyphens, invisible math operators, bidi overrides, free-floating tag characters and variation selectors, noncharacters, and other format controls. Everything below goes further than that — it either changes text you can see or removes characters that are doing a job — so you decide.

Whitespace & layout

Non-breaking, thin, and ideographic spaces become a plain space. Off because NBSP is often deliberate.
CRLF, CR, NEL, and Unicode line/paragraph separators become a plain newline.
Spaces parked at the end of a line can encode a hidden message (the SNOW technique).
Double spaces between words can carry binary data. Off because it changes fixed-width layouts.

Letters & punctuation

Cyrillic а in “apple” becomes Latin a. Off because it would mangle genuine Russian or Greek text.
Em dash, curly quotes, ellipsis and minus sign become ASCII. Often used as an “AI tell”, but also just good typography.
Composed vs. decomposed accents (é as one character or two) can encode bits. NFC picks one form.
Also folds fullwidth, superscript and ligature forms. Changes more than you might expect, and supersedes NFC.

Controls

We already remove bidi overrides, and marks in left-to-right prose. This one also removes the marks sitting next to Hebrew or Arabic, where they do real layout work.
C0 and C1 controls other than tab and newline.
Breaks icon fonts and some brand glyphs, which is exactly where these normally live.

Aggressive

Removes every zero-width joiner and variation selector, including the ones holding 👨‍👩‍👧 together and the ones Persian, Hindi and flag sequences need. Only use this when you want plain letters and nothing else.

ZWSPTAG U+E0053

Press Clean text and we'll show you exactly what was hiding in there — in plain language first, with the codepoints underneath if you want them.

  • Your text never leaves this pageThere is no upload, no server call, no analytics on what you paste. Turn off your wifi and it still works.
  • Free, unlimited, no accountNo character cap, no daily quota, no email wall in front of the results.
  • Beaten 11 real hiding schemesZero-width binary, tag smuggling, variation-selector bytes, SNOW whitespace, homoglyph and NFC/NFD binary, and five more.
  • Same engine as the CLIThe Python tool and this page share one rulebook, so a pre-commit check and a browser paste agree.
Three steps

Paste, clean, copy.

That's the whole thing. Everything else on this page is optional reading.

  1. Paste it in

    Straight from a chat window, a PDF, a translation tool, an AI assistant, or a colleague's email. The hidden characters come along for the ride — that's the point.

  2. Press Clean text

    We walk your text character by character and decide, for each suspicious one, whether it is hiding or working. The visible words are never touched.

  3. Copy the clean version

    One button. If you want the receipts, open the findings table and see every character with its position, codepoint and official Unicode name.


What it detects

Six families of characters you can't see.

Roughly 26 distinct character types, grouped into the families that actually behave differently.

Zero-width characters

Take up no space, survive copy-paste, and can encode a whole message in binary. The classic way to fingerprint a document per recipient.

U+200B ZWSP · U+200C ZWNJ · U+200D ZWJ · U+2060 WORD JOINER · U+FEFF BOM

Bidi controls

Reorder how text is displayed. Harmless next to Hebrew or Arabic; a way to make source code read one way and compile another when they aren't.

U+202A–202E · U+2066–2069 · U+200E LRM · U+200F RLM · U+061C ALM

Tag characters

A shadow ASCII alphabet. An entire English sentence can sit inside plane 14 and render as absolutely nothing — the trick behind ASCII smuggling into AI assistants.

U+E0001 · U+E0020–E007E · U+E007F CANCEL TAG

Variation selectors

Meant to pick an emoji or glyph style. Free-floating ones carry a byte each, which makes a long invisible payload trivially easy to attach to any character.

U+FE00–FE0F · U+E0100–E01EF

Spaces & separators

Not all spaces are the space bar. Swapping NBSP for a plain space, or leaving one extra space at the end of a line, encodes bits nobody sees.

U+00A0 · U+2000–200A · U+202F · U+205F · U+3000 · U+2028 · U+2029

Look-alike letters

Cyrillic а is not Latin a, but nothing on screen tells you that. Our map has 1,513 entries generated from Unicode's own confusables.txt.

U+0430 а · U+043E о · U+0435 е · U+03BF ο · U+0456 і · U+FF41 a


The part other tools get wrong

Why your emoji survive.

The same character can be a hiding place or load-bearing structure. Tools that delete by codepoint alone break things; tools that play it safe miss things. We look at the neighbours.

👨ZWJ👩ZWJ👧

Kept — it's holding the family together

U+200D sits between two emoji. Remove it and the family emoji falls apart into three separate people. Same character, same codepoint, completely different job.

heZWJllo

Removed — it's hiding between two letters

The identical U+200D between two Latin letters joins nothing. It is there to survive a copy-paste and carry a bit of someone’s watermark.

🏴󠁧󠁢󠁳󠁣󠁴󠁿 stays a flag

Tag characters spell out “gbsct” inside the Scotland flag. Free-floating tag characters get removed; the ones inside a valid flag sequence don’t.

Persian and Hindi keep their joiners

A zero-width non-joiner inside a Persian word changes how the letters connect. Delete it and you have misspelled the word. We keep it and tell you it's there.

Hebrew keeps its direction marks

An RLM next to Hebrew text is doing real layout work. An RLM in a run of English is not. Both are U+200F.


Straight answer

What this tool cannot do.

Plenty of sites promise to “remove AI watermarks”. Here is where character cleaning genuinely stops, so you don't rely on it for something it was never able to do.

  • Statistical watermarks stay put. Schemes like Google's SynthID-Text are embedded in which words the model chose, not in any character. Nothing we strip touches them. Rewriting the text does.
  • Stylometry is untouched. Sentence rhythm, vocabulary and structure are what AI-detection classifiers actually read. Straightening your quotation marks changes none of it.
  • Format-level hiding is out of scope here. Metadata in a DOCX, white text on white in a PDF, comments in HTML — that lives in the file, not the text. Paste the text and we clean the text; take the whole file to the Documents page.
  • An em dash is not evidence. We can convert typographic punctuation on request, but a person who writes well uses em dashes too. Treat it as formatting, not proof.

What we do claim: eleven published character-level steganography techniques, each one defeated in testing with the visible text byte-for-byte identical afterwards.

Who uses it

Mostly people with something pasted.

Writers & students

Text moved through three apps before it reached your document. Clean it once so a submission portal or plagiarism checker doesn't choke on characters you never typed.

Translators

CAT tools and MT engines leave behind NBSPs, soft hyphens and stray bidi marks. Clean the invisible layer without touching the joiners your target language depends on.

Publishers & CMS teams

Author submissions arrive from Word, Docs and email. Normalise them before they hit the database, and keep a report of what changed.

Developers

Run --check in a pre-commit hook and fail the commit when an invisible character or bidi override lands in source, config or a locale file.

Security & AI-safety teams

Inspect suspected prompt-injection payloads: see the tag-character sentence, the codepoints and the exact offsets before deciding what to do with the input.

Anyone who got a weird file

If a document looks fine but behaves strangely — search fails, sorting breaks, a name won't match — paste it here first. It's usually a character you can't see.


Open source

The same engine, in your terminal.

strip_invisible.py is a single dependency-free Python file. No install step, no package to trust — download it, read it, run it.

# See what is hiding, without changing anything
python3 strip_invisible.py --show file.md

# Clean in place, and normalise spaces and punctuation while you are there
python3 strip_invisible.py -i -s --punct draft.md

# Fail the build if invisible characters made it into the repo
python3 strip_invisible.py --check src/**/*.ts

# Documents too: hidden runs, tracked changes, metadata, rsids
python3 clean_file.py --show essay.docx

Exit code 1 when anything is found, so it drops straight into a pre-commit hook or CI step. Every toggle in the Advanced panel above maps to one flag.

Questions

Reasonable things to ask.

Is my text uploaded anywhere?

No. Everything runs in JavaScript on this page. There is no request to any server when you press Clean text, and nothing you paste is logged or stored. If you want to verify that, open your browser's network tab, or just disconnect from the internet and use the page offline.

Will it break my emoji?

Not with the default settings. Zero-width joiners between emoji, variation selectors that pick an emoji presentation, and tag characters inside flag sequences are all recognised as load-bearing and kept — and reported, so you can see them. The only way to remove them is to deliberately switch on “Strip joiners even when they matter” under Advanced.

Does it work with Arabic, Hebrew, Hindi or Persian?

Yes, and that is the main reason this tool exists. Zero-width non-joiners inside Persian words, joiners in Indic scripts, and directional marks next to right-to-left text all change how the text reads, so they are kept by default. The same characters floating in a run of English are removed.

Can it remove an AI watermark?

It removes character-level watermarks — the kind that hide data in invisible codepoints, whitespace patterns or look-alike letters. It does not remove statistical watermarks such as SynthID-Text, which live in word choice, and it does not change how an AI-detection classifier scores your writing. Any tool claiming otherwise is overselling.

Why is a character listed but not removed?

Two reasons. Either it is load-bearing — removing it would change how your text renders — or it needs a setting that is off by default because it would alter visible text, like look-alike letters or curly quotes. In both cases the findings table names the exact setting that would remove it, so nothing is silently ignored.

What is an ASCII smuggling attack?

Unicode has a block of “tag” characters (U+E0000–E007F) that mirror ASCII but render as nothing at all. A full instruction can be written in them, pasted into a document, and read by a language model while being completely invisible to the person reviewing it. We strip free-floating tag characters by default and show you what they spelled.

What is the difference between NFC and NFKC?

NFC picks one consistent spelling for accented characters — é as a single codepoint rather than e plus a combining accent. Choosing between the two forms at random is itself a way to encode bits. NFKC goes further and also folds fullwidth letters, ligatures and superscripts into plain equivalents, which is more aggressive than most text wants.

Is there a limit on how much text I can clean?

No quota and no account. The practical limit is your browser: a few hundred thousand characters is fine, and beyond that the terminal version is a better fit — it streams files and handles whole directories.

Does cleaning change how my text looks?

With the defaults, no — every character removed is one that renders as nothing. That is the test we hold the tool to: run it against a steganography scheme and the visible text should come out byte-for-byte identical. Options under Advanced deliberately break that rule, which is why each one says what it changes.

How is this different from a find-and-replace?

A find-and-replace deletes every instance of a codepoint. That is how tools end up shredding family emoji and Persian spelling. This one decides per occurrence, based on the characters either side, and shows you the reasoning for each decision with a line and column number.