How to Handle Attachment Rendering and File Uploads in CopilotKit Messages
CopilotKit processes file uploads through the useAttachments hook, which validates MIME types, manages custom upload workflows, and tracks state from uploading to ready, while AttachmentChip components render thumbnails and removal UI for the chat interface.
CopilotKit enables multimodal chat interfaces by allowing users to attach images, documents, and media directly to messages in the CopilotKit/CopilotKit repository. The system provides a complete pipeline from file selection to AI-ready payload formatting, ensuring secure and performant handling without breaking React rendering optimizations.
Core Architecture of Attachment Handling
Three primary components orchestrate the file upload experience in CopilotKit.
useAttachments hook – Located in packages/react-core/src/v2/hooks/use-attachments.tsx, this hook manages the entire attachment lifecycle. It handles drag-and-drop events, clipboard pasting, file validation against MIME filters and size limits, optional custom upload processing, and state transitions. The hook exposes consumeAttachments() to extract ready items for message submission.
AttachmentChip rendering components – The showcase implementation in showcase/integrations/strands/src/app/demos/headless-complete/attachments/attachment-preview.tsx demonstrates how to render file thumbnails. These components display preview images for Base64 data URLs or hosted URLs, file names, and removal buttons.
Utility functions – Shared helpers in packages/react-ui/src/components/chat/attachment-utils.ts provide matchesAcceptFilter for MIME-type validation, readFileAsBase64 for client-side encoding, generateVideoThumbnail for preview generation, and size formatting utilities.
Step-by-Step File Upload Workflow
When a user interacts with the attachment system, the following sequence occurs in the CopilotKit source code:
-
Input Capture – The
handleDropandhandleFileUploadmethods inuseAttachmentsprocess file selection events from drag-and-drop operations, file pickers, or paste events. -
Validation –
matchesAcceptFilterandexceedsMaxSizecheck against configuredacceptpatterns andmaxSizelimits. Invalid files triggeronUploadFailedcallbacks. -
Upload Processing – If
attachments.onUploadis defined, the hook executes your custom implementation. Otherwise, it automatically converts files to Base64 usingreadFileAsBase64. -
Thumbnail Generation – For video files,
generateVideoThumbnailcreates preview frames before finalizing the attachment. -
State Transition – The hook replaces placeholder attachments (status
"uploading") with"ready"objects containing the finalsourceURL or Base64 data. -
UI Rendering –
AttachmentChipreadsattachment.sourceto render thumbnails, generic file icons, or loading states. -
Message Consumption –
consumeAttachments()returns onlyreadyattachments and clears the internal queue, preparing the data for theCopilotChatmessage payload.
The hook maintains referential stability for all callbacks, preventing unnecessary re-renders in memoized components.
Implementation Examples
Enable Attachments in CopilotChat
Configure the attachments prop on CopilotChat to accept specific file types:
import { CopilotChat } from "@copilotkit/react-core/v2";
<CopilotChat
agentId="my-agent"
attachments={{
enabled: true,
accept: "image/*,application/pdf",
maxSize: 10 * 1024 * 1024, // 10 MB
}}
/>
Custom Upload Handler
Implement external storage uploads by providing an onUpload function:
<CopilotChat
attachments={{
enabled: true,
onUpload: async (file) => {
const form = new FormData();
form.append("file", file);
const res = await fetch("/api/upload", {
method: "POST",
body: form
});
const { url } = await res.json();
return {
type: "url",
value: url,
mimeType: file.type,
metadata: { uploadedBy: currentUser.id },
};
},
}}
/>
Rendering Attachment Chips
Integrate the attachment UI into a custom composer using the useAttachments hook:
import { useAttachments } from "@copilotkit/react-core/v2";
import { AttachmentChip } from "./attachment-preview";
function Composer() {
const {
attachments,
fileInputRef,
handleFileUpload,
handleDrop,
removeAttachment,
} = useAttachments({ config: { enabled: true } });
return (
<div className="chat-composer">
{attachments.map((att) => (
<AttachmentChip
key={att.id}
attachment={att}
onRemove={removeAttachment}
/>
))}
<input
type="file"
multiple
ref={fileInputRef}
onChange={handleFileUpload}
className="hidden"
/>
<div onDrop={handleDrop}>
Drop files here or click to upload
</div>
</div>
);
}
Handling Upload Errors
Capture validation failures with the onUploadFailed callback:
<CopilotChat
attachments={{
enabled: true,
maxSize: 5 * 1024 * 1024, // 5 MB
onUploadFailed: (error) => {
// error.reason: "invalid-type" | "file-too-large" | "upload-failed"
toast.error(error.message);
},
}}
/>
Consuming Attachments When Sending
Extract ready attachments before dispatching the message to the AI:
const { consumeAttachments } = useAttachments({ config: { enabled: true } });
async function sendMessage(text: string) {
const readyAttachments = consumeAttachments();
const content = [
{ type: "text", text },
...readyAttachments.map((att) => ({
type: att.type,
source: att.source,
metadata: att.metadata,
})),
];
await copilotAgent.sendMessage({ content });
}
Summary
useAttachmentsinpackages/react-core/src/v2/hooks/use-attachments.tsxmanages validation, upload logic, and state transitions fromuploadingtoready.AttachmentChipcomponents in the showcase directory render thumbnails for both Base64 data URLs and remote URLs.- Utility functions in
packages/react-ui/src/components/chat/attachment-utils.tshandle MIME detection, size validation, and video thumbnail generation. - Referential stability guarantees prevent re-renders when using the hook's callbacks in memoized components.
consumeAttachments()extracts only validated, ready attachments for inclusion in the message payload.
Frequently Asked Questions
What file types can CopilotKit accept?
CopilotKit accepts any MIME type specified in the accept configuration string, including images, audio, video, and documents. The matchesAcceptFilter utility in attachment-utils.ts validates files against standard MIME patterns like image/* or application/pdf before processing.
How does CopilotKit handle large file uploads?
By default, the hook reads files as Base64 data URLs for immediate preview, but you should implement a custom onUpload handler for large files to avoid memory issues. Configure maxSize in bytes to reject oversized files client-side, and handle the onUploadFailed callback to display user-friendly error messages when limits are exceeded.
Can I customize the attachment preview appearance?
Yes, the AttachmentChip component in showcase/integrations/strands/src/app/demos/headless-complete/attachments/attachment-preview.tsx demonstrates the expected interface, but you can implement your own rendering logic. The hook provides all necessary attachment metadata including source, mimeType, and name to build custom UIs.
What happens to attachments during the upload process?
Attachments transition through a status field: initial placeholders have "uploading" status while being processed or uploaded to external storage, then change to "ready" once the source URL or Base64 data is available. Only ready attachments are returned by consumeAttachments() and sent to the AI model.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →