Find and remove the invisible characters in your text
Watermarks Remover is a free, browser-based tool that finds hidden Unicode in text and removes it exactly. It names, counts and locates every zero-width space (U+200B), byte order mark (U+FEFF), right-to-left override (U+202E), tag character, private-use codepoint and homoglyph space, then strips them.
It keeps the invisible characters that are load-bearing, so emoji, Persian, Khmer, Hangul and regional flags survive unchanged — which is what separates it from a find-and-replace that strips every zero-width character and corrupts real text.
Nothing is uploaded. The scan, the reveal view, the style score and the clean all run in your browser, with no account and no cost.
Either one lands in the box below and is scanned straight away. The example ships with a saved model run, so you can see every panel without spending anything.
What is hidden in your text — free, in your browser, no model
Every invisible character is shown as a chip where it actually sits. Chips marked kept are load-bearing — part of an emoji, a Persian word, a regional flag — and a default clean leaves them alone.
Does it read like AI wrote it? — zero-model, computed here
This measures cadence and vocabulary, not authorship. Heavily edited human writing scores high too, and a low score does not mean a text was not generated. It also cannot see a provider watermark: those live in word choice at generation time and leave no trace a counter can find.
Ask the model
Character removal cannot touch a provider watermark, because provider watermarks are statistical: the signal is spread across word choice. Substantially rewording is the only lever, and it is a partial one that costs style. Only the highest-signal paragraphs are sent — the rest of your text is returned untouched, and a long document goes in batches so it still finishes.
nothing selected yet
Before and after
The whole document, rewritten paragraphs spliced back in
Past scans
How to remove invisible characters from text
-
Put the text in. Paste it into the box, drag a file onto it, or use
Upload your own file. Text files are read in your browser byte for byte; a
.pdfor.docxis converted first. - Read what was found. The scan runs immediately and lists every hidden codepoint with its Unicode name, class, exact count and character offsets. The Reveal view shows each one as a labelled chip where it actually sits, so you can see that U+200B sitting between two ordinary words.
- Choose what to strip. Open Cleaned. The default removes contraband and keeps load-bearing characters. Toggles control space normalization, Latin confusables, directional controls, emoji and script joiners, and NFKC — each one is reversible, and the counts update as you change them.
- Take the result. Copy the cleaned text or download it under its own extension. The file's structure is untouched, because cleaning operates on codepoints rather than on markup.
- Check your work. Use Re-scan the cleaned text to run the scan again on the output. A clean result shows zero carriers, and the delta names exactly which codepoints cleared.
No account is needed for any of these steps, and nothing is uploaded. If you would rather do it
in your own code, the scanner is dependency-free JavaScript —
unicode.js exposes WmUnicode.inspect(text) and
WmUnicode.clean(text, options) and runs anywhere a browser or Node does.
What invisible characters does it find?
Eight classes, enumerated by codepoint rather than sampled — so a result of zero means the text contains none of them, exactly.
| Class | Codepoints | Why it is there |
|---|---|---|
| Zero-width family | U+200B, U+200C, U+200D, U+2060, U+FEFF, U+180E | Invisible, survives copy-paste. The classic carrier for hidden data, and the usual cause of a diff or a search that will not match. |
| Bidirectional controls | U+202A–U+202E, U+2066–U+2069, U+200E, U+200F, U+061C | Reorder how text displays. An unpaired override is the Trojan Source technique: a filename that reads one way to a person and another way to the computer. |
| Tag characters | U+E0001, U+E0020–U+E007F | Render as nothing at all. Legitimate only inside a complete regional-flag sequence; loose ones have no benign explanation in prose. |
| Variation selectors | U+FE00–U+FE0F, U+E0100–U+E01EF | Choose a glyph form. Load-bearing after an emoji or a CJK ideograph, contraband between plain ASCII. |
| Private-use codepoints | U+E000–U+F8FF, U+F0000–U+FFFFD, U+100000–U+10FFFD | No portable meaning — what they look like depends entirely on the font. |
| Homoglyph spaces | U+00A0, U+2000–U+200A, U+202F, U+205F, U+3000, U+1680 | Spaces that are not U+0020. Usually word-processor residue, and they still break naive search and diff. |
| Latin confusables | U+0410–U+0456 (selected Cyrillic), U+FF21–U+FF5A (fullwidth) | Letters that look Latin and are not. Optional, because they are genuine letters in their own scripts. |
| Other format controls | Any remaining Unicode category Cf | Soft hyphen, interlinear annotation, invisible maths operators and the rest. |
Each finding is reported with its codepoint, Unicode name, general category, class, exact count and character offsets. The full method is in llms.txt, and the two engines are plain dependency-free JavaScript you can read: unicode.js and stylometry.js.
Questions people ask
Is my text uploaded anywhere?
No, not for the scan or the clean. Both run entirely in your browser, and a text file you drop
in is read locally with FileReader — byte for byte, so nothing is normalized away
before the scan sees it. Only two things ever leave the page: a PDF or .docx, which needs the
platform's converter, and the optional model lane, which sends the findings plus the specific
paragraphs you asked it to rewrite.
What does it cost?
The scan, the reveal view, the style score, the clean and the bundled example are free and need no account. The optional model lane is sponsored by the publisher, so it is free to run until the daily allowance is used up.
How do I remove zero-width characters from a Word document or a PDF?
Drop the .docx or .pdf in and it is converted to text first. Be aware
that conversion rebuilds text rather than copying it, so character-level evidence may not
survive that step — the app labels such input as converted. For an exact result, export or save
as .txt or .md and drop that instead.
Will it break my emoji, Arabic or Persian text?
No. A default clean preserves the invisible characters that are part of the visible text: emoji joiners and presentation selectors, zero-width joiners and non-joiners inside Arabic, Indic, Khmer and Mongolian words, every tag character in a complete regional-flag sequence, Hangul jamo fillers, Khmer inherent vowels, and right-to-left marks with their paired embeddings. The same characters between plain ASCII are still removed.
Does it detect AI-generated text?
It gives a signal, not a verdict. A zero-model score measures sentence-length variation, lexical diversity over a moving window and the density of 24 formulaic phrases, and reports CLEAN, LOW, MEDIUM or HIGH. Heavily edited human writing scores high too, so treat it as evidence about cadence rather than proof of authorship.
Does it remove the watermark ChatGPT or Claude puts in text?
Not by removing characters, and no character-based tool can. Current providers watermark text statistically — the signal is spread across word choice during generation and leaves no character evidence at all. Rewording is the only lever, which is what the optional rewrite lane does, and it is best-effort: it cannot be certified against any detector and it costs some of your own voice. Removing a mark is also not a claim that the text was never AI-assisted.
What file formats does it accept?
Read locally, byte-exact: .txt, .md, .html,
.json, .csv, .tsv, .yaml,
.xml, .srt, .vtt and most source-code extensions.
Converted by the platform: .pdf and .docx. Cleaning works on
codepoints, so the file's structure survives — Markdown stays Markdown, HTML stays HTML, code
stays code.
Can I use it from my own code?
Yes. The API documentation covers the endpoints, the two task lanes and their exact input and output contracts, with a worked example in cURL, Python, JavaScript, Go, Java, Ruby, PHP and C#. The scanning engines are also usable directly — they have no dependencies and make no network calls.
What this does and does not establish
Exact
The character scan enumerates codepoints. Every count and every offset is reproducible, and an empty result genuinely means the text contains none of them. The clean removes exactly what it reports and nothing else.
A signal, not a verdict
The style score measures sentence-length variation, lexical diversity and formulaic phrase density. Those correlate with generated prose. They do not prove it, and careful human editing produces the same numbers.
Out of reach
Current providers watermark text statistically, biasing word choice during generation. No character scan can see that, and this app does not claim to. The rewrite lane is the only lever that touches it, and it is best-effort — it cannot be certified against any detector, and it costs you some of your own voice.
Also out of scope
File-level provenance — signed content credentials, embedded metadata, pixel and audio watermarks. A clean text scan says nothing about any of them.
More detail: the eight classes of invisible character it finds, questions people ask, the API documentation, and llms.txt for the full method.
The method implemented here is derived from github.com/guillaumemeyer/watermarks-remover (MIT, Guillaume Meyer) — specifically its text layers. The layer split, the preservation rules for load-bearing invisibles, the stylometric measures and the honesty discipline above all come from that project. This app is an independent re-implementation for the browser, not a port or a redistribution of its code.