In the previous post, we learned the basics of posting to Bluesky.

Today, we are taking it a step further by adding tags to the post.

You might think that it's as easy as just adding tags as text prepended with a # sign, but it is a little more complicated than that unfortunately.

If you add the tags as plain text, you'll see that they are represented that way in Bluesky and that you are unable to click on them.

To add real tags in your Bluesky post, we need to use a concept called "facets". Facets can be multiple things such as tags, mentions and clickable links.

Let's see how they work.

Step 1: Get the list of tag facets from the text

We will start with creating a helper function which can extract the tags from the text and return the proper data structure as expected by Bluesky:

defmodule BlueskyHelpers do
  def get_tags_as_facets(text) do
    Regex.scan(~r/#\S+/, text, return: :index)
    |> Enum.map(fn [{start, length}] -> tag_to_facet(text, start, length) end)
  end

  def tag_to_facet(text, start, length) do
    tag =
      text
      |> String.slice(start, length)
      |> String.trim_leading("#")

    %{
      index: %{
        byteStart: start,
        byteEnd: start + length
      },
      features: [
        %{
          "$type": "app.bsky.richtext.facet#tag",
          tag: tag
        }
      ]
    }
  end
end

What this code does is:

  • It will use a regular expression to extract the tags from the text
  • It will then tranform each tag into the proper facet data structure

The helper function for turning the tag into a facet does:

  • It will use the start index and the length to get the actual tag text
  • It will then create a map with the index containing the byte start and end of the tag (including the # sign)
  • It will use the type app.bsky.richtext.facet#tag to indicate that this is a tag facet
  • It will specify the actual tag for the facet (without the # sign)

Step 2: Creating the post record

This is again the same structure as in the previous post, but with one change.

We now added the list of facets:

# Create the current timestamp in ISO format with a trailing Z
created_at = DateTime.utc_now() |> DateTime.to_iso8601()

# The text to post
text = "Hello from Elixir #MyElixir #example"

# Get the list of facets
facets = BlueskyHelpers.get_tags_as_facets(text)

# Create the post record
record =
  %{
    text: text,
    createdAt: created_at,
    "$type": "app.bsky.feed.post",
    langs: ["en-US"],
    facets: facets # Add the list of facets
  }

Step 3: Post to Bluesky

We can now use the same code from the previous post to publish the record:

%{"commit" => %{"rev" => rev}} =
  Req.post!(
    "https://bsky.social/xrpc/com.atproto.repo.createRecord",
    auth: {:bearer, token},
    json: %{
      repo: did, # The did value from step 1
      collection: "app.bsky.feed.post", # We want to post to our timeline
      record: record # The record we want to post to our timeline
    }
  ).body

If all goes well, the tags should show up in the post as clickable items.

Next time, we'll continue with facets to enable mentioning other users.