Link previews
A link preview is the card with a title, a description and an image that shows for a link in a message. This page covers how the composer loads a preview while the user types, what's sent with the message, and how the message list shows it.
What users get
The user types "Look at https://example.com/launch". When they stop typing, a card appears above the input with the page's image, title and description, and the URL. Hovering the card shows the full URL. They can dismiss it with its ✕. When they send, the message in the list shows a card for the link above the text.
How it works across the layers
- State. The composer's
linkPreviewsManagerfinds the links in the text, loads a preview for each, and keeps them in its store. Sending adds the loaded previews to the message as attachments. - UI.
LinkPreviewListshows the previews above the input, asLinkPreviewCards. In the message list,Attachmentrenders each preview withCard. - Layout. Not involved.
From typing to sending
- Each text change starts a wait of
linkPreviews.debounceURLEnrichmentMs, 1500 ms by default. When the user stops typing for that long,linkPreviews.findURLFnfinds the URLs in the text. - Each new URL gets a preview with status
loading, and the manager asks the server for the page's data withclient.getOG({ url }). The preview becomesloaded, orfailedwhen the request fails. When the text has no URL left, all previews are cleared. - Dismissing a preview calls
LinkPreviewsManager.dismissPreview(url). Its status becomesdismissed, andlinkPreviews.onLinkPreviewDismissedis called. - On send, the composition middleware adds every loaded preview to the message's attachments, and tells the server not to add previews of its own (
skip_enrich_url). It also setsskip_enrich_urlwhen the user dismissed a preview, but only when the message carries attachments. A text-only message whose preview was dismissed goes out without the flag, and the server adds the card. - If a preview is still loading when the user sends, no previews are sent, and the server adds them while it saves the message.
Message composer lists the statuses.
In the composer and the message list
LinkPreviewList shows the previews that are loading or loaded. It shows only the first one by default; the others are still sent. LinkPreviewCard shows the image, title, description and URL, and its ✕ dismisses the preview.
In the message list, an attachment with og_scrape_url or title_link is a link preview, and Attachment renders it with Card.
Turning it on or off
Link previews are on by default. Two sides decide:
- The client:
messageComposer.linkPreviews.enabled,trueby default. It decides whether the composer loads and shows previews. - The server: the channel type's
url_enrichmentsetting. It decides whether the server adds previews to messages. With it off, the composer doesn't load previews either.
To stop the composer from loading previews:
client.config.set({
messageComposer: { linkPreviews: { enabled: false } },
});The server still adds previews to sent messages while url_enrichment is on, so messages keep their cards; only the composer stops showing them. To send messages without cards, turn url_enrichment off for the channel type. Configuration lists every setting both sides decide.
Customizing
Change how links are found and loaded
import type { LinkPreview } from "stream-chat";
client.config.set({
messageComposer: {
linkPreviews: {
// Only links to your own domain get a preview.
findURLFn: (text: string) =>
Array.from(
text.matchAll(/https:\/\/example\.com\/\S*/g),
([url]) => url,
),
onLinkPreviewDismissed: (linkPreview: LinkPreview) => {
console.log(`Dismissed ${linkPreview.og_scrape_url}`);
},
},
},
});debounceURLEnrichmentMs is read only when a composer is built, so set it before the client creates composers, for example in the config you pass to useCreateChatClient.
Show more than one preview
Wrap LinkPreviewList and pass displayLinkCount:
import {
Channel,
LinkPreviewList,
MessageComposer,
MessageList,
WithComponents,
} from "stream-chat-react";
const ThreeLinkPreviews = () => <LinkPreviewList displayLinkCount={3} />;
const overrides = { LinkPreviewList: ThreeLinkPreviews };
export const ChannelWithThreePreviews = () => (
<WithComponents overrides={overrides}>
<Channel channel={channel}>
<MessageList />
<MessageComposer />
</Channel>
</WithComponents>
);Replace the preview list
A list of your own reads linkPreviewsManager from the composer and renders the previews it wants. LinkPreviewsManager.previewIsLoaded() and LinkPreviewsManager.previewIsLoading() tell the statuses apart:
import type { LinkPreviewsManagerState } from "stream-chat";
import { LinkPreviewsManager } from "stream-chat";
import {
LinkPreviewCard,
useMessageComposerController,
useStateStore,
} from "stream-chat-react";
const loadedPreviewsSelector = ({ previews }: LinkPreviewsManagerState) => ({
previews: Array.from(previews.values()).filter(
LinkPreviewsManager.previewIsLoaded,
),
});
export const LoadedLinkPreviews = () => {
const { linkPreviewsManager } = useMessageComposerController();
const { previews } = useStateStore(
linkPreviewsManager.state,
loadedPreviewsSelector,
);
if (!previews.length) return null;
return (
<div className="loaded-link-previews">
{previews.map((preview) => (
<LinkPreviewCard key={preview.og_scrape_url} linkPreview={preview} />
))}
</div>
);
};Register it with <WithComponents overrides={{ LinkPreviewList: LoadedLinkPreviews }}>. It works in channel, thread and edit composers alike, because useMessageComposerController() returns the composer it's rendered in.
Change the card in the message list
Pass a Card of your own to Attachment, and register the result as the Attachment override:
import type { AttachmentProps, CardProps } from "stream-chat-react";
import { Attachment } from "stream-chat-react";
const CompactCard = ({ og_scrape_url, title, title_link }: CardProps) => (
<a className="compact-card" href={title_link ?? og_scrape_url}>
{title ?? og_scrape_url}
</a>
);
export const AttachmentWithCompactCards = (props: AttachmentProps) => (
<Attachment {...props} Card={CompactCard} />
);Reference
- Attachments: the
Attachmentprops, includingCard. - Composer UI components:
LinkPreviewListand the composer's other parts. - MessageComposer API:
linkPreviewsManager. - Configuration tree: every
messageComposer.linkPreviewssetting.