File Storage
Examples: File Storage with HTTP Endpoints, File Storage with Queries and Mutations
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.
There are two main ways to use Convex storage:
- Use HTTP endpoints to handle file uploads and downloads
- Use queries and mutations to get upload and download URLs
If you don't want to define HTTP endpoints in order to use storage, you can use queries and mutations instead, but with a few more steps on the client.
File Storage with HTTP endpoints
Uploading files
A file can be sent as the body of an HTTP endpoint, and directly stored using
storage.store
, which returns a storageId
which we can save in our data
model.
Here's an example of uploading an image as a chat message via a form submission handler:
// e.g. https://happy-animal-123.convex.site/sendImage?author=User+123
const sendImageUrl = new URL(`${convexSiteUrl}/sendImage`);
sendImageUrl.searchParams.set("author", name);
await fetch(sendImageUrl, {
method: "POST",
headers: { "Content-Type": selectedImage.type },
body: selectedImage,
});
http.route({
path: "/sendImage",
method: "POST",
handler: httpEndpoint(async ({ storage, runMutation }, request) => {
// Store the image
const storageId = await storage.store(request);
const author = new URL(request.url).searchParams.get("author");
// Save the storage ID to the messages table via a mutation
await runMutation("sendMessage:sendImage", { storageId, author });
return new Response(null, {
status: 200,
// CORS headers
headers: new Headers({
// e.g. https://mywebsite.com
"Access-Control-Allow-Origin": process.env.CLIENT_ORIGIN,
Vary: "origin",
}),
});
}),
});
When calling this endpoint from a website, you will need to add CORS headers in your HTTP endpoints.
Fetching files
Files can be returned in the response of an HTTP endpoint:
http.route({
path: "/getImage",
method: "GET",
handler: httpEndpoint(async ({ storage }, request) => {
const storageId = new URL(request.url).searchParams.get("storageId");
const responseOrNull = await storage.get(storageId);
if (responseOrNull === null) {
return new Response("Image not found", {
status: 404,
});
}
return responseOrNull;
}),
});
These can be used in img
tags to render images:
function Image({ storageId }) {
// e.g. https://happy-animal-123.convex.site/getImage?storageId=456
const getImageUrl = new URL(`${convexSiteUrl}/getImage`);
getImageUrl.searchParams.set("storageId", storageId);
return <img src={getImageUrl.href} height="300px" width="auto" />;
}
File storage with queries and mutations
Uploading files
Uploading files require three steps:
- Generate an upload URL using a mutation that calls
storage.generateUploadUrl()
. - POST a file to that URL and receive a storage ID.
- Save the storage ID into your data model via a mutation.
As an example, these might appear in a form submission handler.
// 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, author: name });
// 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
using storage.getUrl()
.
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;
});
Download URLs can be used in img
tags to render images.
function Image({ message }) {
return <img src={message.url} height="300px" width="auto" />;
}
Interacting with files
Mutations and HTTP endpoints support deleting files via
storage.delete()
.
export const deleteById = mutation(async ({ storage }, { storageId }) => {
return await storage.delete(storageId);
});
Queries, mutations, and HTTP endpoints all support fetching file metadata like
size and content type via
storage.getMetadata()
.
export const getMetadata = query(async ({ storage }, { storageId }) => {
return await storage.getMetadata(storageId);
});