You paste your link into Slack, LinkedIn or a group chat, and instead of a card with a title and an image you get a bare URL. Or a card with yesterday's image. Or a card with the right title and a grey box where the picture should be.
A link preview is built by a bot, not by a browser. When you paste the link, the platform's crawler fetches your page once, reads a handful of tags from the <head> of the HTML your server sent, downloads the image they point at, and caches the result. It does not run your JavaScript, it does not have your login cookies, and it does not come back for a while. Most missing previews trace to one of those three facts, or to a tag the bot cannot use — so fix the tags, check what the bot actually receives, and then make the platform look again.
This article covers Facebook, LinkedIn, Slack, Discord and iMessage. X is not covered here.
The 30-second version
| Platform | Reads | Image | When you change something |
|---|---|---|---|
og: tags; URLs must be absolute |
At least 1200×630 for best display, 200×200 minimum, under 8 MB, close to 1.91:1 | Re-scrape in the Sharing Debugger; a post already shared keeps the old card until refreshed from its menu | |
og:title, og:image, og:description, og:url |
At least 1200×627, under 5 MB, 1.91:1 | Not documented on its help page | |
| Slack | oEmbed, Twitter Card and Open Graph tags | Not documented | Cached about 30 minutes; wait it out |
| Discord | og: tags, twitter:card for the layout, oEmbed |
Declare width and height; fetch must finish in 10 seconds | Cached about 30 minutes; share with ?v=2 to see a change now |
| iMessage | og:title, og:image in the page itself |
At least 900 px wide; the page itself under 1 MB | Not documented |
What happens when you paste a link
The platform's servers make an ordinary HTTP request for your URL, with their own user agent, and parse whatever comes back. The platforms are unusually explicit about how little that is:
- No JavaScript. Discord: no code is executed, so tags added by a client-side script after load are never seen. Apple says the same about Messages: link previews do not follow
metaredirects, nor run JavaScript. - Only the top of the page. Slack's bot fetches as little of the page as it can, using HTTP Range headers. Facebook says Open Graph properties must appear before the first 1 MB or they are cut off.
- Not for long. Facebook expects the content to be crawlable within a few seconds; Discord gives the whole fetch, images included, 10 seconds.
- Not you. The bot does not carry your session, your password cookie or anything else your browser has.
Then it caches. Slack and Discord hold the result for about 30 minutes. Facebook re-scrapes on its own schedule — its own pages give two different figures for how often — so to see a change there now you ask it to scrape again. Even then, a post that already went out keeps its old card until somebody refreshes that post.
So the useful mental model is: one quick, anonymous, script-free read of the top of your HTML, remembered for a while.
The tags that cover every platform
Put these near the top of <head>, before any large inline script or style:
<meta property="og:title" content="What the page is called" />
<meta property="og:description" content="One or two sentences about it." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/the-page" />
<meta property="og:image" content="https://example.com/social/the-page.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="What the image shows" />
<meta name="twitter:card" content="summary_large_image" />Why each one is there:
- The first five are the four properties the Open Graph protocol requires plus
og:description, which LinkedIn lists among the tags that must exist. - Width and height let Facebook render the image immediately instead of downloading and measuring it first — without them, the first person to share may get no image at all. Discord also uses them rather than fetching the image to measure it.
og:image:altis asked for by the protocol whenever there is an image.twitter:cardmatters here because Discord reads it to decide the layout: withsummary_large_imageyou get text with a large image below it.
One image at 1200×630, PNG or JPEG, well under 5 MB, satisfies every row of the table above.
Why yours isn't showing
Check these in order. The first one that matches is usually the whole problem.
The image URL is relative
<meta property="og:image" content="/social.png"> works nowhere reliably. Facebook says plainly that all URLs must be absolute, and the Open Graph protocol defines a URL value as one that uses http:// or https://. The other platforms do not document whether they resolve a relative path, so do not rely on it. LinkedIn's own example uses a protocol-relative // address; write the full https:// one and it works for everybody.
The tags are added by JavaScript
A single-page app that sets its title and Open Graph tags from React, Vue or a router hook shows the right thing in a browser tab and nothing in a preview. The bot receives the empty index.html shell and never runs the code that would have filled it in.
The fix is to have the tags in the HTML the server sends: prerender each route at build time, use a framework that renders HTML per page, or at the very least put sensible site-wide defaults in the shell so every link gets some card. This is the same line between a static file and a page assembled in the browser that shows up everywhere else.
The page needs a login
A page behind a password, a sign-in or an invitation answers the bot with a login page or a 401. The bot has none of your cookies, so that is all it will ever see. Expect no card, or a card with the login page's title.
robots.txt blocks the bot
Facebook's crawler identifies itself as facebookexternalhit/1.1, and Meta documents blocking its crawlers with a Disallow rule for their user agent — while noting the crawler "might bypass robots.txt when performing security or integrity checks". A blanket Disallow: / meant for search engines can remove your Facebook previews too.
Slack is the exception worth knowing: it does not currently honor robots.txt, on the grounds that it is acting for a person who pasted a link rather than crawling. So when Slack shows a preview and Facebook does not, check robots.txt first.
The image is too big, too small, or slow
Over 8 MB on Facebook or 5 MB on LinkedIn, and the image is not used. Under 200×200 on Facebook it is not used either, and between that and 600×315 it shrinks to a small square. In Messages, an image under 150 pixels wide may be ignored or shown as an icon. An image server that takes several seconds to answer can push the whole fetch past the platform's time limit.
It is the old version, cached
You fixed the tags and the card did not change. Nothing is wrong; the platform remembered the last read. See the next section.
See what the bot sees
Ask for the page the way the bot does. First, where it ends up:
curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' -A "facebookexternalhit/1.1" https://example.com/the-pageThat should print 200 and the address you shared. A 401, a 403, or a sign-in address means the bot is stopped at the door. Then what it reads:
curl -sL -A "facebookexternalhit/1.1" https://example.com/the-page | grep -i -o -E "<meta[^>]*(property|name)=[\"']?(og|twitter):[^>]*>"If that prints nothing, the tags are not in the HTML the server sends. Finally request the og:image address itself with curl -sIL and check the last response is 200 with an image content-type, not an HTML error page.
Making the platform look again
- Facebook: paste the URL into the Sharing Debugger and scrape again. If you replaced the image, give it a new filename — Facebook caches images by their URL, and its documentation says to use a new URL for the new image. Keep the old image on your site: Meta's FAQ warns that existing shares show a blank area if it disappears. A post already published keeps its card until somebody chooses Refresh share attachment from that post's menu.
- LinkedIn: its help page does not say how long a card is kept or how to refresh one.
- Discord: wait about 30 minutes, or share the same URL with a new query string such as
?v=2— Discord's own suggestion. - Slack: the result is cached for about 30 minutes across the whole service. Wait, then paste again.
- iMessage: nothing documented.
Where nothing is documented, sharing the page with a query string added is worth a try, though a platform that keys its cache on og:url may still show the old card.
Apart from Facebook's refresh option, assume a card that has already gone out into a conversation will not change. Fix it before you send the link that matters.
When a missing preview is correct
Some pages should not have a preview, and the fix is to leave them alone.
- Anything behind a password or an invitation. A preview card is shown to everyone in the channel, not only the person you meant it for. A private page that shows no card is working as intended — a password-protected site hosted with dropto.run answers the bot with a 401 login page that carries no Open Graph tags, so nothing behind the password reaches the card.
- Do not let the crawlers' user agents past your login. It is a common workaround and a bad one: a user agent is a string anybody can send, so letting
facebookexternalhitthrough lets everybody through.
If you want a private page to look good when shared, give the login page itself a title and an image — the bot will see that, and nothing behind it.
Questions
I changed the image and Facebook still shows the old one. Why?
Facebook caches the image by its URL, and posts already shared keep their card. Upload the new image under a new filename, update og:image, and scrape again in the Sharing Debugger. For a post already published, choose Refresh share attachment from the post's menu.
Can og:image be a relative path?
No. Facebook requires absolute URLs, the Open Graph protocol defines URLs as full http:// or https:// addresses, and no other platform documents resolving a relative one. Use the full https:// address.
My React or Vue site has no preview. Why?
The tags are set by JavaScript after the page loads, and preview bots do not run JavaScript. Put the tags in the HTML the server sends, by prerendering or rendering per page.
Do I need twitter: tags if I already have og: tags?
For the platforms in this article, og: tags do the work. Discord reads twitter:card to choose a large image layout, so summary_large_image is worth adding.
What image size works everywhere?
1200×630 pixels, PNG or JPEG, well under 5 MB. It meets Facebook's best-display size, LinkedIn's minimum and ratio, and Messages' minimum width.
Slack shows a preview but Facebook doesn't. Why?
Check robots.txt first: Meta documents blocking its crawler there, and Slack ignores it. After that, Facebook's 1 MB cut-off and its image minimums are the next differences to check.
Can a password-protected page have a preview?
Not of what is behind the password, and it should not. The bot sees only the login page, so give that page a title and image if you want the card to look intentional.