Handles file uploads, image attachments, and media processing in agent conversations. Use this when agents analyze images, process documents, or generate files.
apm install @sstobo/convex-agents-files[](https://apm-p1ls2dz87-atlamors-projects.vercel.app/packages/@sstobo/convex-agents-files)---
name: "Convex Agents Files"
description: "Handles file uploads, image attachments, and media processing in agent conversations. Use this when agents analyze images, process documents, or generate files."
---
## Purpose
Files and images let agents understand and generate visual content. Covers uploading, storing, attaching to messages, and managing file lifecycle.
## When to Use This Skill
- Users upload images for agent analysis
- Agents need to process documents or files
- Agents generate images with DALL-E
- Building file-based workflows
- Implementing file cleanup and tracking
## Upload and Store a File
```typescript
import { storeFile } from "@convex-dev/agent";
export const uploadFile = action({
args: { fileData: v.string(), filename: v.string(), mimeType: v.string() },
handler: async (ctx, { fileData, filename, mimeType }) => {
const bytes = Buffer.from(fileData, "base64");
const { file } = await storeFile(
ctx,
components.agent,
new Blob([bytes], { type: mimeType }),
{ filename, sha256: "hash" }
);
return {
fileId: file.fileId,
url: file.url,
storageId: file.storageId,
};
},
});
```
## Send File with Message (2-Step)
Upload first, then send message with attachment:
```typescript
import { saveMessage, getFile } from "@convex-dev/agent";
// Step 1: Save message with file
export const submitFileQuestion = mutation({
args: { threadId: v.string(), fileId: v.string(), question: v.string() },
handler: async (ctx, { threadId, fileId, question }) => {
const { imagePart, filePart } = await getFile(ctx, components.agent, fileId);
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
message: {
role: "user",
content: [
imagePart ?? filePart,
{ type: "text", text: question },
],
},
metadata: { fileIds: [fileId] },
});
return { messageId };
},
});
// Step 2: Generate response
export const generateFileResponse = action({
args: { threadId: v.string(), promptMessageId: v.string() },
handler: async (ctx, { threadId, promptMessageId }) => {
const { thread } = await myAgent.continueThread(ctx, { threadId });
await thread.generateText({ promptMessageId });
},
});
```
## Inline File Saving (Action Only)
Pass file directly in generation:
```typescript
export const analyzeImageInline = action({
args: { threadId: v.string(), imageData: v.string(), question: v.string() },
handler: async (ctx, { threadId, imageData, question }) => {
const { thread } = await myAgent.continueThread(ctx, { threadId });
await thread.generateText({
message: {
role: "user",
content: [
{
type: "image",
image: Buffer.from(imageData, "base64"),
mimeType: "image/png",
},
{ type: "text", text: question },
],
},
});
},
});
```
## Generate and Save Images
```typescript
export const generateAndSaveImage = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const { image } = await generateImage({
model: openai.image("dall-e-2"),
prompt,
});
const { file } = await storeFile(ctx, components.agent, image, {
filename: `generated-${Date.now()}.png`,
});
return { fileId: file.fileId };
},
});
```
## Key Principles
- **Automatic storage**: Files > 64KB stored automatically
- **File tracking**: Metadata tracks which messages reference files
- **URL generation**: Signed URLs prevent unauthorized access
- **MIME type inference**: Auto-detected if not provided
- **Cleanup**: Files tracked for garbage collection
## Next Steps
- See **messages** for message management
- See **streaming** for streamed file processing
- See **fundamentals** for agent setup