OG Image Generator
EditorCheckerBlogPricing

OG Image Generator

Free online tool to create beautiful Open Graph images for your content. No signup required.

Links

  • OG Image Generator
  • OG Checker
© 2026 OG Image Generator. All rights reserved.
PrivacyTerms
Back to Blog

Bluesky Link Preview: How Its Card Service Reads OG Tags

Nov 3, 2026·9 min read

Bluesky does not read your HTML when someone pastes your link. Its client hands the URL to a service at cardyb.bsky.app, takes back a small JSON object, and draws the card from that. We called the same endpoint 59 times with homepages of sites you would recognize, on November 3, 2026. Fifty-six came back with a card. Forty-seven carried an image. Every image we could trace matched the page's og:image exactly — same URL string, same bytes.

So the tag you write really is the picture that appears. What the service inserts between you and the timeline is a proxy, and that proxy is where the surprises live.

What the Card Service Actually Returns

One GET, one query parameter, eight fields in the answer. This is the whole response for github.com:

GET https://cardyb.bsky.app/v1/extract?url=https%3A%2F%2Fgithub.com

{
  "error": "",
  "likely_type": "html",
  "url": "https://github.com/",
  "title": "GitHub · Change is constant. GitHub keeps you ahead.",
  "description": "Join the world's most widely adopted, AI-powered developer platform...",
  "image": "https://cardyb.bsky.app/v1/image?url=https%3A%2F%2Fimages.ctfassets.net%2F...",
  "icon": "https://cardyb.bsky.app/v1/favicon?url=https%3A%2F%2Fgithub.com",
  "twitter_card": "summary_large_image"
}

Read the image and icon lines closely. Neither points at github.com. Both are rewritten to cardyb.bsky.app, with your asset URL tucked inside as a parameter. All 47 images and all 56 favicons in our sample came back rewritten that way — no exceptions.

Real talk: this is the detail that turns a five-minute fix into an afternoon. The card in the timeline is served from Bluesky's host, carrying Bluesky's copy of your file. Ship a new PNG at the same og:image URL and you have no way to tell from outside whether the card service already picked up the old one; the extract endpoint returns no cache header we could read. Test the card again instead of trusting the deploy.

Fifty-Nine URLs, Counted

Method: a plain GET on the extract endpoint, no JavaScript, one request per homepage, plus a separate fetch of each page's server HTML so we could compare what the page claims against what the service returned. Our HTML fetch used a desktop Safari user agent and never ran scripts.

MeasuredResultWhat it means for you
URLs queried59Homepages only, one request each
Card returned56 of 59Three refusals, all covered below
Card with an image47 of 56 (84%)Nine cards went out as a text block
Image identical to og:image39 of 39 verifiableNot one rewritten, not one dropped
Image taken from twitter:image0 of 56og:image wins every time it exists
Title identical to og:title41 of 56The rest are pages with no og:title to use
Empty url field7 of 56All seven pages ship no og:url
Icon proxied56 of 56Even the favicon is copied
PDF or direct image link0 of 2 produced a cardNon-HTML gets nothing at all

Thirty-nine is the number we trust most. Those are the pages where we could read the server HTML and see its og:image, and in every single one the image field held that exact string, wrapped in the proxy. The other eight images belonged to pages that blocked our fetch — medium, reddit, producthunt, stackoverflow, openai, reuters, cnn and spotify — so we could confirm a card existed but not where its picture came from.

When It Refuses

Three URLs produced nothing. npmjs.com and behance.net both answered our request with 403, and the service came back empty with the string "Unable to generate link preview". A made-up path on smashingmagazine.com behaved the same way. The pattern is dull and worth remembering: when the origin refuses a crawler, the card does not degrade — it vanishes.

Then we tried content that is not a webpage. A hosted PDF and a direct .png both returned the same refusal with every field blank. Bluesky link cards are an HTML feature. Send someone a link to your deck and there is nothing to draw.

The opposite case is more interesting. microsoft.com handed our Safari fetch a page that said, in so many words, that our request had been blocked. The card service pulled the real title and description anyway. Its fetcher clears bot walls that stop a curl from a VPS. Put those two facts together and the debugging rule flips: a card working proves nothing about what your own fetch sees, and your local curl being blocked proves nothing about the platform.

