Back to blog

OG images

How to Build Rich Link Previews Like Slack and iMessage

July 29, 2026 · 6 min read · Grabbit Team

How to Build Rich Link Previews Like Slack and iMessage

When you paste a link into Slack, iMessage, or Discord, it usually expands into a card with a title, a short description, and an image. That card is a rich link preview, and when yours shows up as a bare URL instead, the cause is almost always the same across every platform: the page is missing the Open Graph tags those apps read, or its og:image points at something they cannot fetch. This guide covers how the previews work, the exact tags to add, and how to give the card a real image of your page instead of a generic logo.

Slack, iMessage, Discord, and the social platforms do not screenshot your page on demand or guess at its content. When a URL is posted, the app fetches your page's raw HTML and looks for Open Graph meta tags in the <head>. Four tags drive the entire card:

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A one-line summary of the page." />
<meta property="og:image" content="https://yourdomain.com/og.png" />
<meta property="og:url" content="https://yourdomain.com/page" />

og:title and og:description become the text. og:image becomes the picture. og:url is the canonical link the card points at. If a page has no Open Graph tags at all, the app falls back to the bare URL with no card. That is what a "broken" preview almost always is: missing or unreachable tags, not a bug in Slack or iMessage.

Two details trip people up on every platform:

  • The fetcher reads the raw HTML, not the rendered page. It does not run your JavaScript. If your meta tags are injected client-side (a single-page app that sets them after load), the fetcher never sees them. The tags must be in the HTML the server sends.
  • og:image must be an absolute, public URL. A relative path like /og.png will not resolve. Use the full https://yourdomain.com/og.png, and make sure that URL is reachable without a login or a redirect the app cannot follow.

The tags every platform reads

The good news for anyone building link previews into an app: you do not write a different set of tags per platform. Open Graph is the shared standard, so one correct set of tags produces a rich card in Slack, iMessage, Discord, Facebook, and LinkedIn at once. Add one X-specific tag and you cover X (Twitter) too:

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A one-line summary of the page." />
<meta property="og:image" content="https://yourdomain.com/og.png" />
<meta property="og:url" content="https://yourdomain.com/page" />
<meta name="twitter:card" content="summary_large_image" />

twitter:card set to summary_large_image tells X to render the wide card instead of a small thumbnail; X falls back to your og:image for the picture, so you do not need a separate twitter:image. That is the whole tag set. The work that remains is producing the image, and making sure the tags actually reach the fetcher.

Making the preview image a real render of your page

The og:title and og:description are just strings. The og:image is the part that makes a preview feel rich, and it is also the part most people get wrong by shipping a single generic logo for every URL. For a marketing page, a docs page, or a product page, the best preview image is often a clean shot of the page itself.

You can produce that with a screenshot API: point it at the URL, get back a hosted image, and use that image as your og:image.

curl https://api.grabbit.live/v1/grabs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourdomain.com/og",
    "width": 1200,
    "height": 630,
    "format": "png",
    "delay_ms": 500
  }'

The response includes a hosted image_url:

{
  "id": "grb_01jx...",
  "status": "done",
  "image_url": "https://cdn.grabbit.live/grabs/grb_01jx....png",
  "width": 1200,
  "height": 630,
  "format": "png",
  "bytes": 71240,
  "execution_ms": 1240
}

width accepts 320 to 1920 and height 240 to 1080, so the 1200 by 630 card is comfortably in range. delay_ms (0 to 10000) gives client-rendered content or web fonts a moment to settle before the capture fires. Drop the returned image_url into your tag:

<meta property="og:image" content="https://cdn.grabbit.live/grabs/grb_01jx....png" />

Capture once per unique page and cache the image_url alongside the page record rather than rendering on every request. A common pattern is to build a dedicated /og route with your normal components, capture that at 1200 by 630, and reuse the result everywhere. The same template-and-cache approach is covered in how to generate dynamic OG images from any URL, and the per-platform fix-it details are in how to make your Discord links unfurl.

