Skip to main content

File Storage

Example: File Storage in Convex Chat

Convex supports uploading and serving files, such as images, via Convex storage.

Each file is stored keyed by a specific string storage ID. You can save the storage IDs within your documents

screenshots/file_storage.gif

Uploading Files

Uploading files require three steps:

  1. Generate an upload URL using a mutation that calls storage.generateUploadUrl().
  2. POST a file to that URL and receive a storage ID.
  3. Save the storage ID into your data model via a mutation.

As an example, these might appear in a form submission handler.

src/App.jsx
// Step 1: Get a short-lived upload URL
const postUrl = await generateUploadUrl();
// Step 2: POST the file to the URL
const result = await fetch(postUrl, {
method: "POST",
headers: { "Content-Type": selectedImage.type },
body: selectedImage,
});
const { storageId } = await result.json();
// Step 3: Save the newly allocated storage id to the messages table
await sendImage(storageId, name);
convex/sendMessage.js
// Generate a short-lived upload URL.
export const generateUploadUrl = mutation(async ({ storage }) => {
return await storage.generateUploadUrl();
});
// Save the storage ID within a message.
export const sendImage = mutation(async ({ db }, storageId, author) => {
const message = { body: storageId, author, format: "image" };
await db.insert("messages", message);
});

Fetching Files

Download URLs can be generated from storage IDs inside of queries and mutations.

convex/listMessages.js
export default query(async ({ db, storage }) => {
const messages = await db.query("messages").collect();
for (const message of messages) {
if (message.format == "image") {
message.url = await storage.getUrl(message.body);
}
}
return messages;
});

Rendering Images

Download URLs can be used in img tags to render images.

src/App.jsx
function Image({ message }) {
return <img src={message.url} height="300px" width="auto" />;
}

Deleting Files

Files can be deleted inside of mutations using storage.delete(). The delete method returns true if successful and false if the storage id is not found.

export const deleteById = mutation(async ({ storage }, storageId) => {
return await storage.delete(storageId);
});