Character Counter

Count characters, code points, UTF-16 units and UTF-8 bytes at once, with live limit meters for X, SMS segments, meta descriptions and title tags.

Runs in your browser

Counts graphemes, code points, UTF-16 units and UTF-8 bytes — they differ for emoji and Korean.

0 Characters (as people see them)
0 Unicode code points
0 UTF-16 code units
0 UTF-8 bytes

Type or paste to see how it counts against each limit.

Characters (no spaces): 0 · Words: 0 · Lines: 0

Limit meters

X / Twitter 0 / 280 280 left
X / Twitter 0 / 280
Bluesky 0 / 300
SMS 0 / 160
GSM-7 · 0 / 160 · 0 segments
Meta description 0 / 155
Google measures pixels, so this is an approximation.
Title tag 0 / 60
Google measures pixels, so this is an approximation.
Google Ads headline 0 / 30
Google Ads description 0 / 90
Instagram caption 0 / 2,200
Open Graph title 0 / 88
Writing something longer? Word Counter →

Four ways to count one character

A grapheme is what a reader calls one character; a code point is one Unicode value; a UTF-16 unit is what JavaScript’s .length counts; a UTF-8 byte is what a database column and an HTTP header measure. For plain English all four are the same number, which is why the difference stays invisible until it breaks something.

The family emoji 👨‍👩‍👧‍👦 is 1 character, 7 code points (four people joined by three zero-width joiners), 11 UTF-16 units and 25 UTF-8 bytes. The Korean syllable 한 is 1, 1 and 1 — but 3 UTF-8 bytes. Typed on macOS it often arrives decomposed as ᄒ + ᅡ + ᆫ: still 1 grapheme, but now 3 code points and 9 bytes for the same visible character.

This is why “20 characters” is not a specification. A form that measures graphemes accepts a name that a VARCHAR(20) column measuring bytes rejects, and a JavaScript check on .length disagrees with both. All four counts are shown at once so you can pick the one the receiving system actually uses.

Which count your system is actually using

  • Graphemes: what people count, and what a well-built form or a designer’s character limit should count.
  • Code points: Python’s len(), Swift’s unicodeScalars, and the CHAR/VARCHAR length of MySQL’s utf8mb4 and PostgreSQL.
  • UTF-16 units: JavaScript .length, Java String.length(), C# string.Length, and the SMS UCS-2 count — an emoji is 2 here, which is where “my 140-character field ate half my emoji” comes from.
  • UTF-8 bytes: Go’s len(string), file sizes, HTTP header limits, JSON payload sizes, and Oracle’s VARCHAR2(n BYTE).

One count is deliberately missing: legacy Korean systems that store EUC-KR (CP949) give a Hangul syllable 2 bytes rather than the 3 it takes in UTF-8, so a 100-byte EUC-KR field holds 50 Hangul characters and the same field in UTF-8 holds 33. This tool measures UTF-8 only — for an EUC-KR limit, count 2 bytes per Hangul or CJK character and 1 per ASCII character by hand.

SMS: why 160 characters becomes 70

A text message is encoded with the GSM 03.38 alphabet — 128 slots covering ASCII plus a few European letters and symbols — which packs 7 bits per character and fits 160 of them in one segment. Nine characters live in an extension table (^ { } \ [ ~ ] | €) and cost two slots each, so a message full of curly braces runs out at 80.

One character outside that set — an emoji, a curly quote pasted from Word, an em dash, any Korean or Japanese character — switches the entire message to UCS-2, which stores 16 bits per character and fits only 70. Nothing is wrong with the rest of the text; the encoding is a property of the whole message. The tool names the first characters that forced the switch, so a single stray “…” is easy to find and replace.

Past one segment each part gives up room to a concatenation header: 153 characters per segment in GSM-7 and 67 in UCS-2. So 161 characters is not “one and a bit messages”, it is two, and a 300-character Korean message is five segments, not three. Carriers bill per segment, which is why the counter shows segments rather than a single length.

X (Twitter) counts with weights

X does not count characters, it counts weighted units up to 280. Characters in the ranges U+0000–U+10FF, U+2000–U+200D, U+2010–U+201F and U+2032–U+2037 — Latin, Cyrillic, Greek, Hebrew, Arabic, common punctuation — weigh 1. Everything else, which means Korean, Japanese, Chinese and every emoji, weighs 2.

So a Korean post really does stop at about 140 characters while an English one reaches 280, and this is deliberate rather than a bug — the weighting was introduced when the limit doubled in 2017, precisely because CJK writing carries more meaning per character.

Links are the one place the meter reads high. X replaces every URL with a t.co link of exactly 23 characters no matter how long the original is, so a post carrying a 90-character URL has 67 more characters of room than the counter shows. A link in the text raises a note for that reason.

The limits in the preset list

  • X 280 weighted units · Bluesky 300 graphemes · Instagram caption 2,200 · og:title 88
  • SMS 160 in GSM-7 or 70 in UCS-2 — the meter switches to whichever encoding your text needs
  • Meta description 155 · title tag 60 · Google Ads headline 30 · Google Ads description 90

Treat the two search ones as rules of thumb rather than hard limits: Google truncates a snippet by pixel width, not by character count, so a title of wide capitals is cut earlier than 60 lowercase characters and Korean text is cut earlier still. Staying at or under those numbers is what keeps a title intact on both desktop and mobile.

The Custom limit field takes any number and adds a meter of its own — useful for a CMS field, a product title cap or an internal style rule. Trim to limit cuts the text on grapheme boundaries, so it can never leave half an emoji or a stranded combining mark behind.

Normalization, invisible characters and broken emoji

Korean and accented Latin text can be stored two ways: precomposed (NFC) or decomposed (NFD). Text typed on macOS, and text pulled out of some PDFs, often arrives decomposed — 한 as three jamo instead of one syllable. It looks identical and it sorts and searches differently. Normalize (NFC) rewrites it and the tool shows how many bytes that saved: for a 100-character Korean string, 600 bytes down to 300.

An unpaired surrogate — half of an emoji, left behind when some other system truncated a string by UTF-16 units — is flagged separately. It has no visible form, it becomes U+FFFD (3 bytes) the moment it is encoded to UTF-8, and it is the usual reason a string that looked fine arrives as a black diamond.

Pasted text also carries invisible passengers: non-breaking spaces from a web page, soft hyphens from a PDF, zero-width joiners from an emoji sequence. They count toward every limit and none of them look like anything. If a count is higher than the text appears to justify, the “no spaces” figure next to the total is the quickest way to see how much of it is whitespace.

Limits and privacy

Above 100 KB the grapheme walk is replaced by code-point counting and an “approximate” badge appears. The two counts differ only where emoji sequences and combining marks appear, so for a large plain-text document the number is the same either way — the badge is there so you know which rule produced it.

The text stays in this browser. It is kept in localStorage so a reload does not lose a draft, and Clear removes it; there is no account, no upload and no server copy. That also means the draft is gone if you clear site data, use a private window or switch browsers.

How to use

  1. Type or paste your text — all four counts update as you type.
  2. Compare characters, code points, UTF-16 units and UTF-8 bytes; hover one for what it means.
  3. Pick a limit in ‘Highlight limit’ — X, SMS, meta description — or enter a Custom limit.
  4. Watch the limit meters: ‘Trim to limit’ cuts the text, ‘Normalize (NFC)’ shortens the bytes.
  5. Use ‘Copy counts’ for the numbers, or ‘Copy text’ for the text as it now stands.

FAQ

Why do you show four different character counts?

Because "one character" means four different things. A family emoji is 1 character to a reader, 7 Unicode code points, 11 UTF-16 units (what JavaScript's .length returns) and 25 UTF-8 bytes (what a VARCHAR column measures). Picking the wrong one is how a "20 character" field rejects a valid name.

Why does my SMS split into two messages at 70 characters?

A single emoji or a curly quote forces the whole message from the GSM-7 alphabet to UCS-2, which fits 70 characters instead of 160. And once a message needs more than one segment, each segment loses room to a header — 153 or 67 instead of 160 or 70. We show which character caused the switch.

Is the Twitter/X count exact?

We use X's weighted counting, where CJK characters and emoji count as 2 and Latin characters count as 1 — so a Korean tweet really is limited to about 140 characters. We count links literally; X shortens every link to 23 characters, so your real length may be a little lower.

Are spaces and line breaks included?

Yes, in every count — and every platform limit counts them too. The “no spaces” figure beside the total is the same count with all whitespace removed, which is the number some school and journal word limits mean. A Windows CRLF line break is one grapheme but two code points, two UTF-16 units and two UTF-8 bytes, so a text with many line breaks can be a few bytes longer than it looks.

Why is one Korean character 3 bytes?

UTF-8 stores ASCII in 1 byte, most European letters in 2, and everything in the U+0800–U+FFFF range — which includes every Hangul syllable and every CJK ideograph — in 3. Older Korean systems using EUC-KR (CP949) store the same syllable in 2 bytes, so a byte limit copied from a legacy system holds fewer characters once the data moves to UTF-8. This tool reports UTF-8 bytes; for an EUC-KR field, count 2 bytes per Hangul character and 1 per ASCII character.

Related tools

More Text tools: Text

Send feedback

Found a bug or have an idea? Tell us — it helps make the tools better.

Only needed if you would like a reply.

Page: /text/character-counter/