BV
All tools
utility

Character Counter

Count characters four ways at once and see the SMS segment cost, with limits sourced from Twilio, X and Google documentation.

Muhammad Bilal
Muhammad Bilal Virk
14 min read
Live tool
Characters used
0 / 280
Remaining
280
Words
0

Paste your text and see every count that matters at once: UTF-16 code units, Unicode code points, grapheme clusters and X weighted length, alongside SMS segment counts for GSM-7 and UCS-2. One visible emoji can be one character or eleven depending on who is counting, and only one of those numbers decides whether your message sends.

What "one character" actually means

Every character counter answers a question, and most of them never tell you which one. That is fine when you are counting an English sentence. It stops being fine the moment an emoji, an accented vowel or a curly quotation mark enters the string, because at that point there are four defensible answers and they disagree with each other.

UTF-16 code units. This is what JavaScript's String.length returns. The MDN reference is explicit that the property "contains the length of the string in UTF-16 code units", and warns that "it's possible for the value returned by length to not match the actual number of Unicode characters in the string". Anything above the Basic Multilingual Plane, which includes almost every emoji, occupies two code units rather than one.

Character Counter β€” illustration

Unicode code points. This is what Python's len() returns, and what you get in JavaScript from [...str].length. One count per abstract character. It is closer to intuition than code units, but a single visible glyph is still frequently assembled from several code points.

Grapheme clusters. This is what a person means by "a character": one visible unit on screen. In JavaScript you get it from Intl.Segmenter with granularity: "grapheme", and MDN's own worked example returns 1 for the four-person family emoji.

Platform-weighted length. This is what the receiving platform's validator returns, and it is the only one that decides whether your post is accepted. X allows 280, but not 280 code points: its documentation assigns Latin text, punctuation and common symbols a weight of 1, and gives everything else a default weight of 2. So 280 characters of English is 140 characters of Japanese.

The four answers, measured

These figures were produced by running each string through Node 22 and Python 3.10 rather than by reasoning about them, and the platform-weighted column follows the weights published in X's counting documentation.

String JS .length Python len() Grapheme clusters X weighted
Hello 5 5 5 5
cafΓ© composed (NFC) 4 4 4 4
cafΓ© decomposed (NFD) 5 5 4 4
πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ family 11 7 1 2
πŸ™‹πŸ½ with skin tone 4 2 1 2
πŸ‡¬πŸ‡§ flag 4 2 1 2
θ‡ͺε‹•εŒ–γƒ―γƒΌγ‚―γƒ•γƒ­γƒΌ 9 9 9 18

Read the family emoji row slowly, because it is the whole argument on one line. One visible character. Eleven UTF-16 code units, because it is four emoji joined by three zero-width joiners and each emoji needs a surrogate pair. Seven code points. One grapheme cluster. Two by X's rules, which X states plainly: "All emojis count as 2 characters, regardless of complexity."

The two cafΓ© rows are the subtler trap. Both look identical on screen. The composed form uses a single code point for Γ©; the decomposed form uses e followed by a combining acute accent. Neither string equals the other under ===, and a naive length check gives 4 for one and 5 for the other. X sidesteps this by normalising to NFC before counting, which its documentation says explicitly, so both arrive at 4. Your own validation code almost certainly does not normalise, which is how a form that accepts a name typed on a Mac rejects the same name pasted from a different source.

The Japanese row runs the other way. Nine code points, nine grapheme clusters, and 18 against the X limit, because CJK carries a weight of 2. A counter that shows you 9 and a green tick will let you publish a post the API rejects.

Hard limits and soft limits are not the same thing

Most character-limit tables mix two completely different categories of number and mark neither. It is worth separating them, because one kind will break your message and the other will only make it look untidy.

A hard limit is enforced by a protocol or an API. Exceed it and the message is rejected, truncated or split into billable parts. These are worth encoding into your validation.

Limit Value Source
SMS, single segment, GSM-7 160 characters Twilio messaging documentation
SMS, per segment in a multi-segment message, GSM-7 153 characters Twilio messaging documentation
SMS, single segment, UCS-2 70 characters Twilio messaging documentation
SMS, per segment in a multi-segment message, UCS-2 67 characters Twilio messaging documentation
SMS, maximum concatenated GSM-7 message 1,600 characters Twilio messaging documentation
X post 280 weighted characters X counting documentation
Any URL in an X post 23 characters, whatever its real length X counting documentation

A soft limit is a display truncation. The platform accepts your text in full and then shows part of it. Going over costs you attention, not delivery, and the exact cut-off point is neither published nor stable. Anyone quoting you a pixel figure for a Google title is quoting a measurement someone took on one device, on one day, not a documented rule.

