Skip to content

Set up · Bluesky

Your buddy on Bluesky

A Block Buddy is a face for your agent, drawn from its name. Bluesky shows an account's avatar as a circle, so use the pad file: a PNG under 1 MB, the whole tile on its own colour.

What Bluesky shows: a circle, beside every post and on the profile.

Get the files

Drawn in your browser from the name above. Nothing is uploaded anywhere.

No animated file here: Bluesky takes only PNG and JPEG avatars, and re-encodes them. There's no banner file yet.

Set it in Edit profile

  1. Download the Avatar (the 1024 × 1024 PNG).
  2. In Bluesky, open your Profile and tap Edit profile.
  3. Tap the avatar, then Upload from Files (on a phone, Upload from Library), and choose the PNG.
  4. In the circle cropper, keep the whole tile inside the circle and confirm. Then tap Save.
  5. If the account is an agent: go to Settings → Account → Automation label and turn on Show automation label. Its posts then say "Automated account".

For agents

An agent can set its own avatar. Upload the PNG with uploadBlob, then write it into the profile record with putRecord. putRecord replaces the whole profile, so read it first and keep the name, bio, banner and labels.

Instructions
platform: bluesky (atproto profile avatar)
crop:     circle, applied by the client
variant:  pad (the tile at 74%, centred on its own ground colour, opaque)
file:     brass-otto-pad-1024.png
format:   PNG or JPEG, at most 1,000,000 bytes. No other type is accepted.
buddy:    Brass Otto
hash:     0x4f7857573d121112474507868c95e98e2cbb66d73cc61e786482793f28ed9bb0
method:   com.atproto.repo.uploadBlob, then com.atproto.repo.putRecord
record:   collection app.bsky.actor.profile, rkey self, swapRecord = the previous record's cid
field:    record.avatar = the blob uploadBlob returns. Merge, don't replace: keep displayName, description, banner, labels.
label:    record.labels = {$type: com.atproto.label.defs#selfLabels, values: [...existing, {val: bot}]}
per-mode: possible (pad-<mode>-512.png), but each swap is a public commit and clients cache profiles. Big moments only: offline, back.
auth:     an app password (Settings > Privacy and security > App passwords) or OAuth
limits:   5,000 write points an hour; an update costs 2
checked:  2026-10-21
TypeScript, @atproto/api
import { readFile } from 'node:fs/promises'
import { AtpAgent } from '@atproto/api'

const agent = new AtpAgent({ service: 'https://bsky.social' })
await agent.login({ identifier: process.env.BSKY_HANDLE, password: process.env.BSKY_APP_PASSWORD })

// 1. upload the image
const png = await readFile('brass-otto-pad-1024.png')
const { data } = await agent.uploadBlob(png, { encoding: 'image/png' })

// 2. read, merge, write (upsertProfile does the compare-and-swap for you)
await agent.upsertProfile((prev = {}) => {
  const kept = prev.labels?.values?.filter(l => l.val !== 'bot') ?? []
  return { ...prev, avatar: data.blob,
    labels: { $type: 'com.atproto.label.defs#selfLabels', values: [...kept, { val: 'bot' }] } }
})

Limits

FormatPNG or JPEG only
Max size1,000,000 bytes
DimensionsNo limit in the record. The app squares the image and serves it at about 1000 × 1000, so 1024 is right.
CropCircle, for accounts
AnimationNo
Who can set itYou, in the app. Or the agent itself, through the API.
Per-mode facesPossible, but slow to show: each change is a public commit, and profiles are cached. Save it for offline and back.

Last checked 2026-10-21 against the app.bsky.actor.profile lexicon, Editing profiles, Bots and Rate limits. App labels come from the Bluesky app's own source. If a step is wrong, the platform's own docs win.