Text Inspector
Exact counts of characters, bytes, words and lines; count and locate any letter or substring; and reveal invisible, bidirectional and look-alike characters. Analysed in your browser.
Where your data goes
| Using this page | Using the API, MCP or A2A | |
|---|---|---|
| Where it runs |
In your browser. The text is analysed in this page with your browser's Intl.Segmenter and Unicode regular expressions; nothing is sent, and the page has no length limit. | In a Cloudflare Pages Function at the Cloudflare data centre nearest the caller. |
| What leaves your device | Nothing you enter. After the page and its scripts load, no request
carries your input. The browser enforces this: the page's Content-Security-Policy
sets connect-src 'none' and form-action 'none', which
block fetch, XHR, WebSocket, EventSource, beacons and form posts.
| The server receives the text (and the search string), analyses it in memory, and returns counts and positions. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. For private text use the page, which does the same analysis in your browser. |
| Stored | Nothing leaves the page. No cookies, analytics or third-party scripts; the browser keeps only your View → Inline config choice. | Your inputs and results: never. What is stored is your client record (client ID, a SHA-256 hash of your secret, expiry, and the profile you registered with) and one metadata row per call: operation, time, transport, status, error code, duration, request and response sizes, and country. |
| Logged | Nothing (no requests are made). | Only the per-call metadata above, attributed to your client ID. Never parameters, bodies, results, or credentials. Cloudflare, as the host, also processes request metadata (IP address, full URL including the query string, headers) under its own policies, so put inputs in POST bodies. |
| Access | Open to anyone. | Requires a client ID and secret (how to get one). HTTPS only; TLS terminates at Cloudflare. |
Details and how to verify this yourself: Security & privacy.
User guide
The tool sits in a window that works like a desktop app: menus on the top row and the full-screen icon at its right (Esc leaves full screen). The set-up options described below are in Settings → Options…, an overlay, and the Settings menu also lists the common choices directly. To keep the options in view instead, choose View → Inline config; the page remembers that choice. Help → Keyboard shortcuts… lists the keys.
- Paste or type text. It is analysed as-is: nothing is trimmed or normalized.
- Read the Counts card. Characters are grapheme clusters, what a person would count. The other rows are what programs count:
- Code points: Python’s
len(). - UTF-16 code units: JavaScript’s
.length, Java’slength(). - UTF-8 bytes: what storage and network limits measure.
- Code points: Python’s
- To count a letter or word, type it in Count occurrences of. Every match is listed with its line, column and index. Tick Ignore case to match
Randralike. - The banner turns red if the text contains hidden or deceptive characters, which are listed with their positions:
- zero-width characters;
- bidirectional controls;
- Unicode tag characters;
- Latin look-alikes from other scripts.
- Tick Per-character breakdown to see each code point’s U+ number, UTF-8 bytes, Unicode category and script.
Why counts differ. 👍🏽 is one character to a reader, but it is two code points (thumbs up + skin tone), four UTF-16 code units, and eight UTF-8 bytes. é can be one code point (U+00E9) or two (e + U+0301 combining accent), and both look identical. Normalization tells you whether the text is in NFC, the composed form most systems expect.
Hidden characters matter.
- A zero-width space breaks a search, a password or an identifier invisibly.
- Bidirectional overrides can make source code display differently from how it compiles (“Trojan Source”).
- Unicode tag characters can hide instructions that language models read but people cannot see.
- A Cyrillic
аinpаypalmakes a look-alike domain or username.
Specification
Counts.
| Count | Definition |
|---|---|
graphemes | Extended grapheme clusters, Unicode UAX #29, via Intl.Segmenter (granularity grapheme) |
codePoints | Unicode code points. A lone surrogate counts as one |
utf16CodeUnits | UTF-16 code units (the JavaScript string length) |
utf8Bytes | Length of TextEncoder output. A lone surrogate is encoded as U+FFFD (3 bytes) |
words | Intl.Segmenter word segments with isWordLike true. Hyphenated words count as two (e-mail → e, mail); don't and 3.14 count as one. Thai, Japanese and Chinese use the runtime’s dictionary-based segmentation |
sentences | Intl.Segmenter sentence segments that contain a non-whitespace character |
lines | 0 for empty text. Otherwise line breaks + 1, minus 1 if the text ends with a line break. nonEmptyLines excludes empty lines |
Line breaks are LF, CRLF (counted once) and CR. lineEndings.style is none, LF, CRLF, CR, or mixed when more than one kind occurs. U+0085, U+2028 and U+2029 are not counted as line breaks; they are reported as line-separator findings.
Character classes count code points by Unicode property: \p{L}, \p{Lu}, \p{Ll}, \p{M}, \p{Nd}, \p{N}, \p{P}, \p{S}, \p{White_Space}, \p{Zs}, \p{Cc} and \p{Cf}. Titlecase letters such as Dž count as letters, but as neither upper nor lower case. emoji counts grapheme clusters containing an \p{Extended_Pictographic} code point.
Occurrences (find).
- Matching: literal (special characters have no meaning) and on code points.
- No normalization: precomposed
édoes not matche+ U+0301. - Case: case-sensitive by default. With
case_sensitive=false, Unicode simple case folding is used:K(Kelvin sign) matchesk, butßdoes not matchssandİdoes not matchi. count: non-overlapping matches from left to right (aainaaaais 2).overlappingCount: a match may start at every code point (aainaaaais 3).- Positions: each match has
utf16Index,codePointIndexandgraphemeIndex(0-based), andlineandcolumn(1-based, column in code points).splitsGraphemeis true when a match starts or ends inside a grapheme cluster, such aseinsidee+ U+0301. - Limit: at most 1,000 positions are listed;
countis always exact.
Hidden and deceptive characters.
| Kind | Code points |
|---|---|
bom | U+FEFF at the start |
zero-width | U+200B, U+200C, U+200D, U+2060, U+180E, and U+FEFF elsewhere |
bidi-control | U+202A–U+202E, U+2066–U+2069 (embeddings, overrides, isolates; CVE-2021-42574) |
bidi-mark | U+200E, U+200F, U+061C |
unusual-space | Every \p{Zs} except U+0020, e.g. U+00A0 NBSP, U+202F, U+2007, U+3000 |
control | C0 and C1 controls and DEL, except tab, LF and CR |
line-separator | U+0085, U+2028, U+2029 |
tag | U+E0000–U+E007F |
variation-selector | U+FE00–U+FE0D, U+E0100–U+E01EF |
invisible-format | Other \p{Cf} (e.g. U+00AD soft hyphen, U+2061–U+2064) and U+034F |
invisible-letter | Hangul fillers U+115F, U+1160, U+3164, U+FFA0 |
replacement | U+FFFD (a sign of an earlier decoding error) |
lone-surrogate | An unpaired UTF-16 surrogate |
private-use | \p{Co} |
confusable | A Latin look-alike from Cyrillic, Greek or Armenian, inside a word that mixes Latin with those scripts; or a fullwidth ASCII form (U+FF01–U+FF5E) anywhere |
Exempted as normal emoji syntax:
- ZWJ inside an emoji grapheme cluster (👨👩👧);
- tag characters in an emoji tag sequence (U+1F3F4, 1–7 tags, U+E007F, such as the flag of England);
- variation selectors 15 and 16 (U+FE0E, U+FE0F).
Other detection rules.
- Look-alikes: the built-in list covers common look-alikes of ASCII letters. It is a subset of Unicode’s
confusables.txt, not the full UTS #39 algorithm. - Unbalanced bidi controls:
bidiUnterminatedLineslists lines where an embedding or override is not closed by U+202C, or an isolate by U+2069, before the line ends. This is the Trojan Source pattern. - Limit: at most 1,000 findings are listed, in text order;
totalandbyKindare exact.
Per-code-point breakdown (breakdown = N, at most 2,000 through the API, 500 on the page). For each code point:
- its index and
U+XXXX; - UTF-8 and UTF-16 bytes in hex;
- its General_Category, tested against all 30 values;
- its Script, checked against 49 widely used scripts plus
Unknownfor unassigned code points (null if it is in none of them); - a name for control, space and invisible characters (null for others: the full Unicode name list is not bundled).
Locale. locale must be a valid BCP 47 tag. It affects word and sentence segmentation; grapheme segmentation is locale-independent in practice.
Versions. Segmentation and properties come from the ICU and Unicode data in the runtime: V8 in Cloudflare Workers for the API, your browser for the page. Counts for newly added emoji can differ between runtimes on different Unicode versions.
API limits. input is at most 20,000 UTF-16 code units (about 6 ms of server CPU, inside the Workers per-request budget); split longer text and add the counts. find is at most 1,000 code units.
API & MCP
The same logic is available over HTTP and as MCP tools. Unlike this page, these run on the server: see where your data goes.