Why 1200 by 630 covers every platform

Slack, iMessage, Discord, Facebook, X, and LinkedIn all render the same 1.91 to 1 aspect ratio as a large card, and 1200 by 630 pixels is the size that satisfies all of them. That is why one og:image fixes the preview everywhere at once: you are not producing a Slack image and an iMessage image and a Discord image, you are producing one Open Graph image that every reader parses.

Smaller images still work, but many apps fall back to a small thumbnail beside the text below roughly 300 pixels wide, which reads as a weaker preview. Stay at 1200 by 630 and the card is full-width on every platform.

Why your preview still is not showing

If the tags are present and the image URL works in a browser but a platform still shows nothing, work through these in order:

  1. The platform cached the old version. Slack, iMessage, Discord, and Facebook all cache the metadata they fetched the first time a URL was posted, so editing your tags will not update a preview that already rendered. Append a unique query string, such as ?nocache=1, and post the link again so the app treats it as a new URL and re-fetches. Facebook and X also offer manual re-scrape tools in their sharing debuggers.
  2. The image is behind auth or a redirect. If og:image requires a login, sits behind a paywall, or redirects in a way the fetcher will not follow, the card renders without an image. Test the raw image URL in a private browser window.
  3. The tags are set by JavaScript. The fetchers read static HTML and do not execute scripts. Server-render the meta tags so they are in the initial response.
  4. The image is too small. Below roughly 300 pixels wide, many apps render a small thumbnail instead of a full card. Use 1200 by 630.

Re-fetch first, because a stale cache is the single most common reason a correct tag set still shows nothing.

The short version

A rich link preview is your page's Open Graph tags rendered as a card, and every platform reads the same four tags. Add og:title, og:description, og:image, and og:url server-side, plus twitter:card for X; make og:image an absolute, public, 1200 by 630 URL; and append ?nocache=1 to force a refresh after changes. If you want the image to be a real render of the page instead of a static logo, a screenshot API turns the live URL into the hosted image you point og:image at. New to the format? Start with what is an OG image.

FAQ

How do rich link previews work?
When you paste a URL into Slack, iMessage, Discord, or any social app, the app fetches the page's raw HTML and reads its Open Graph meta tags: og:title, og:description, og:image, and og:url. It builds the preview card from those four values. No app screenshots your page on the fly or runs your JavaScript; it reads the tags in the HTML the server sends. If a page has no Open Graph tags, the link shows up bare with no card.
What tags do I need for a rich link preview?
Four Open Graph tags in the page's <head>: og:title (the headline), og:description (a one-line summary), og:image (an absolute, public image URL), and og:url (the canonical link). Add twitter:card set to summary_large_image so X renders the wide card too. These must be server-rendered, because the apps that build previews read the raw HTML and do not execute your JavaScript.
Why is my link preview not showing?
The common causes are: the page has no og:image, or it points at a relative or broken URL; the image sits behind a login or a redirect the app cannot follow; the tags are injected client-side by JavaScript that the fetcher never runs; or the platform cached an earlier version of the page. Confirm the four tags are in the served HTML (view source or curl the URL), make sure og:image is a public absolute URL, then force a re-fetch.
What size should a link preview image be?
1200 by 630 pixels, the 1.91 to 1 ratio that Slack, iMessage, Discord, Facebook, X, and LinkedIn all render as a large card. One image at that size covers every platform. Smaller images still work but many apps show them as a small thumbnail beside the text instead of a full-width card.
How do I make the preview image a screenshot of my page?
Point a screenshot API at the page (or a dedicated /og route) at 1200 by 630, get back a hosted image URL, and use that as your og:image. Capture once per unique page and cache the returned image_url alongside the page record rather than rendering on every request. The same tag then drives the preview on every platform.

Capture any website with one API call

Get a free test key and capture your first screenshot in two minutes.

Written by

Grabbit Team

Screenshots as a service

The team behind Grabbit, the screenshot API for developers and AI agents. We write about web capture, rendering, and automating screenshots at scale.

Keep reading