# 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](/chat/docs/sdk/react/components/message-components/attachment#card/).

## 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`:

```tsx
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]);
};
```

## Rendering link previews

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](/data/docs/chat-sdk/react/v13-latest/_assets/link-preview-message-input.png)

<hr />

**Edit message form**

![Link Preview Edit Message Form](/data/docs/chat-sdk/react/v13-latest/_assets/link-preview-edit-message-form.png)

## Link Preview customization

### Custom rendering of link previews

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.

```tsx {35}
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>
);
```

### Link preview states

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

```typescript
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:

```tsx
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]);
};
```


---

This page was last updated at 2026-03-13T13:15:42.226Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/guides/customization/link-previews/](https://getstream.io/chat/docs/sdk/react/guides/customization/link-previews/).