Google's actual position on title length, from the Search Central documentation on title links, is that you should "avoid unnecessarily long or verbose text in your <title> elements. While there's no limit on how long a <title> element can be, the title link is truncated in Google Search results as needed, typically to fit the device width." There is no character limit, there is no pixel limit you can rely on, and Google reserves the right to replace your title entirely if it detects one of the issues listed in that same document.

So treat 60 characters for a title and roughly 155 for a description as working conventions that keep you comfortably inside the truncation point on most devices, and not as thresholds with authority behind them. That is the honest version, and it is the one this tool is built around. When you want the numbers measured against a live page rather than typed into a box, the SEO Meta Tag Analyzer fetches the URL and reads the tags Google will actually see.

The SMS segment cliff, costed

This is the case where a character count is money rather than tidiness, and it is the reason a counter that shows you one number is not enough.

SMS was specified around a 140-byte payload. Twilio's documentation traces it back to the GSM 03.38 recommendation: 140 bytes, 1,120 bits, or 160 seven-bit characters. Stay inside the GSM-7 character set and you get 160 characters in one segment. Introduce a single character that GSM-7 does not contain and the whole message switches to UCS-2 at two bytes per character, which drops the ceiling to 70. Twilio's own worked example is the one that catches everybody: "Your text editor changes " to β€œ: a 'curly' or 'smart' quotation mark. Your message includes a UCS-2 encoded character. This reduces the single message character limit from 160 to 70."

Longer messages are concatenated, and each segment gives up seven characters of its payload to a User Data Header that tells the handset how to reassemble the parts. That is why the per-segment figure is 153 for GSM-7 and 67 for UCS-2 rather than 160 and 70.

Here is the arithmetic, computed rather than asserted, with a GSM-7 encoder that accounts for the extension-table characters.

Message Encoding Visible characters Units used Segments
160 plain ASCII characters GSM-7 160 160 septets 1
159 ASCII characters plus one curly apostrophe UCS-2 160 160 code units 3
69 ASCII characters plus one emoji UCS-2 70 71 code units 2
80 euro signs GSM-7 80 160 septets 1
81 euro signs GSM-7 81 162 septets 2

Row two is the expensive one. The visible length did not change. One straight apostrophe became a curly apostrophe, most likely without anyone touching it, because a word processor or a CRM rich-text field did it automatically. The encoding flipped to UCS-2, the segment size dropped to 67, and a message that shipped as one segment now ships as three. Twilio bills per segment, and states the rule directly: "A message of 140 characters that includes one non-GSM character gets split into two segments. To avoid the cost of a second message segment, remove that UCS-2 character." At three segments across a 20,000-contact campaign you have quietly bought 40,000 extra messages.

Rows four and five are the other half of the story, and they are why counting visible characters is not sufficient even when you never leave GSM-7. Nine characters, € [ ] { } \ ^ ~ |, live in the GSM-7 extension table and cost two septets each. Eighty euro signs is 160 septets and fits in one segment. Eighty-one is 162 septets and needs two. The visible count says 81, comfortably under 160, and it is wrong.

Twilio's Smart Encoding feature mitigates the common version of this by substituting equivalent GSM characters, curly quotation marks for straight ones being the documented example, but it is a setting you have to turn on and it only covers characters that have a sensible replacement. An emoji does not.

If you are building SMS sequences rather than counting one-off messages, the segment arithmetic needs to live in the workflow, not in a browser tab. The walkthrough in GoHighLevel SMS Automation covers where in a sequence that validation belongs.

Counting inside an automation pipeline

The reason this page exists in a developer toolkit rather than a marketing blog is that character limits are where generated copy meets a protocol, and that is a boundary that fails silently.

A model asked for "a subject line under 60 characters" will hand you 63 and be entirely confident about it. Language models do not count characters reliably, because they do not see characters. Any pipeline that takes generated text and posts it to an API with a length limit needs a deterministic check between the two, and the check needs to measure the same thing the receiving platform measures.

Four rules that hold up in production:

Validate with the platform's own counter, not with len(). For X, that means the twitter-text library, which X publishes and points at from its own documentation, and which returns weightedLength rather than a naive length. Reimplementing the weight table from a blog post is how you end up two characters over on a post containing one emoji.

Normalise before you count, and normalise consistently. Call .normalize("NFC") in JavaScript or unicodedata.normalize("NFC", s) in Python at the point text enters your system. Otherwise the same name counts differently depending on which device typed it.

Truncate on grapheme boundaries. Slicing a string at index 160 can land in the middle of a surrogate pair or between an emoji and its skin-tone modifier. The output is a replacement character, or a differently-coloured emoji, in front of a customer. Segment first, then take the number of clusters you want.

