Skip to main content

What is TextBuilder?

TextBuilder is a fluent builder class for constructing AT Protocol facets — the rich-text annotations that power mentions, links, and hashtags in Bluesky posts. A Bluesky post body is plain text, but to render mentions, links, and hashtags the API also requires a facets array describing each annotation’s byte offset range and type. TextBuilder handles the offset calculation and array construction automatically.

Basic text creation

TextBuilder::make()

Call TextBuilder::make() with an optional initial string to create an instance.

text()

Append more text to the current content.

newLine()

Append one or more newlines. The count parameter defaults to 1.

toPost()

Convert the builder to a Post record ready to pass to Bluesky::post().

Post::build()

You can also use Post::build() with a closure. The return value is a Post.

Adding mentions (@mention)

Use mention() to add a mention facet.

Automatic DID resolution

If did is omitted, the DID is resolved automatically from the handle via Bluesky::resolveHandle().

Explicit DID

Provide the DID directly to skip the resolution API call.
Cache resolved DIDs in production to reduce the number of resolveHandle API calls when mentions appear frequently.
Use link() to add a link facet.
When uri is omitted, text is used as the URI.

Adding hashtags

Use tag() to add a hashtag facet.

Composing rich text

Combine multiple facets to build a fully annotated post.

Using Post::build()

Integration with posts and notifications

With Bluesky::post()

With notification channels

Use Post::build() inside the toBluesky() method of a notification class.

Automatic facet detection

detectFacets() scans the current text for @mentions, URLs, and #hashtags and populates facets automatically.
detectFacets() uses regex-based detection. For guaranteed linking, use link(), mention(), and tag() explicitly.

Custom facets

Use facet() to append an arbitrary facet array directly.

Character limits and important notes

Byte offsets vs. grapheme clusters

AT Protocol facet indices use UTF-8 byte offsets. TextBuilder uses strlen() internally to calculate these byte positions. Multi-byte characters (Japanese, emoji, etc.) consume more than one byte per visible character, so offsets are based on bytes, not displayed characters.

Post character limit

Bluesky limits posts to 300 grapheme clusters (visible characters), not bytes. Japanese and emoji characters still count as individual graphemes, so you can write 300 Japanese characters just as you can 300 ASCII characters.
TextBuilder does not validate character count. If you exceed 300 graphemes, the AT Protocol API returns an error.

Method reference

Last modified on April 26, 2026