← All free tools

X (Twitter) character counter

X does not count characters. It counts weight — and the difference decides whether your post sends.

—/ 280 weighted

Runs in your browser. Nothing you type leaves this page.

The four rules .length gets wrong

Every URL costs 23, however long it is

X wraps every link in t.co, so a nine-character link and a nine-hundred-character link both cost the same 23. A post that looks 90 characters over can be comfortably inside the limit — and a counter that measures the raw string will refuse it.

X decides where a link starts and stops

A link is not “everything up to the next space”. The full stop, comma or question mark that ends a sentence is not part of the link before it, so Read more: https://example.com/post. costs 35, not 34. A bare example.com is a link at 23, and so is main.rs, because X checks the ending against a list of 1,581 and .rs is on it. README.md is not: x.com never treats a bare domain ending in .md as a link. An email address is not a link at all. Our own counter had the full stop, the list of endings and the email address wrong until October 2026.

Emoji and CJK cost 2 each

Latin letters, Greek, Cyrillic, Hebrew, Arabic and Thai all cost 1. Chinese, Japanese and Korean characters cost 2, as do emoji. So 150 Japanese characters is 300 to X — over the limit — while your notes app, your CMS field and almost every character counter you might have drafted it in say 150 and report room to spare. The disagreement is not discovered while writing. It is discovered at the end.

One emoji is one unit, however many pieces it has

A four-person family emoji is four people joined by three invisible characters. A flag is two letters in disguise. X charges 2 for the whole thing; count the pieces separately and you get 11 for the family and 4 for the flag. This is a rule real counters get wrong, including ours until we tested it against X's own configuration.

The weights, from X's documentation

WhatCostExample
Latin, punctuation, common symbols1abc → 3
Greek, Cyrillic, Hebrew, Arabic, Thai1Ж → 1
CJK — Chinese, Japanese, Korean2日本語 → 6
Any emoji, however complex2👨‍👩‍👧‍👦 → 2
Any URL, however long23https://a.co → 23

Counting happens after Unicode NFC normalisation, so a composed é and a decomposed e + accent give the same answer.

What your counter says, and what that tells you

Wrong counts are not wrong at random. Paste 👨‍👩‍👧‍👦 into whatever you use and compare — X charges 2.

It saysBecauseWhat that means
11UTF-16 units (str.length) — or X's real weights applied per code pointThe emoji is not treated as one unit. It may also be cut in half when the text is truncated.
7Code points ([...str].length)The usual "fix". Still wrong — code points are not characters.
2Whole emoji first, then weightsCorrect.

The first row is the part worth knowing, and we have not seen it written down anywhere else: naive length and correct-weights-applied-wrongly give the same answer. It is not a coincidence. Every emoji code point outside the basic plane is two UTF-16 units and costs 2; every zero-width joiner is one unit and costs 1. The two methods agree on every emoji — so from the number alone you cannot tell a counter that has never heard of X's rules from one that implements them and segments text incorrectly. Only the shape of the failure differs, and only treating the emoji as one unit fixes either.

The same arithmetic explains the numbers in other people's bug reports. 🧑‍🔧 is two emoji joined by one joiner: 2 + 1 + 2 = 5, which is exactly the figure reported in issue 369 of twitter-text. X charges 2.

We got this wrong too, and here are the dates

This counter charged the family emoji 11 until 27 August 2026. It was found by testing our shipped code against X's published configuration rather than against our own assumptions — our two copies agreed with each other perfectly and were both wrong, which is precisely what a consistency check cannot catch.

On 29 August 2026 we found a second one, in our own editor rather than here: a bare example.com was charged 11 instead of 23, because X wraps every link in t.co including links typed without https://. That error runs in the dangerous direction — it says a post fits when X will reject it.

On 3 October 2026 we found the third, and the largest, by running X's own library over the same strings: this counter was deciding for itself where a link ends. It took the full stop after a link into the link, so a post one over the limit read as fitting. It knew 49 domain endings where X’s library knows 1,574, so whitehouse.gov was 14 instead of 23. And it charged an email address 23 for its domain. The counter on this page is now a port of the patterns X publishes, and it runs X's own test cases. Lens carries the same fix from version 0.7.1.

On 9 October 2026 we found a fourth, by pasting five strings into X’s own composer: the site no longer counts quite the way the library X publishes does. It never treats a bare README.md as a link, it knows seven more domain endings, among them .music, and its emoji table is newer. This counter now counts what x.com counts, and Lens gets the same change in version 0.7.2.

All three have tests pinning the exact inputs. We publish the dates because a page arguing that other counters are wrong should show its own.

Writing the same post for more than one platform?

This page counts one platform correctly. PostSiete takes one idea and writes it for seven — X, LinkedIn, Threads, Instagram, Facebook, TikTok and a blog — with each platform’s real limit enforced as it drafts, the same weighted count you just used. Then it schedules and publishes through the official APIs.

Start free — no card → Free plan, then $12/month Pro or $39/month Siete. Flat, not per seat. Self-hostable.

This counter, inside the composer

The tool above is the same code that runs in PostSiete Lens, a free Chrome extension that puts the real count inside X, LinkedIn, Instagram, Threads and Facebook while you type. No account, no trial. It reads — it never posts for you.

Add to Chrome — free →

Questions

Do links count toward the 280?

Yes, at a flat 23 each, regardless of the link's real length. Shortening a URL before posting saves you nothing on X.

Does a longer post exist?

X has offered longer posts to paying subscribers. The 280 figure here is the standard limit that applies without a subscription, which is what this counter measures against.

Why does my Japanese post get rejected at 150 characters?

Because it is 300 to X. CJK characters cost 2 each, so the practical limit for Japanese, Chinese or Korean text is about 140 visible characters.

Is anything I type sent to a server?

No. The counting runs in your browser; the page makes no network request with your text.

Check this counter against open test data

This page's counter passes 74 published conformance vectors — the light and heavy weight ranges, joined emoji, the flat 23 for URLs and where a link starts and stops, the dotted words that must not be charged as URLs, NFC normalisation, and the exact range boundaries. Each one shows the arithmetic behind its expected value. It also passes the 1,706 cases in X's own test suite that concern counting and links.

In a hurry? Five cases, one minute — paste them into whatever counter you use and see how it does.

They are served as JSON with CORS enabled so you can run them against your own implementation. We publish them because our counter has been wrong three times, and each time the only thing that found it was testing against what X publishes rather than against a second copy of our own code.

Sources