Text & Data

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.

Text inspector options

Keyboard shortcuts

Esc
Close a menu or overlay, then leave full screen
↓ on a menu
Open it; ↑ ↓ move through its items
← → in the menu bar
Move between menus

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.

  1. Paste or type text. It is analysed as-is: nothing is trimmed or normalized.
  2. 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’s length().
    • UTF-8 bytes: what storage and network limits measure.
  3. 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 R and r alike.
  4. 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.
  5. 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 а in pаypal makes a look-alike domain or username.

Specification

Counts.

CountDefinition
graphemesExtended grapheme clusters, Unicode UAX #29, via Intl.Segmenter (granularity grapheme)
codePointsUnicode code points. A lone surrogate counts as one
utf16CodeUnitsUTF-16 code units (the JavaScript string length)
utf8BytesLength of TextEncoder output. A lone surrogate is encoded as U+FFFD (3 bytes)
wordsIntl.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
sentencesIntl.Segmenter sentence segments that contain a non-whitespace character
lines0 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 match e + U+0301.
  • Case: case-sensitive by default. With case_sensitive=false, Unicode simple case folding is used: K (Kelvin sign) matches k, but ß does not match ss and İ does not match i.
  • count: non-overlapping matches from left to right (aa in aaaa is 2).
  • overlappingCount: a match may start at every code point (aa in aaaa is 3).
  • Positions: each match has utf16Index, codePointIndex and graphemeIndex (0-based), and line and column (1-based, column in code points). splitsGrapheme is true when a match starts or ends inside a grapheme cluster, such as e inside e + U+0301.
  • Limit: at most 1,000 positions are listed; count is always exact.

Hidden and deceptive characters.

KindCode points
bomU+FEFF at the start
zero-widthU+200B, U+200C, U+200D, U+2060, U+180E, and U+FEFF elsewhere
bidi-controlU+202A–U+202E, U+2066–U+2069 (embeddings, overrides, isolates; CVE-2021-42574)
bidi-markU+200E, U+200F, U+061C
unusual-spaceEvery \p{Zs} except U+0020, e.g. U+00A0 NBSP, U+202F, U+2007, U+3000
controlC0 and C1 controls and DEL, except tab, LF and CR
line-separatorU+0085, U+2028, U+2029
tagU+E0000–U+E007F
variation-selectorU+FE00–U+FE0D, U+E0100–U+E01EF
invisible-formatOther \p{Cf} (e.g. U+00AD soft hyphen, U+2061–U+2064) and U+034F
invisible-letterHangul fillers U+115F, U+1160, U+3164, U+FFA0
replacementU+FFFD (a sign of an earlier decoding error)
lone-surrogateAn unpaired UTF-16 surrogate
private-use\p{Co}
confusableA 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: bidiUnterminatedLines lists 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; total and byKind are 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 Unknown for 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.