What Is a Grapheme and Why Bluesky Measures Them Instead of Characters
A grapheme is one unit of text as a human perceives it. It is defined by Unicode Standard Annex #29 (UAX #29) as the smallest visible unit of a written language. The word "character" is ambiguous: to a JavaScript engine, "character" usually means a UTF-16 code unit. To a server, it might mean a UTF-8 byte. To a human being reading a screen, it means a single visible symbol.
Bluesky chose graphemes deliberately. The AT Protocol's app.bsky.feed.post lexicon specifies two simultaneous ceilings: maxGraphemes: 300 and maxLength: 3000 (bytes). Whichever ceiling you hit first, your post is rejected. For almost every writer in a Latin alphabet, the 300-grapheme limit is the only constraint you will ever encounter. The 3,000-byte ceiling is a backstop for extreme cases: 300 family emoji ZWJ sequences (each one grapheme but 25 UTF-8 bytes) would total 7,500 bytes, exceeding the byte cap long before reaching 300 graphemes.
This is why a naive character counter gives you the wrong number for emoji-heavy posts, and why that wrong number causes real post failures. Bluesky enforces its limits at the API level, not just in the composer UI. A post that looks fine in a counter that measures bytes or code units can be silently rejected when it hits Bluesky's servers.
What Each Character Type Costs on Bluesky
| Character Type | Graphemes | UTF-8 Bytes | Example |
|---|---|---|---|
| Latin letter, number, space, punctuation | 1 |
1 | a, 5, ?, space |
| Accented letter (precomposed) | 1 |
2 | é, ñ |
| CJK character (Chinese, Japanese, Korean) | 1 |
3 | 語, か, 한 |
| Basic emoji | 1 |
4 | 🙂, 🚀, ✨ |
| Flag emoji (regional indicator pair) | 1 |
8 | 🇵🇰, 🇯🇵 |
| Skin-tone modified emoji | 1 |
8 | 👋🏽 |
| ZWJ sequence (family, professional) | 1 |
25+ | 👨👩👧👦, 🧑🏽💻 |
| Line break (newline) | 1 |
1 | Each newline in post text |
The practical consequence: a post written entirely in Japanese kanji can contain 300 full Japanese characters and be a valid Bluesky post. That same 300 characters costs 900 UTF-8 bytes but is still well under the 3,000-byte cap. Compare that to a post with 300 family emoji ZWJ sequences: 300 graphemes but 7,500 bytes. In that extreme case, you would hit the 3,000-byte cap at roughly 120 graphemes. Real-world posts almost never approach either extreme. Standard text with a handful of emoji hits the grapheme limit first, every time.
The graphemer Library vs. Intl.Segmenter: The Counting Discrepancy Bluesky Already Fixed
One edge case worth knowing, because older guides still cite it as an open problem: the AT Protocol's server-side validation originally used a JavaScript library called graphemer for its grapheme counting, and graphemer follows an older version of the Unicode segmentation rules than modern browsers do. For most scripts the two produce identical counts. But for some Indic scripts (Hindi, Bengali, Tamil), a single visible conjunct cluster could count as 3 graphemes under graphemer and as 1 in Intl.Segmenter, the current standard used by browsers and the official Bluesky app. The mismatch was documented as issue #4321 in the bluesky-social/atproto repository, opened October 30, 2025.
Bluesky has since fixed it. The AT Protocol's validation code now uses Intl.Segmenter directly, with the unicode-segmenter package — which also follows current Unicode — as its fallback for older environments. Server-side counting and browser-side counting now agree, including for Devanagari and other Brahmic scripts. The practical upshot: a counter built on Intl.Segmenter, like this one, matches the protocol's own counting, which is exactly the standard we count with.
Do URLs Count Toward the Bluesky Character Limit?
Yes, URLs count toward your 300-grapheme budget. But the exact cost depends on how you paste them, and this is the single most useful piece of practical knowledge for any Bluesky content creator. There are three distinct scenarios, and most guides only describe one of them.
Scenario 1: URL as plain text in your post body
When you paste a URL directly into a Bluesky post and do not generate a link card, the URL counts at its full grapheme length, approximately one grapheme per character for standard ASCII URLs. A UTM-tagged tracking URL like https://textcountify.com/article?utm_source=bluesky&utm_medium=social&utm_campaign=launch2026 is 93 characters long. That single URL consumes almost one third of your entire 300-grapheme post budget.
Scenario 2: URL as shortened display text in the official Bluesky app
The official Bluesky app at bsky.app runs a link-shortening function before counting your graphemes. If a URL's path runs longer than 15 characters, the app rewrites the display text to: domain + first 13 characters of path + ellipsis. The full URL is preserved in the rich-text facet (so the link still works), but the composer counts the shortened display text against your 300.
This means URL cost on Bluesky is variable, based on your domain length, not fixed at 22 or 23 characters. Those numbers come from Twitter's t.co shortening logic. Bluesky has no equivalent of t.co.
| URL | Raw Length | Cost After App Shortening |
|---|---|---|
| https://bsky.app | 16 chars | 8 graphemes (short path, no shortening) |
| https://bsky.app/about | 22 chars | 14 graphemes (path under 15, no shortening) |
| https://textcountify.com/bluesky-character-counter | 50 chars | 32 graphemes |
| https://example.com/article?utm_source=bluesky&utm_campaign=launch | 66 chars | 27 graphemes |
Third-party Bluesky clients do not all apply this shortening. Some count the raw URL against your grapheme budget. Your post may appear over the limit in a third-party scheduler but succeed perfectly on bsky.app. When in doubt, check your final count in the official Bluesky composer before concluding your post is too long.
Scenario 3: The link card technique (zero grapheme cost)
The link card is stored as an embedded record in the post, not as text in the post body. Deleting the URL from the text does not remove the card. Your post goes out with a rich preview and a zero-grapheme URL cost. For any post that includes a link, this technique should be your default workflow.
Bluesky vs Other Platforms: Character Limit Comparison
If you are coming from Twitter/X, Threads, or any other platform, here is exactly how Bluesky's limits compare for the same type of content.
| Platform | Post Limit | How It Counts | Key Notes |
|---|---|---|---|
| Bluesky | 300 graphemes |
Unicode graphemes (UAX #29) | Equal limit for all users, no paid upgrade |
| Twitter/X (free) | 280 characters |
Weighted character units | CJK = 2 units, emoji = 2 units |
| Twitter/X Premium | 25,000 characters |
Weighted character units | Paid tier only |
| Threads | 500 characters |
Characters | Meta platform, no grapheme distinction |
| Mastodon | 500 characters (default) |
Characters | Server-dependent, some servers allow more |
3,000 characters |
Characters | More generous for long-form posts | |
2,200 characters |
Characters | Feed truncates long posts at roughly 125 characters | |
| Discord | 2,000 characters |
Characters | Per message limit |
| Reddit (post body) | 40,000 characters |
Characters | Essentially unlimited for practical purposes |
63,206 characters |
Characters | No practical limit for most content |
Why Bluesky's 300 Graphemes Feels Bigger Than Twitter's 280 Characters
The headline numbers are 300 vs 280. The practical difference is larger, especially for emoji-heavy writers and non-Latin writers.
Emoji: On Twitter, an emoji counts as 2 character units. On Bluesky, any emoji, including a complex ZWJ family sequence, counts as 1 grapheme. A post with 10 emoji has 10 more usable units on Bluesky than on Twitter for the same visual content.
CJK and non-Latin scripts: Twitter weights Japanese, Chinese, and Korean characters at 2 units each. On Twitter's free tier, a post written entirely in Japanese effectively has a 140-character equivalent limit. On Bluesky, a Japanese character costs 1 grapheme, same as a Latin letter. A full 300-grapheme Japanese post is valid. This is one of Bluesky's most significant but least discussed advantages for non-English writers and global brands.
No paid upgrade to unlock more: Twitter charges for 25,000 characters per post. Bluesky's 300-grapheme limit is equal for all users. This is a deliberate design principle of the AT Protocol, not a business limitation. The intended answer for longer thoughts is native threads, where each post gets its own 300-grapheme budget.
How Long Are Bluesky Posts in Practice?
In a sample of 2,135 real Bluesky posts from 264 accounts, analyzed in July 2026 via the public app.bsky.feed.getAuthorFeed endpoint, the numbers tell a different story than the anxiety around the 300-grapheme limit suggests.
| Metric | Finding |
|---|---|
| Median post length | 117 graphemes |
| Posts at 140 graphemes or fewer | 55.6% of all posts sampled |
| Posts at 280 graphemes or more | 12.2% of all posts sampled |
| Posts that actually hit the 300-grapheme ceiling | 1.6% of all posts sampled |
| Accounts with no bio at all | 52.2% (from a separate 3,712 account sample) |
| Median bio length (among accounts that wrote one) | 118 graphemes (out of 256 available) |
The practical conclusion: the 300-grapheme limit is almost never the actual constraint for most writers. Over half of Bluesky posts would have fit inside Twitter's pre-2017 140-character limit. The limit becomes relevant mostly for content marketers crafting dense, link-plus-hashtag posts, non-Latin writers with longer words, or anyone trying to squeeze a full thought into one post instead of a thread.
The bio data is more striking. More than half of all Bluesky accounts have no bio. Among accounts that wrote one, the median was 118 graphemes of the 256 available. If you have a bio at all, and it is over 50 graphemes, you are already ahead of the majority of the platform. That is not a motivational statement; it is what the data from 3,712 real accounts shows.
Budgeting Your 300 Graphemes: URLs, Mentions, and Hashtags
Every element in your post consumes graphemes from the same 300-unit budget. Here is how to plan before you write.
Mentions
A mention like @alice.bsky.social is 18 graphemes. The full handle, including the @ symbol and the .bsky.social domain suffix, counts in full. Long custom domain handles can be even more expensive: @their-full-name.journalist.com might cost 30 or more graphemes before you have written a single word of your actual message. When you need to mention multiple people with long handles, consider using one mention and tagging others in a reply post.
Hashtags
A hashtag counts at its full grapheme length including the # symbol. #BlueskyTips is 12 graphemes. #SocialMedia is 12 graphemes. Two standard-length hashtags at the end of a post consume roughly 25 to 30 graphemes before you account for the space before each one. If you routinely add two or three hashtags, budget 30 to 45 graphemes for them and write your core message within the remaining 255 to 270 graphemes.
One well-chosen hashtag consistently outperforms three generic ones for discoverability. Hashtags on Bluesky are detected automatically from your post text as rich-text facets. There is no separate hashtag field. Whatever you type with a # prefix becomes a tagged link, consuming its full grapheme length from your budget.
Line breaks
Each line break in your post costs 1 grapheme. A post with ten short punchy lines (a common Bluesky style) uses 9 graphemes in line breaks alone. Posts over 25 lines are collapsed behind a "Show more" fold in the Bluesky feed, a behavior defined in the app source as MAX_POST_LINES = 25. This is not a character limit. Your text is fully there; readers just need to tap to see it. But if engagement matters, front-loading your hook before line 26 matters more than total character count for this style of writing.
Cross-Posting to Bluesky from Twitter, Threads, and Other Platforms
Bluesky's 300 graphemes sits between Twitter's 280 and Threads' 500. Here is what that means for each cross-posting workflow.
Twitter/X to Bluesky
A tweet at 280 characters almost always fits in a Bluesky post. The 20-grapheme difference is a comfortable buffer for most content. The exceptions are emoji-heavy posts: because Twitter counts emoji as 2 units and Bluesky counts them as 1 grapheme, a tweet at exactly 280 units that contains 10 emoji is actually only 270 graphemes on Bluesky (10 of those Twitter units were the second half of emoji pairs). These posts fit even more comfortably on Bluesky than they did on Twitter.
The risk direction is reversed: a tweet with heavily accented text or Indic script characters may fit on Twitter's weighted system but behave differently on Bluesky's grapheme count depending on which Unicode rules apply. Run every cross-post through the Twitter character counter and this Bluesky counter before assuming it transfers cleanly.
Threads to Bluesky
Threads allows 500 characters. Any Threads post over 300 characters needs editing for Bluesky. Common adaptation strategies: cut secondary context that is not essential to the hook, replace long calls to action with short ones, move hashtags to the end so they can be cut first if needed, and split longer posts into Bluesky threads where each post gets its own 300-grapheme budget. Our Threads character counter applies the same live check on the Threads side.
LinkedIn to Bluesky
LinkedIn posts can run to 3,000 characters. Adapting a LinkedIn post for Bluesky almost always means extracting the core argument or hook into one 300-grapheme post, linking to the full LinkedIn post with a link card (zero grapheme cost), and letting readers click through for the full text.
Bluesky Bio and Display Name: Making the Most of 256 and 64 Graphemes
The bio (256 graphemes)
Your Bluesky bio is your most-read content after your display name. It supports up to 256 graphemes. Compared to Twitter's 160-character bio and Instagram's 150-character bio, 256 graphemes is generous. But given that 52% of Bluesky accounts have no bio and the median bio is 118 graphemes, the ceiling is not the challenge. Deciding what to put there is.
Front-load your bio. The first line is what appears in profile previews before a reader taps to expand. Put your niche, role, or value proposition in the first 60 graphemes. Use the remaining space to add specifics, a call to action, or a link (which counts at full grapheme length in the bio, unlike the link card trick in posts). Line breaks count as 1 grapheme each. Bluesky bios support plain text only: no bold, no italic, no markdown formatting. For counted bio templates, a live search snippet preview, and link cost variants for the same 256 grapheme budget, use our dedicated Bluesky bio character counter.
The display name (64 graphemes)
Your display name is the bold name above your handle in the feed. 64 graphemes is enough for "First Last" plus a short descriptor or emoji. Emoji in your display name count as 1 grapheme each. A verification checkmark emoji, a flag, and a short tagline can all coexist comfortably within 64 graphemes. The display name is not your handle: it can contain spaces, emoji, and any Unicode character. Your handle has stricter rules (see the handle section below).
Bluesky Handle Character Limits and Custom Domain Verification
Standard handles (.bsky.social)
The left-label portion of a standard .bsky.social handle (the part before .bsky.social) must be between 3 and 18 characters. It can contain lowercase letters, numbers, and hyphens only. No underscores, no spaces, no capital letters, no special characters beyond hyphens. The full handle including the domain suffix can be up to 253 characters total, following standard DNS length rules.
Handles are case-insensitive in the AT Protocol, meaning @alice.bsky.social and @Alice.bsky.social resolve to the same account. But the convention is all lowercase. When you mention someone with a long handle, you pay the full grapheme cost in your post text.
Custom domain handles: Bluesky's unique identity verification feature
Bluesky is the only major social platform where you can use your own domain name as your handle, and doing so constitutes verified identity at the protocol level, with no application, no follower threshold, and no fee.
The mechanism is a DNS TXT record. You add _atproto.yourdomain.com as a TXT record pointing to your Bluesky DID, then set your handle to @yourdomain.com. Anyone who visits your profile and sees that handle can verify your identity by checking that the DNS record exists. A journalist whose handle is @nytimes.com is verifiably the New York Times account. A developer whose handle is @saif.dev is verifiably linked to that domain.
Custom domain handles follow standard DNS rules: the full handle can be up to 253 characters total, and each dot-separated segment (label) can be up to 63 characters. This is a meaningfully different constraint than the 3 to 18 character limit of the .bsky.social label. There is no other major social platform where brand and individual verification works this way.
Bluesky Threads: Writing Long-Form Content Within the 300-Grapheme Limit
When your idea does not fit in 300 graphemes, threads are the native answer. Each post in a Bluesky thread gets its own independent 300-grapheme budget. There is no overall thread length limit. A thread can be as long as you need.
Replies do not share a character budget with the original post. Post 1 gets 300 graphemes. Post 2 gets another 300. Post 10 gets another 300. The budget resets per post, not per thread. A ten-post thread gives you up to 3,000 graphemes of total content, presented as a cohesive narrative.
Strong thread structure follows a simple rule: each post should work as a standalone unit. Do not cut a sentence mid-thought across two posts. End each post with enough momentum that a reader who encounters it mid-scroll still wants to continue. Front-load post 1 with the hook: the most compelling sentence goes first, because that is what determines whether readers tap to expand the thread.
Quote posts follow the same 300-grapheme limit as standalone posts. When you quote someone else's post, your commentary must fit in 300 graphemes. The quoted post is embedded separately and does not consume your grapheme budget.
Why Bluesky Has a 300-Grapheme Limit
Bluesky's 300-grapheme limit is not arbitrary and it is not a legacy decision inherited from Twitter's 140-character era. It is a deliberate constraint embedded in the AT Protocol, the open, decentralized protocol that powers Bluesky. The AT Protocol is designed to be run by many different operators, not just Bluesky Social PBC. Keeping post records small reduces the storage and bandwidth requirements for every server in the federated network.
The limit applies equally to all users by architectural design. Unlike Twitter/X, there is no paid tier that unlocks more characters. One of the most-supported feature requests on the AT Protocol GitHub (issue #2551, opened June 2024) asks for a limit increase to 500. It remains open with no resolution. Bluesky's data suggests why: fewer than 2% of posts actually hit the ceiling. The intended tool for longer thoughts is the thread.
There was one announced change: on April 1, 2025, Bluesky jokingly announced it was lowering the limit to 299 characters for one day. That post still ranks in search results for "Bluesky character limit" questions. The limit has been and remains 300 graphemes.
Bluesky in 2026: Why the Character Limit Matters More Now
Bluesky has grown significantly. The platform reached 46 million registered users by August 2026, up from roughly 10 million in September 2024. Daily active users are estimated at roughly 3 to 3.5 million, and TechCrunch reported 10.4 million monthly active users on the Bluesky mobile app in June 2026, as the platform's focus expands beyond text posts. The United States accounts for the largest share of users, followed by Japan and Brazil.
The migration cohort matters for content strategy. Former Twitter/X users, including journalists, academics, developers, and tech-adjacent professionals, form the dominant growth segment. The Boston Globe reported 3 times more traffic referrals from Bluesky than from Threads, and 4.5 times more conversions to paying digital subscribers. These are not casual scrollers. They read, click, and subscribe.
The average bsky.app session lasts 7 minutes and 37 seconds with 5.41 pages per visit, per SimilarWeb estimates from mid-2026. Users are engaged. Writing carefully within 300 graphemes for this audience is not a formatting constraint. It is how you reach an audience that responds to good writing.
How to Use the Bluesky Character Counter
The tool is divided into five tabs — Post, Profile, DM, Alt Text, and Thread Splitter — each with its own text areas, a live grapheme counter, and a color-coded progress indicator. Here is what each one does.
- Post tab (300-grapheme limit). Type or paste your Bluesky post draft. The counter shows graphemes used against the 300 limit, with words, lines, an emoji breakdown, and a live UTF-8 byte readout of the 3,000-byte backstop ceiling alongside. Drafts that run past 25 lines get a fold warning, because the Bluesky feed collapses long posts behind a "Show more" link and Bluesky's own composer blocks posting at 301 graphemes. The URL cost estimator underneath shows exactly what the official app will count for any link you paste.
- Profile tab (display name 64, bio 256, pronouns 20). The three text fields of a Bluesky profile, side by side. Remember that line breaks in your bio cost 1 grapheme each and that profile previews typically show only the first line.
- DM tab (1,000-grapheme limit). Check longer direct messages before sending. The DM tab also shows word count.
- Alt Text tab (image, GIF, and video alt text — 2,000 each). Write and measure alt text against the accessibility limit before you attach media. Images, GIFs, and videos all share the same 2,000-grapheme app-level cap.
- Thread Splitter. Paste a long draft and it splits into 300-grapheme posts at sentence boundaries, each with its own copy button — the fastest way to turn a long passage into a Bluesky thread.
Each tab has a Copy button that copies the current text to your clipboard and a Clear button that resets the field. The tool works on desktop and mobile. Dark mode is supported and remembers your preference. And as with everything on TextCountify, your text never leaves your browser.