Bluesky character counter

Counts a post the way the Bluesky app does: by grapheme (what you see as one character), with links shortened first. Everything runs in your browser; nothing is sent anywhere.

0/ 300 in the app

What gets posted

Other counts

Graphemes in the app (links shortened)
Graphemes as typed (no shortening: what an API client sends unless it shortens links itself)
UTF-8 bytes as typed (limit 3000)
Unicode code points
JavaScript .length (UTF-16 units)

How Bluesky counts characters

What is the Bluesky character limit?

300 per post. The post record allows at most 300 graphemes and 3000 UTF-8 bytes of text. The app shows the grapheme count next to the Post button and won't let you post while it's over 300.

Do emoji count as one character?

Yes, if you see one symbol. Bluesky counts graphemes, so ๐Ÿ™‚, ๐Ÿ‡ซ๐Ÿ‡ท and ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง each count as 1, even though the family emoji is 5 code points and 18 bytes. Accented letters typed as two code points (e + โ—Œฬ) also count as 1.

Do links count against the 300?

Only their short form. Before counting, the app replaces each link that starts with http:// or https:// with the host + the first 13 characters of the path + ..., if the path (counting the leading /, query and #fragment) is longer than 15 characters. That short text is what gets posted. The link itself still goes to the full URL. Links typed without https:// (like example.com/some/long/path) are not shortened and count in full. Hosts with accents become punycode (xn--...) and non-ASCII path characters become %XX codes.

My text is too long. Can I make it a thread?

Yes: the thread splitter cuts long text into posts that each fit, counted the same way as here, and breaks at paragraphs and sentences where it can.

What else changes when I post?

Leading blank lines and trailing whitespace are trimmed, and 3 or more line breaks in a row become 2.

My post fits in the app but fails through the API. Why?

The 300 limit is checked on the text you send. The app sends the shortened text; a script that sends the raw text with full URLs can go over. Shorten link text yourself and keep the full URL in the link facet. facet-check builds those facets with the right byte offsets.

Limits, honestly

This mirrors the bsky.app web code (toShortUrl, shortenLinks, the post-time trim) and the official link detection, and was checked against @atproto/api plus that code on 20,019 texts with 0 differences. Grapheme counts come from your browser's Intl.Segmenter, as in the app, so very new emoji may count differently on an old browser. The iOS and Android apps run the same code, but their URL parser could differ on unusual links. @mentions don't change the count. Found a mismatch? Tell me in an issue.