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:

  1. Input Capture – The handleDrop and handleFileUpload methods in useAttachments process file selection events from drag-and-drop operations, file pickers, or paste events.

  2. Validation – matchesAcceptFilter and exceedsMaxSize check against configured accept patterns and maxSize limits. Invalid files trigger onUploadFailed callbacks.

  3. Upload Processing – If attachments.onUpload is defined, the hook executes your custom implementation. Otherwise, it automatically converts files to Base64 using readFileAsBase64.

  4. Thumbnail Generation – For video files, generateVideoThumbnail creates preview frames before finalizing the attachment.

  5. State Transition – The hook replaces placeholder attachments (status "uploading") with "ready" objects containing the final source URL or Base64 data.

  6. UI Rendering – AttachmentChip reads attachment.source to render thumbnails, generic file icons, or loading states.

  7. Message Consumption – consumeAttachments() returns only ready attachments and clears the internal queue, preparing the data for the CopilotChat message 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

  • useAttachments in packages/react-core/src/v2/hooks/use-attachments.tsx manages validation, upload logic, and state transitions from uploading to ready.
  • AttachmentChip components 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.ts handle 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →