Standard.site cards
How to make links to your own site render as rich publication cards on Sifa, Bluesky, and other AT Protocol apps. Colours, icon, troubleshooting.
When you share a link on Sifa ID or Bluesky, the app tries to render a rich preview card: title, reading time, author, site name, accent colour, action button. The content and the look of that card come from a site.standard.publication record on the PDS of the publisher. They do not come from the linked page. Control that record, and you control the card.
This page shows how to make the card appear and how to set its colours and icon. It also names the traps on the wrong verification route.
What you actually need
One site.standard.publication record on your PDS, with a url field that matches your link.
That is the whole mechanism. The Bluesky AppView indexes Standard.site records from the firehose. It matches them to post links by URL, with this matching logic. Sifa does the same. The card itself needs no DNS record, no /.well-known/ file, and no page metadata.
Three ways to get the record
You do not need to publish the record by hand. Most people do not.
-
Use a Standard.site app: Leaflet, pckt.blog, or Offprint create and maintain the record for you when you set up a publication on their platform. The publication's
urllives on that platform (yourname.leaflet.pub, etc.), so links to that URL get the card. The card's action button shows "Subscribe on Leaflet" (or pckt / Offprint) because Bluesky's social-app ships a hardcoded allowlist for those three hosts. -
WordPress with the ATmosphere plugin: installing it gives your WordPress site a publication record automatically, with
urlset to your blog. The card's button reads "View publication" with a generic arrow icon (your domain is not on the hardcoded allowlist, so no platform branding). Functionally identical otherwise. -
Roll your own: if you have your own AT Protocol account and a static blog, publish a
site.standard.publicationrecord on your PDS pointing to your domain. Steve Simkins' Sequoia CLI is the most common tool. The card again reads "View publication." Hannah Shelley's Neocities walkthrough is a good real-world example of the roll-your-own path.
On all three routes, the card appears on Bluesky and Sifa after the AppView indexes the record. That takes seconds.
With nothing in place yet, start at the Standard.site implementations guide. Create the publication record first. The rest of this page covers the look of the card, and it matters only after the record exists.
You do not need DNS TXT or /.well-known/ for the card
Community write-ups sometimes suggest you do. You do not. These files are real, they just serve different purposes:
| File / record | What it does |
|---|---|
DNS TXT _atproto.<domain> with did=<your-did> | AT Protocol username resolution. Makes a custom domain work as a Bluesky / Sifa username. |
/.well-known/atproto-did (plain-text DID) | Same purpose as the TXT record: username resolution. Useful when you cannot set TXT records, or for usernames too long for DNS. |
/.well-known/site.standard.publication (AT-URI) | Standard.site verification: proves your domain controls the publication record. A strict reader can refuse to trust an unverified record. |
None of these get the card to render. The AppView does not fetch your website. It reads from the PDS.
Hosts that block /.well-known/
Ghost Pro, some managed WordPress hosts, and certain SaaS site builders block the /.well-known/ path or do not let you set DNS TXT records. That does not affect the card. It still appears as long as the publication record exists with a matching url.
What you do lose on those hosts:
- Using your custom domain as a Bluesky / Sifa username.
- Standard.site verification, which strict consumers such as Sequoia and some third-party readers may need.
If those matter, move to a host where you control DNS or /.well-known/. Most static hosts (GitHub Pages, Netlify, Cloudflare Pages, plain nginx) let you serve files at any path.
Setting the card's button colour
The button colour comes from the publication record's basicTheme block, not from your site.
If you publish through Leaflet, pckt, or Offprint, the platform sets it for you. For roll-your-own and ATmosphere WordPress setups, you set it yourself.
Easiest route: edit the record on pdsls.dev.
pdsls.dev is a web UI for AT Protocol records. Sign in with your username and an app password, under Bluesky → Settings → App Passwords. Open your site.standard.publication record. Paste this basicTheme block into the existing JSON:
"basicTheme": {
"$type": "site.standard.theme.basic",
"accent": {"$type": "site.standard.theme.color#rgb", "r": 49, "g": 116, "b": 143},
"accentForeground": {"$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255},
"background": {"$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255},
"foreground": {"$type": "site.standard.theme.color#rgb", "r": 0, "g": 0, "b": 0}
}| Field | Used for |
|---|---|
accent | Button background |
accentForeground | Button text |
background | Card body background (not used by Bluesky's renderer yet; Sifa may use it) |
foreground | Card body text (same) |
Replace the RGB values with your brand colours. Only accent and accentForeground change anything visible on Bluesky today. The renderer derives the hover state (5% darker) and the disabled state (5% lighter) from accent, so you set neither.
The lexicon needs all four colours, even the ones that the current Bluesky card ignores. The PDS rejects a record without them. Other Standard.site renderers use the full set on the publication page, and the card may use them later.
Save the record. Within seconds the AppView indexes the new version and your next link on Bluesky or Sifa shows the coloured button.
Setting the card's icon
The icon on the card is a blob reference on the record. It is square, at least 256×256, and 1 MB at most. Two paths:
Reuse a blob you already have
In AT Protocol, a hash addresses each blob, and records on the same PDS can share it. Did you already upload an image to Bluesky as an avatar, a banner, or a post image? Paste that exact blob object into the icon field of the publication record. You upload nothing.
Fetch your profile record:
curl -s "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=<your-username>&collection=app.bsky.actor.profile&rkey=self" | jq .value.avatarThe output looks like:
{
"$type": "blob",
"ref": {
"$link": "bafkreih7flx4skmjivhxle5eqod6jo44k4ux4ewvkbjq6ycha4iwb27j7y"
},
"size": 466276,
"mimeType": "image/jpeg"
}Copy the whole object, open your site.standard.publication record on pdsls.dev, paste it as the icon field, save.
The same trick works for your banner, or for any image that you posted. Every blob already on your PDS is available.
Upload a new image
pdsls.dev intentionally does not expose blob upload, so for a fresh image you need a CLI or a small script.
With goat:
goat blob upload your-icon.png
goat record update --rkey <your-publication-rkey>Plain bash + curl + jq:
#!/usr/bin/env bash
set -euo pipefail
USERNAME="your.username"
IMAGE="${1:?usage: ./set-icon.sh path/to/image.png}"
RKEY="<your-publication-rkey>"
COLLECTION="site.standard.publication"
PDS="https://bsky.social" # or your own PDS endpoint
read -srp "App password: " APP_PW; echo
MIME=$(file -b --mime-type "$IMAGE")
SESSION=$(curl -sS -X POST "$PDS/xrpc/com.atproto.server.createSession" \
-H "Content-Type: application/json" \
-d "{\"identifier\":\"$USERNAME\",\"password\":\"$APP_PW\"}")
JWT=$(echo "$SESSION" | jq -r .accessJwt)
DID=$(echo "$SESSION" | jq -r .did)
BLOB=$(curl -sS -X POST "$PDS/xrpc/com.atproto.repo.uploadBlob" \
-H "Authorization: Bearer $JWT" -H "Content-Type: $MIME" \
--data-binary "@$IMAGE" | jq -c .blob)
CURRENT=$(curl -sS "$PDS/xrpc/com.atproto.repo.getRecord?repo=$DID&collection=$COLLECTION&rkey=$RKEY" | jq .value)
NEW=$(echo "$CURRENT" | jq --argjson blob "$BLOB" '. + {icon: $blob}')
curl -sS -X POST "$PDS/xrpc/com.atproto.repo.putRecord" \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d "$(jq -n --arg repo "$DID" --arg coll "$COLLECTION" --arg rkey "$RKEY" --argjson record "$NEW" \
'{repo:$repo,collection:$coll,rkey:$rkey,record:$record}')" | jq .Edit USERNAME, RKEY, and PDS, then run:
chmod +x set-icon.sh
./set-icon.sh ~/path/to/your-avatar.pngThe script logs into your PDS, uploads the image, fetches the publication record, adds the icon blob ref, and writes the record back.
Finding your rkey
In pdsls.dev, your publication record's URL ends in something like .../site.standard.publication/3mgfeypogdk2r. The string after the last / is the rkey.
You can also list your records with the API:
curl -s "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=<your-username>&collection=site.standard.publication" | jq .Verifying it works
Post a link to your publication URL on Bluesky and watch the embed render. If the card does not appear:
- Confirm that a
site.standard.publicationrecord exists on the PDS of your account. Browse it on pdsls.dev. - Confirm that the
urlfield of the record matches your link. The AppView canonicalises host case and the trailing slash, and it drops the query and the fragment. Beyond that it needs an exact match. Leaflet, pckt and Offprint also accept subpaths. - Give the firehose a minute to propagate.
One record, every AT Protocol app
The site.standard.publication record lives on your PDS as a plain AT Protocol record. Bluesky is one app reading it. Sifa is another. Any app that indexes the firehose can render your publication however it likes. You do not sign up for anything.

Same data, different readers, and each reader takes the parts it wants. Set the record once, and you finish. That is the point of an open protocol, against the API of one platform.
Lexicon caveats worth knowing
- Bluesky raised its own profile-avatar upload limit to 2 MB in v1.121 (April 2026). The
site.standard.publicationlexicon still caps the publication icon at 1 MB. The PDS validates against the lexicon, so it refuses a larger file at write time. - The lexicon needs all four
basicThemecolours, and the current Bluesky card uses onlyaccentandaccentForeground. The validator refuses a record that drops the others. - We recommend the well-known verification file in production. The Bluesky card never checks it. Sequoia and some readers do.
Company pages
Every organization on Sifa has a shared page at /c/<domain>. It starts as an auto-generated stub built from public company data, and the people who work there connect to it from their profiles.
Move your account to another provider
How to migrate your Atmosphere account to another provider and keep your followers, posts, profile data and Sifa records. Browser tools and CLI options.