Link Previews in Message Input

Link previews in MessageInput give users a visual hint of what will render later in the MessageList via the Card component.

Best Practices

  • Enable link previews only when they add real value to your UX.
  • Keep preview rendering lightweight and dismissible.
  • Use MessageComposer config for behavior changes instead of custom hacks.
  • Handle failed previews gracefully to avoid broken UI.
  • Debounce URL detection to reduce unnecessary requests.

Link previews are rendered once enabled via MessageComposer:

import { useCreateChatClient } from "stream-chat";

const App = () => {
  const chatClient = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  useEffect(() => {
    if (!chatClient) return;

    chatClient.setMessageComposerSetupFunction(({ composer }) => {
      composer.linkPreviewsManager.enabled = true;
    });
  }, [chatClient]);
};

Link previews are rendered via LinkPreviewList, which subscribes to MessageComposer.linkPreviewsManager state.

The default list shows previews that are loading or loaded.

The default link preview UI is implemented for:

Message input

Link Preview Message Input


Edit message form

Link Preview Edit Message Form

If the default UI isn’t enough, provide a custom LinkPreviewList via the Channel LinkPreviewList prop. Your component should subscribe to the message composer’s link preview manager.

import {
  Channel,
  LinkPreviewListProps,
  LinkPreviewCard,
  useMessageComposer,
} from "stream-chat-react";
import { LinkPreviewsManagerState } from "stream-chat";

// on every link preview manager state extract these link previews
const linkPreviewsManagerStateSelector = (state: LinkPreviewsManagerState) => ({
  linkPreviews: Array.from(state.previews.values()).filter(
    (preview) =>
      LinkPreviewsManager.previewIsLoaded(preview) ||
      LinkPreviewsManager.previewIsLoading(preview),
  ),
});

const CustomLinkPreviewList = ({ linkPreviews }: LinkPreviewListProps) => {
  const { linkPreviewsManager } = useMessageComposer();

  // subscribe to link preview manager's state change
  const { linkPreviews } = useStateStore(
    linkPreviewsManager.state,
    linkPreviewsManagerStateSelector,
  );

  const showLinkPreviews = linkPreviews.length > 0;

  if (!showLinkPreviews) return null;

  return (
    <div className="str-chat__link-preview-list">
      {linkPreviews.map((linkPreview) => (
        <LinkPreviewCard
          key={linkPreview.og_scrape_url}
          linkPreview={linkPreview}
        />
      ))}
    </div>
  );
};

const App = () => (
  <Channel LinkPreviewList={CustomLinkPreviewList}>{/* ...  */}</Channel>
);

Each LinkPreview has a state property that you can use to render different UI. Possible states:

enum LinkPreviewState {
  /** Link preview has been dismissed using MessageInputContextValue.dismissLinkPreview **/
  DISMISSED = "dismissed",
  /** Link preview could not be loaded, the enrichment request has failed. **/
  FAILED = "failed",
  /** Link preview has been successfully loaded. **/
  LOADED = "loaded",
  /** The enrichment query is in progress for a given link. **/
  LOADING = "loading",
  /** The preview reference enrichment has not begun. Default status if not set. */
  PENDING = "pending",
}

Behavior customization

You can customize these aspects of link preview management:

  • The debounce interval for the URL discovery and enrichment requests.
  • URL discovery
  • Link preview dismissal

All is done via MessageComposer configuration API:

import { useCreateChatClient } from "stream-chat";
import type { LinkPreview } from "stream-chat";
import { customUrlDetector } from "./urlDetection";

const App = () => {
  const chatClient = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  useEffect(() => {
    if (!chatClient) return;

    chatClient.setMessageComposerSetupFunction(({ composer }) => {
      composer.updateConfig({
        linkPreviews: {
          debounceURLEnrichmentMs: 2000,
          enabled: true,
          findURLFn: customUrlDetector,
          onLinkPreviewDismissed: (linkPreview: LinkPreview) => {
            chatClient.notifications.addInfo({
              message: "Link preview dismissed",
              origin: { emitter: composer.linkPreviewsManager },
            });
          },
        },
      });
    });
  }, [chatClient]);
};