Skip to main content
Rich text in AT Protocol allows you to add interactive elements like mentions, links, and hashtags to your posts. The RichText class handles the complexity of encoding these elements correctly.
Always use the RichText class: JavaScript uses UTF-16 encoding while AT Protocol uses UTF-8. The RichText class handles the conversion automatically, preventing character position bugs.

Understanding Facets

Facets are annotations on text that add meaning or interactivity. They include:
  • Mentions: References to other users (@alice.bsky.social)
  • Links: Clickable URLs (https://example.com)
  • Hashtags: Topic tags (#bluesky)
  • Cashtags: Stock tickers ($AAPL)
Each facet has:
  • Index: The byte position in the UTF-8 encoded text
  • Features: What the facet represents (link, mention, tag)

Creating Rich Text

Basic Usage

Detecting Facets

Use detectFacets() to automatically find and resolve mentions and links:
detectFacets() makes network requests to resolve handles to DIDs. For offline detection, use detectFacetsWithoutResolution(), but you’ll need to resolve DIDs manually.

Creating a Post with Rich Text

Working with Mentions

Auto-detecting Mentions

Manually Creating Mentions

If you already have a user’s DID:
Let detectFacets() handle the UTF-8 byte indexing for you. Manual facet creation is error-prone.
The detector automatically:
  • Adds https:// to URLs without a protocol
  • Strips trailing punctuation (., ,, ;, :, !, ?)
  • Handles parentheses correctly
  • Validates domain TLDs

Working with Hashtags

Auto-detecting Hashtags

Hashtag Rules

  • Must start with #
  • Can contain letters, numbers, and underscores
  • Maximum 64 characters
  • Trailing punctuation is stripped

Working with Cashtags

Cashtags are stock ticker symbols:

Rendering Rich Text

Use the segments() iterator to render rich text:

Rendering as Markdown

React Rendering Example

jsx

Text Length Calculations

Grapheme Length

AT Protocol limits posts to 300 graphemes (user-perceived characters):
Always check graphemeLength, not length, when validating post length.

Validating Post Length

Modifying Rich Text

Inserting Text

Deleting Text

Example: Adding a Tag to Existing Text

Sanitizing Rich Text

Cleaning Newlines

Remove excessive newlines:

Manual Sanitization

Advanced Usage

Cloning RichText

Manual Facet Creation

For advanced use cases, create facets manually:

Best Practices

1

Always use RichText for user input

Never manually construct facets from user input. Always use RichText and detectFacets().
2

Check grapheme length, not string length

Always validate using graphemeLength for proper emoji and unicode handling.
3

Handle detection failures gracefully

Handle resolution may fail. Provide fallbacks.
4

Preserve facets when editing

When users edit posts, preserve existing facets when possible.

Common Pitfalls

UTF-8 vs UTF-16 confusion: Never use string indices directly in facets.
Not resolving mentions: Mentions must have valid DIDs.
Forgetting to await detection: detectFacets() is async.

Examples

Complete Post with Multiple Facets

Building a Rich Text Editor

Next Steps

Using the API

Learn more about the Agent API

Moderation

Implement content moderation

API Reference

Explore the complete API

Development Setup

Set up your development environment