Guide

Bluesky link preview: what the website card reads

You paste a URL on Bluesky and get a bare link, or a card that still shows yesterday’s title. Bluesky reads og:title, og:description, and og:image, then stores that website card inside the post.

The card is an app.bsky.embed.external embed: a uri, a title, a description, and an optional thumb image. Bluesky’s posts guide (Website card embeds) shows the client fetching the page, parsing those three Open Graph tags, and uploading the image with com.atproto.repo.uploadBlob. Each client does that work when the post is written. Readers then see the snapshot in the post, so your site is not fetched again on every view.

Check a live URL on CardScope. The free check shows the meta tags and whether og:image and og:title are set — a few checks a day. It is not a screenshot of Bluesky, and it does not rewrite a card already stored in a post. A paid check of every app still shows X, LinkedIn, Slack, Discord, and iMessage.

To sketch the words and the picture before the page is live, the link preview mockup draws X, Facebook, LinkedIn, Slack, Discord, and iMessage from text you type. It does not draw a Bluesky card. The tags are the same Open Graph set.

Checklist before you share

  1. og:image is an absolute https:// URL. The sample in the posts guide turns a relative image into an absolute one by joining the page URL and the image string when the image has no ://. That join is naive: /og.png on https://example.com/blog/post becomes https://example.com/blog/post/og.png. An absolute URL skips that line. See absolute OG image URL.
  2. og:title names the page. A brand-only title is a weak card. Length and wording are in OG title & description.
  3. og:description is one sentence that still makes sense on its own.
  4. The page and the image answer a public fetch. No login, no signed cookie the client does not have. Open the image URL in a private window.
  5. The tags are in the first HTML response. The client parses the HTML it fetched. It does not run your JavaScript. See JavaScript / SPA previews.
  6. CardyB can fetch the page. A WAF challenge, a bot block, or robots.txt can prevent the card. See crawler blocked.

Bluesky’s posts article does not publish a pixel size or a file-format list. 1200×630 PNG or JPEG is the usual cross-app default in OG image size and OG image format, not a number from that article. The Open Graph image resizer crops to 1200×630 in the browser. The Open Graph tag generator writes the tags. Neither one posts to Bluesky.

A minimal set

This is the set the official sample looks for. Width and height are not in that sample. They are here because the other apps use them, and because you will check this page in CardScope too.

<meta property="og:title" content="A specific title, not the brand name">
<meta property="og:description" content="One sentence that matches the page.">
<meta property="og:image" content="https://example.com/og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://example.com/permalink">

The embed the client stores looks like this. thumb is the uploaded image blob, and it is optional when there is no og:image.

"embed": {
  "$type": "app.bsky.embed.external",
  "external": {
    "uri": "https://example.com/permalink",
    "title": "A specific title, not the brand name",
    "description": "One sentence that matches the page.",
    "thumb": { "$type": "blob" }
  }
}

Check the live HTML

Read the first response, not the Elements panel after JavaScript has run. These three requests are enough to see what a client can parse.

curl -sL https://example.com/your-page | grep -iE 'og:title|og:description|og:image'
curl -sL -A 'Mozilla/5.0 (compatible; Bluesky Cardyb/1.1; +mailto:support@bsky.app)' \
  https://example.com/your-page | grep -iE 'og:title|og:description|og:image'
curl -sL 'https://cardyb.bsky.app/v1/extract?url=https%3A%2F%2Fexample.com%2Fyour-page'

CardyB is the link-meta helper the official app uses. https://cardyb.bsky.app/v1/extract?url= followed by the page URL returns JSON with title, description, and image when the fetch works. The image value can be a cardyb.bsky.app URL for the picture it found, rather than your raw og:image string. A title and an image in that JSON means the helper read the page.

The user agent commonly includes Bluesky Cardyb. One current example is Mozilla/5.0 (compatible; Bluesky Cardyb/1.1; +mailto:support@bsky.app). The version can change, so match the name, not a single version string. View-source on the same URL should show the same og: tags in the HTML the server sent.

After you change the tags

The card is baked when the post is composed. Fixing og:title or og:image later does not update posts that already exist. There is no Facebook-style “rescrape this URL for everyone” debugger for an ordinary Open Graph card.

Confirm the live tags first (curl, view-source, or a CardScope check). Then compose a new public post, or a draft, that includes the URL. That new post is the check. Clients and tooling may change, so treat a public post as the confirmation, not this page.

Clear Open Graph cache is for Slack, X, LinkedIn, Discord, Facebook, and iMessage, where a client may keep an old card and a few of them have a public debugger. Bluesky differs from those cache-debug platforms: the ordinary card lives in the post record, and CardScope does not edit posts.

If the card never appears

Start with the fetch. If curl as the CardyB user agent returns a challenge, a 403, or a login page, the tags on your real page do not matter yet. The same is true of the image URL: the client has to download it before it can upload the blob. Crawler blocked walks the firewall, robots.txt, and login-wall cases.

If the HTML is only correct after JavaScript runs, the parser in the posts guide will not see the tags. Put them in the server response. That is the JavaScript / SPA case.

Richer embeds are a different path

Some AT Protocol publishers also attach enhanced embeds with Standard.site records (site.standard.*). That is separate from the ordinary Open Graph card this page describes. This guide does not cover those records. If you publish them, use the Bluesky posts guide and the AT Protocol docs, and do not treat this checklist as that API.

FAQ

What does Bluesky read for a link card?

og:title, og:description, and og:image. The client turns those into an app.bsky.embed.external embed and, when there is an image, uploads it with com.atproto.repo.uploadBlob. The result is stored on the post.

Why does an old post still show the old card?

The snapshot was written when that post was composed. A tag change on the site does not edit it. Compose a new post, or a draft, with the URL. There is no Sharing Debugger equivalent that refreshes every existing post.

How do I see what CardyB reads?

Open https://cardyb.bsky.app/v1/extract?url= plus your URL, or curl it. Look for title, description, and image. Then confirm the same tags in view-source.

Can a firewall stop the card?

Yes. The page and the image have to be publicly fetchable. A challenge, a bot block, or robots.txt can prevent the card. Allow the fetch, including a user agent that contains Bluesky Cardyb.

What does a CardScope check cost?

The five previews and the fix checklist are Pro: $19 once, not a subscription. No account. A sample report is the way to see that result before you pay. The free check shows the meta tags and whether og:image and og:title are set — a few checks a day. We parse HTML in memory and discard it — we do not store the page. The sample is not a Bluesky preview either.