Which Tag Wins, And Which One Is Decoration

og:title won 41 times. Twice the service fell back to the HTML title element — news.ycombinator.com and theguardian.com/international, the only two pages in the sample that ship no og:title at all. The fallback works, which is why a missing og:title does not break a card; it just hands the headline to whatever your page title happens to be, suffix included.

twitter:image lost every contest. Fifty-six cards, zero of them built from a Twitter-side image, even on pages that had both. If you maintain two sets of tags and only update the Twitter one, Bluesky will show the older picture.

The twitter_card field came back populated with values like summary_large_image. Whether the app changes its rendering because of it, we cannot see from the outside, so treat the field as passthrough rather than a switch.

Then there is og:url. Seven cards carried an empty url field, and all seven pages ship no og:url — nextjs.org/blog, the wikipedia.org portal, linear.app, tailwindcss.com, news.ycombinator.com, theguardian.com/international, and our own homepage. Where the tag exists, the service echoes an absolute URL. Cards still render without it. Providing it costs one line and keeps the link target honest when the same content answers on more than one host.

The Spotify Case Breaks The Simple Story

open.spotify.com ships exactly one Open Graph property in its server HTML: og:site_name. That is it. Our parse found a title element reading "Spotify – Web Player", no og:image, no og:description.

The card service returned a longer title, "Spotify - Web Player: Listen to Music, Podcasts, and Audiobooks", and an image from spotifycdn.com that does not appear anywhere in the bytes we received. Two explanations fit: its fetcher executes scripts, or it receives a server variant our user agent never gets. We could not separate them from outside, and we are not going to pretend otherwise.

Both readings lead to the same instruction. Do not assume client-side rendering protects you, and do not assume an empty server HTML means an empty card. It means you have no idea what the card will contain until you look at the card.

A Checklist That Survives This Service

  • Ship og:image and confirm the URL resolves. Our proxy pass-through test returned github's 126,590-byte JPEG bit-identical, and an older figma asset came back as a 9.4 MB GIF with no sign of a size cap on the way. Size is your CDN's problem. A 404 is the card's problem.
  • Keep og:title and og:description separate. Where the page had an og:title, the service used it verbatim — no stripping of site names, no truncation we could detect at the field level. Write the first sentence of the description to stand alone.
  • Do not lean on twitter:image. Zero of 56 cards used it. Duplicate your image tag into both properties or watch the Twitter one go stale.
  • Add og:url even though cards render without it. Seven cards in the sample shipped a blank url field because the page never declared one.
  • Test with the endpoint, not your eyes. One GET to cardyb.bsky.app/v1/extract with your URL encoded shows exactly the title, description and image Bluesky will use. Errors arrive as text, not as a broken-looking preview you might miss in your own timeline.
  • Accept that HTML is the entry fee. PDFs, images and other non-HTML links returned nothing at all in both of our tests.

One Line

Bluesky outsources link previews to a single extraction service, that service reads your Open Graph tags faithfully, and then it copies your picture onto its own host so the timeline never touches your server. Forty-seven of 56 cards in our sample had an image; the nine that did not were pages that simply never declared one, or pages that refused the crawler outright. The gap is on your side of the proxy, and it takes one tag to close.

Paste your URL into the free OG checker to see every property a scraper can read from your server HTML, then build the image itself at ogimgen.com in about thirty seconds. Related reading: the full Open Graph tag list sets out which properties are required, the OG image size guide covers dimensions and safe zones, and the og:url guide explains the seven empty url fields above. For other networks, our Telegram and Discord breakdowns measure the same tags against very different caches.

BlueskyLink PreviewOpen Graph

Create your OG image now

Free, no signup, instant download. 5 templates to choose from.

Open Generator →

Related Articles

Threads Link Preview Not Working? What Meta's Crawler Sees

Nov 4, 2026

og:locale: The Tag 43 of 74 Image Pages Skip (91 Measured)

Nov 2, 2026

og:site_name: The Tag Google Reads Twice (66 Pages Measured)

Nov 1, 2026