Complete file handling including upload flows, serving files via URL, storing generated files from actions, deletion, and accessing file metadata from system tables
Support for any file type (images, PDFs, videos, etc.)
File metadata via the _storage system table
Integration with mutations and actions
Generating Upload URLs
// convex/files.tsimport { mutation } from "./_generated/server";import { v } from "convex/values";export const generateUploadUrl = mutation({ args: {}, returns: v.string(), handler: async (ctx) => { return await ctx.storage.generateUploadUrl
Client-Side Upload
// React componentimport { useMutation } from "convex/react";import { api } from "../convex/_generated/api";import { useState } from "react";function FileUploader() {
Saving File References
// convex/files.tsimport { mutation, query } from "./_generated/server";import { v } from "convex/values";export const saveFile = mutation({ args: { storageId: v.id("_storage"), fileName: v.string(), fileType: v.string(),
import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";function FileDisplay({ fileId }: { fileId: Id<"files"> }) { const file = useQuery(api.files.getFile, { fileId });
Storing Generated Files from Actions
// convex/generate.ts"use node";import { action } from "./_generated/server";import { v } from "convex/values";import { api } from "./_generated/api";export const generatePDF
Accessing File Metadata
// convex/files.tsimport { query } from "./_generated/server";import { v } from "convex/values";import { Id } from "./_generated/dataModel";type FileMetadata = { _id: Id<"_storage">; _creationTime
Deleting Files
// convex/files.tsimport { mutation } from "./_generated/server";import { v } from "convex/values";export const deleteFile = mutation({ args: { fileId: v.id("files") }, returns: v.null(), handler: async (ctx,
Image Upload with Preview
import { useMutation } from "convex/react";import { api } from "../convex/_generated/api"
Examples
Schema for File Storage
// convex/schema.tsimport { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ files: defineTable({ storageId: v.id("_storage"), fileName: v.string(),
Best Practices
Never run npx convex deploy unless explicitly instructed
Never run any git commands unless explicitly instructed
Validate file types and sizes on the client before uploading
Store file metadata (name, type, size) in your own table
Use the _storage system table only for Convex metadata
Delete storage files when deleting database references
Use appropriate Content-Type headers when uploading
Consider image optimization for large images
Common Pitfalls
Not setting Content-Type header - Files may not serve correctly
Forgetting to delete storage - Orphaned files waste storage
Not validating file types - Security risk for malicious uploads
Large file uploads without progress - Poor UX for users
Using deprecated getMetadata - Use ctx.db.system.get instead