Fail the run, do not silently trim. A truncated message that goes out is worse than a message that does not, because nobody finds out. Raise the error, route it to a channel a human reads, and let the sequence stop. The pattern for that is the same one in Make.com Error Handling: an explicit error route beats a fallback that quietly produces something wrong.

If your concern is length in words rather than characters, for reading time or content briefs, the Word Counter handles that side, including word, sentence and paragraph counts.

Six mistakes that cost real money

Trusting a pixel figure for a Google title. The commonly quoted 580 pixels for titles and 920 for descriptions appear in no Google documentation. Google says there is no limit and that truncation fits the device width. Design for roughly 60 characters because it works, not because a number was published.

Counting emoji with .length. Every emoji you add to a social post costs two UTF-16 code units at minimum, and a family sequence costs eleven. A counter using .length will tell you a 280-limit post is over when it is not, and a counter using code points will tell you it is fine when X will refuse it.

Ignoring the URL rule on X. Every URL counts as exactly 23 characters once wrapped by t.co, whatever its actual length. Shortening a link before you post saves you nothing, and a 90-character link is not costing you 90 characters.

Letting a rich-text field rewrite your SMS. Curly quotes, dashes and ellipsis characters are all outside GSM-7. Compose SMS copy in a plain-text field, or normalise it in code before it reaches the messaging API.

Forgetting the GSM-7 extension table. The euro sign and the bracket characters cost two septets each. A template with {{first_name}} placeholders in braces is spending four septets on the braces alone before any name is inserted.

Validating the template instead of the merged message. Hi {{first_name}}, your appointment is confirmed is 47 characters. Merged with a real name it is longer, and with an unusually long name it can cross a segment boundary for a subset of your list only. Validate after the merge, on the longest value in the data, not before.

Frequently Asked Questions

Why does this tool show more than one number for the same text?

Because more than one number is true. The count that matters depends on where the text is going: a protocol counts bytes or septets, X counts weighted characters, a human counts what they can see. Showing a single figure would mean picking one and hiding the disagreement, which is exactly how people end up over a limit while looking at a green tick.

Does a space count as a character?

Yes, in every count on this page. A space is a character to a protocol, to an API validator and to a byte counter. The only place it commonly does not count is a word count, where consecutive spaces collapse. If a tool gives you a "characters without spaces" figure, treat it as a curiosity rather than something to validate against.

Is an emoji one character or two?

It depends who is asking, and this is the clearest example of why. To a reader it is one. To Intl.Segmenter it is one grapheme cluster. To JavaScript's .length it is two, or more for a joined sequence. To X it is exactly two, by published rule. To SMS it is fatal to your GSM-7 encoding and drops your segment size from 160 to 70.

How many characters can an SMS message actually hold?

160 in one segment if every character is in the GSM-7 set, and 70 if any single character is not. Beyond one segment the payload drops to 153 and 67 respectively, because each segment carries a reassembly header. Twilio supports up to 1,600 characters as a concatenated message and bills per segment.

What is the real limit for a Google title tag?

There is no limit. Google's documentation says so directly, and adds that the title link is truncated in results as needed, typically to fit the device width. Around 60 characters is the convention because it survives truncation on most devices. Google may also replace your title entirely if it judges it inaccurate, boilerplate or half-empty, which is a bigger risk than length.

Do character limits apply to what I write or to what the recipient sees?

To what is transmitted, which is often longer. Merge fields expand, URLs are rewritten by shorteners, and tracking parameters get appended by the sending platform. Count the final assembled string, and count it for the longest realistic set of values rather than the first row in your data.

Where this fits in a real build

Counting characters by hand is a five-second job. Making sure that nothing in a live automation ever sends a message that is over a limit, mis-encoded or truncated mid-emoji is a design problem, and it is not solved by a browser tab.

In a working pipeline the count sits between generation and delivery: text comes out of a model or a template, gets normalised, gets measured against the specific limit for its destination, and either proceeds or stops loudly. That single step is the difference between an SMS campaign that costs what you budgeted and one that quietly triples, and between a social queue that publishes and one that fails at the API with an error nobody is watching.

If you are building that sort of validation into an SMS sequence, a social scheduler or an AI content pipeline and want the encoding and segment logic handled properly rather than approximated, book a discovery call and bring the workflow you are worried about.

Muhammad Bilal
Muhammad Bilal Virk
AI automation engineer β€” building agents, workflows, and RPA that remove repetitive work.
Share
Newsletter

One email, when I ship something worth reading.

No cadence, no filler. Unsubscribe any time.

Free consultation

Want this built against your real numbers?

A 30-minute call to scope the workflow, agent, or automation you actually need.

Book a free consultation

More utility tools

All tools
Next step

Have a workflow that's burning hours every week?

Bring me one real bottleneck. I'll tell you whether it's worth automating, and what it would take.

Book 30 Minutes Call