showcase/shell-docs/src/content/docs/multimodal-attachments.mdx
You have a working CopilotChat and want users to attach files — images, PDFs, audio, video — that the AI can see and respond to. By the end of this guide, your chat will support drag-and-drop file attachments with previews, lightbox viewing, and multimodal AI responses.
Add attachments to your CopilotChat component:
import { CopilotChat } from "@copilotkit/react-core/v2";
<CopilotChat
agentId="my-agent"
attachments={{ enabled: true }} // [!code highlight]
/>
That's it. Users can now click the attachment button or drag-and-drop files into the chat. The files are sent as part of the message content to your agent.
The attachments prop accepts an AttachmentsConfig object:
<CopilotChat
attachments={{
enabled: true,
accept: "image/*", // MIME filter (default: "*/*")
maxSize: 10 * 1024 * 1024, // 10MB limit (default: 20MB)
}}
/>
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | — | Enable file attachments in the chat input. |
accept | string | "*/*" | MIME type filter. Supports patterns like "image/*", ".pdf,.docx", or comma-separated lists. |
maxSize | number | 20 * 1024 * 1024 | Maximum file size in bytes. |
onUpload | (file: File) => AttachmentUploadResult | Promise<...> | — | Custom upload handler. See Custom upload handler. |
onUploadFailed | (error: AttachmentUploadError) => void | — | Called when a file fails validation or upload. See Handling upload errors. |
Attachments are categorized by modality based on their MIME type:
| Modality | MIME types | Preview | AI support |
|---|---|---|---|
| Image | image/* | Thumbnail with lightbox | Supported by most vision-capable models (GPT-4o, Claude, etc.) |
| Audio | audio/* | Audio player | Model-dependent |
| Video | video/* | Thumbnail with play button + lightbox | Model-dependent |
| Document | Everything else | File icon + name; PDF and text get lightbox preview | Sent as file content — model support varies |
By default, files are read as base64 and sent inline. For large files or production apps, you'll want to upload to your own storage and pass a URL instead.
onUpload returns an AttachmentUploadResult — a discriminated union with two variants:
<Tabs items={["Base64 (inline)", "URL (hosted)"]}>
<Tab value="Base64 (inline)">
tsx <CopilotChat attachments={{ enabled: true, onUpload: async (file) => { const buffer = await file.arrayBuffer(); const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer))); return { type: "data", value: base64, mimeType: file.type, }; }, }} />
</Tab>
<Tab value="URL (hosted)">
tsx <CopilotChat attachments={{ enabled: true, onUpload: async (file) => { const formData = new FormData(); formData.append("file", file); const res = await fetch("/api/upload", { method: "POST", body: formData }); const { url } = await res.json(); return { type: "url", value: url, mimeType: file.type, }; }, }} />
</Tab>
</Tabs>
You can attach custom metadata to any upload. It's included in the InputContent part sent to the agent and accessible via metadata on the content part.
onUpload: async (file) => {
const url = await uploadToStorage(file);
return {
type: "url",
value: url,
mimeType: file.type,
metadata: {
uploadedBy: currentUser.id,
category: "support-ticket",
},
};
},
The filename is always included in metadata automatically — you don't need to add it yourself.
Use onUploadFailed to react when a file is rejected or an upload fails — for example, to show a toast:
<CopilotChat
attachments={{
enabled: true,
accept: "image/*",
maxSize: 5 * 1024 * 1024, // 5MB
onUploadFailed: (error) => {
// error.reason: "file-too-large" | "invalid-type" | "upload-failed"
// error.file: the original File object
// error.message: human-readable description
toast.error(error.message);
},
}}
/>
| Reason | When it fires |
|---|---|
invalid-type | File doesn't match the accept filter. |
file-too-large | File exceeds maxSize. |
upload-failed | The onUpload handler threw, or the default base64 reader failed. |
For errors that happen after the message is sent (e.g., the model doesn't support the file type), use the onError callback on CopilotChat:
<CopilotChat
attachments={{ enabled: true }}
onError={(event) => {
console.error(`[${event.code}]`, event.error.message);
}}
/>
When a user attaches files and sends a message, CopilotKit:
onUpload handler)InputContent parts — text + one part per attachmentcontent: [{ type: "text", ... }, { type: "image", source: ... }, ...]The attachments are part of the standard AG-UI InputContent schema, so any AG-UI-compatible agent (BuiltInAgent, LangGraph, custom) can receive them.