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()
CallTextBuilder::make() with an optional initial string to create an instance.
text()
Append more text to the current content.newLine()
Append one or more newlines. Thecount parameter defaults to 1.
toPost()
Convert the builder to aPost record ready to pass to Bluesky::post().
Post::build()
You can also usePost::build() with a closure. The return value is a Post.
Adding mentions (@mention)
Use mention() to add a mention facet.
Automatic DID resolution
Ifdid 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.Embedding links (URLs)
Uselink() to add a link facet.
uri is omitted, text is used as the URI.
Adding hashtags
Usetag() 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
UsePost::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.
Custom facets
Usefacet() 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
Source: src/RichText/TextBuilder.php