Guide
Mastodon link preview not showing? How Mastodon builds link cards
You post a link on Mastodon and get a bare URL, a card with no picture, or a card that still shows last month’s title. Some people see the card and others do not. That is because Mastodon has no central crawler: every server that carries the post fetches your page itself and keeps its own copy of the card.
The details below come from Mastodon’s own code — FetchLinkCardService and LinkDetailsExtractor, as of the 4.7 release — and the PreviewCard and profile docs. Servers run different versions, so treat a real post as the final check.
Check a live URL on CardScope. The free check shows the meta tags and whether og:image and og:title are set. It is not a screenshot of Mastodon, and it cannot refresh a card a Mastodon server already stored. 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 Mastodon card. The tags are the same Open Graph set.
Every server fetches your page itself
When someone publishes a post with your link, their server queues a fetch of your page. When the post reaches other servers, each one queues its own fetch of the same URL. Current Mastodon spreads those remote fetches over a random delay of up to one minute so they do not all land at once. The card is not copied from the author’s server. A post from an account with followers on 2,000 servers can mean roughly 2,000 requests for your page and 2,000 for your image within a minute or two.
That is why the result can differ by server. If a WAF, a bot challenge, or a rate limit rejects some of those requests, the users on those servers get no card — or a card with no image — while everyone else sees it fine. You cannot allow-list Mastodon by IP address: there are thousands of servers, each on its own IP.
What one fetch looks like in current source:
- User agent. The page request sends
Mastodon/4.7.3 (http.rb/5.3.1; +https://mastodon.social/) Bot— the version numbers and the server’s own address change from server to server. The image download sends the same string withoutBot. If you filter bots, matchMastodon/. - The final response must be
200withContent-Type: text/html. Redirects are followed, and the card is saved for the URL at the end of the chain. A403, a429, a challenge page, orapplication/xhtml+xmlmeans no card. - Only about the first 1 MB of HTML is read. Put the meta tags early in
<head>, not after a megabyte of inlined CSS or JSON. - Timeouts are short. 5 seconds to connect, and about 30 seconds for the whole response.
- No JavaScript runs. Tags added by a client-side framework are invisible. See JavaScript / SPA previews.
If your site sits behind a firewall or a bot manager, crawler blocked covers the allow rules. For Mastodon, the rule should let a short burst of Mastodon/ requests through rather than challenge them.
Which tags Mastodon reads
Mastodon matches a meta tag by property or name, case-insensitively. If a schema.org JSON-LD block of type NewsArticle or WebPage is on the page, its values win over the Open Graph ones for title, description, and site name — so a stale headline in JSON-LD will beat a fixed og:title.
| Card field | Read from, in order |
|---|---|
| Title | JSON-LD headline → og:title → <title>. No title means no card is saved. |
| Description | JSON-LD description → og:description → <meta name="description"> |
| Image | og:image only |
| Image alt text | og:image:alt |
| Site name | JSON-LD publisher.name → og:site_name |
| Author name | JSON-LD author.name → og:author |
| Author account | fediverse:creator (see below) |
| Published date | JSON-LD datePublished → article:published_time |
| Card URL | <link rel="canonical"> → og:url, only if it is on the same host as the fetched page |
| Video player | twitter:player, with twitter:player:width and twitter:player:height |
What is not read: twitter:title, twitter:description, and twitter:image. A page that only has Twitter Card tags gets its title from <title>, its description from the meta description, and no image. Add the Open Graph set — the Open Graph tag generator writes it.
oEmbed is tried first. If the page advertises an oEmbed endpoint (<link type="application/json+oembed">), Mastodon asks it before reading Open Graph. A rich oEmbed response is ignored and Open Graph is used instead. A link, photo, or video response becomes the card, and oEmbed has no description field — so if your endpoint answers with link, the card will have no description no matter what og:description says.
Image requirements
- JPEG, PNG, GIF, or WebP, under 8 MB. The image URL must answer a public
GETwith a 2xx status. If the download fails, the card is saved with text and no image. - Mastodon stores its own copy. Each server downloads the image and shrinks it to about 230,400 pixels (640×360 for a 16:9 image), and an animated GIF is stored as a JPEG. A 1200×630 source is fine; there is no need to make it smaller. See OG image size.
- Use an absolute
https://URL. Mastodon resolves a relativeog:imageagainst the page URL correctly, but many other apps do not. See absolute OG image URL. - Keep it public. No login, no signed cookie, no hotlink protection that rejects a request without a
Referer. The Open Graph image resizer crops to 1200×630 in the browser.
Reasons a post gets no card at all
- The post has media attached. A post with an image, video, or audio attachment does not get a link card.
- The post is a quote post. Current Mastodon skips the link card on quotes.
- It is not the first link. Only the first link that is not a mention, a hashtag, or a link to the poster’s own server is used.
- It has not been fetched yet. The card is added after the post is published. On remote servers that can take up to a minute plus the fetch itself.
Cards are cached per server
Each server keeps one stored card per URL and attaches it to every post on that server that links the URL. Editing your page does not change cards that servers already stored, and there is no Facebook-style debugger that tells every server to look again.
What actually refreshes a card, on one server at a time:
- A new post with the link, after the stored card is two weeks old. When someone on that server posts or edits a post with the URL and the stored card is at least two weeks old, the server fetches the page again and updates that card.
- A new post with the link, when the stored card has no image. A card saved without an image is retried the next time the URL is posted on that server.
- Editing the post is not enough by itself. An edit re-attaches the card, but a stored card younger than two weeks is reused as it is.
- A new image needs a new image URL. When a card is refreshed and
og:imageis the same URL as before, the stored image is kept. Rename the file or add a version, such asog.png?v=2. - A different URL is a different card. Posting
https://example.com/post?v=2makes each server fetch the page fresh. In current source, if that page’srel="canonical"points at the clean URL on the same host, the fresh data is saved onto the clean URL’s card on that server.
Clear Open Graph cache covers Slack, X, LinkedIn, Discord, Facebook, and iMessage. Bluesky is different again: there the card is stored inside the post when it is composed.
Credit the author with fediverse:creator
Since Mastodon 4.3, a card can show the author’s fediverse account under the link. Add one tag to the page:
<meta name="fediverse:creator" content="@you@mastodon.example">
The tag alone is not enough. The account it names must list your site’s domain under Preferences → Public profile → Verification → Author attribution (“Websites allowed to credit you”). That stops any site from claiming to be written by you. The handle can be on any fediverse software that publishes that list of allowed domains, and if a page has more than one tag, the first is used.
Add the domain before the link is shared. A server that fetched the page before the domain was allowed has already stored a card without the byline, and the refresh rules above apply.
A minimal set
<title>A specific title, not the brand name</title>
<meta name="description" content="One sentence that matches the page.">
<link rel="canonical" href="https://example.com/permalink">
<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:alt" content="What the picture shows">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="Example">
<meta property="og:url" content="https://example.com/permalink">
<meta name="fediverse:creator" content="@you@mastodon.example">
Width and height are not read for a link card. They are here for the other apps. Length and wording are in OG title & description, and og:url vs canonical covers the URL tags.
Test it like a Mastodon server
Read the first response, not the Elements panel after JavaScript has run. Use a Mastodon-style user agent so a bot rule that treats Mastodon differently shows up:
UA='Mastodon/4.7.3 (http.rb/5.3.1; +https://mastodon.social/) Bot'
# Final status and Content-Type after redirects: want 200 and text/html
curl -sIL -A "$UA" -H 'Accept: text/html' https://example.com/your-page
# The tags Mastodon reads, in the HTML the server sent
curl -sL -A "$UA" -H 'Accept: text/html' https://example.com/your-page \
| grep -iE 'og:|fediverse:creator|
Then open view-source: on the same URL in a browser and confirm the same og: tags are there. If the curl requests get a 403, a 429, or a challenge page while the browser works, fix the firewall first — the tags do not matter yet. A single curl will not reproduce a burst from hundreds of servers, so check your rate-limit rules too.
Fix checklist
- The page answers
200withtext/htmlto aMastodon/user agent, after redirects, without a challenge. - Rate limits and bot rules allow a burst of
Mastodon/requests for the page and the image. og:title,og:description, andog:imageare in the server-rendered HTML, early in<head>.- Any
NewsArticleorWebPageJSON-LD has the same headline and description as the Open Graph tags. - You are not relying on
twitter:title,twitter:description, ortwitter:imagealone. og:imageis an absolute, publichttps://URL to a JPEG, PNG, GIF, or WebP under 8 MB.- If the page advertises oEmbed, the endpoint does not answer with type
linkunless you are fine with no description. - A changed image has a new URL.
fediverse:creatornames your handle, and your account allows the domain — before you share.- To check a fix, post the URL in a new post without media. Old posts on servers that already stored the card keep it until the refresh rules above apply.
FAQ
Who fetches my page when a link is posted on Mastodon?
Every server that stores the post. The author’s server fetches it when the post is published, and each server that receives the post fetches it again on its own, within about a minute. A rate limit or firewall that blocks some of them leaves their users with no card.
Which tags does Mastodon read?
og:title, og:description, og:image, og:image:alt, og:site_name, and article:published_time, with <title> and the meta description as fallbacks. A NewsArticle or WebPage JSON-LD block wins for title, description, and site name. The twitter: title, description, and image tags are not read.
Why does an old post still show the old card?
Each server stores one card per URL and reuses it. A server fetches again only when someone on it posts the link and the stored card is at least two weeks old or has no image. A changed image at the same URL is not downloaded again.
How does fediverse:creator work?
Add <meta name="fediverse:creator" content="@you@mastodon.example"> to the page, and list your site’s domain under Author attribution in that account’s profile settings. Mastodon 4.3 and later show the byline with a link to the account.
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. We parse HTML in memory and discard it — we do not store the page. The sample is not a Mastodon preview either.