File Storage

Convex includes a built-in file storage system for uploading, storing, and serving files. It handles the infrastructure so you do not need a separate object store or CDN.

Upload flow

The upload process has three steps:

  1. Generate an upload URL — call a mutation that returns a short-lived upload URL.
  2. POST the file — send the file to the upload URL from the client.
  3. Save the storage ID — store the returned storageId in a document for later retrieval.

Server functions

// convex/files.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";

export const generateUploadUrl = mutation(async (ctx) => {
  return await ctx.storage.generateUploadUrl();
});

export const saveFile = mutation({
  args: { storageId: v.id("_storage"), name: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.insert("files", {
      storageId: args.storageId,
      name: args.name,
      uploadedAt: Date.now(),
    });
  },
});

export const getFileUrl = query({
  args: { storageId: v.id("_storage") },
  handler: async (ctx, args) => {
    return await ctx.storage.getUrl(args.storageId);
  },
});

Client component

"use client";

import { useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";

export function FileUploader() {
  const generateUrl = useMutation(api.files.generateUploadUrl);
  const saveFile = useMutation(api.files.saveFile);

  const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    const uploadUrl = await generateUrl();
    const result = await fetch(uploadUrl, {
      method: "POST",
      headers: { "Content-Type": file.type },
      body: file,
    });
    const { storageId } = await result.json();
    await saveFile({ storageId, name: file.name });
  };

  return <input type="file" onChange={handleUpload} />;
}

Serving files

Use ctx.storage.getUrl(storageId) inside a query to generate a public URL for any stored file. The URL is stable and CDN-backed.

Storage API reference

MethodDescription
ctx.storage.generateUploadUrl()Create a temporary upload URL
ctx.storage.getUrl(storageId)Get the public serving URL
ctx.storage.delete(storageId)Remove a file from storage
ctx.storage.getMetadata(storageId)Retrieve file size and content type

Tips

  • Store the storageId in a document field typed as v.id("_storage").
  • Set Content-Type on the upload request so Convex serves files with the correct MIME type.
  • Clean up orphaned files by deleting the storage entry when the parent document is removed.