Skip to content

Advanced configuration

This page documents all configuration options for ConvexFS, including storage backend settings, URL TTLs, and garbage collection tuning.

When creating a ConvexFS instance, you pass an options object with the following properties:

const fs = new ConvexFS(components.fs, {
storage: {
type: "bunny",
apiKey: process.env.BUNNY_API_KEY!,
storageZoneName: process.env.BUNNY_STORAGE_ZONE!,
cdnHostname: process.env.BUNNY_CDN_HOSTNAME!,
tokenKey: process.env.BUNNY_TOKEN_KEY,
region: "ny",
},
downloadUrlTtl: 3600,
blobGracePeriod: 86400,
});

Configuration for the storage backend. ConvexFS supports Bunny.net Edge Storage and an in-memory test backend for use with convex-test.

OptionTypeRequiredDescription
typestringYesMust be "bunny"
apiKeystringYesYour Bunny.net Edge Storage API key (from the FTP & API Access panel)
storageZoneNamestringYesName of your storage zone
cdnHostnamestringYesCDN hostname for downloads (e.g., "myzone.b-cdn.net" or a custom domain)
tokenKeystringNoToken authentication key for signed CDN URLs. Strongly recommended even for public zones, as token generation is very inexpensive.
regionstringNoStorage zone region code. Leave empty for Frankfurt (default). See Bunny.net storage endpoints for available regions: uk, ny, la, sg, se, br, jh, syd

Time-to-live for signed download URLs, in seconds.

TypeDefaultDescription
number3600How long signed CDN URLs remain valid (1 hour)

Shorter TTLs improve security by reducing the window where a leaked or revoked URL can still be used. Longer TTLs improve user experience for media playback and reduce load on your Convex backend.

See Authn & Authz for security considerations.

Grace period before orphaned blobs are permanently deleted, in seconds.

TypeDefaultDescription
number86400How long to keep orphaned blobs before GC (24 hours)

This is your recovery window for accidental deletions. During this period, you can restore deleted files using their blobId.

See Garbage collection for details on how this works with the background GC jobs.

When calling registerRoutes(), you configure the HTTP endpoints for uploads and downloads:

registerRoutes(http, components.fs, fs, {
pathPrefix: "/fs",
uploadAuth: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
return identity !== null;
},
downloadAuth: async (ctx, blobId) => {
return true; // Public downloads
},
});
TypeDefaultDescription
string"/fs"URL path prefix for upload/download routes

The component registers two routes under this prefix:

  • POST {pathPrefix}/upload — Upload endpoint
  • GET {pathPrefix}/blobs/{blobId} — Download redirect endpoint

Authentication callback for uploads. Called before any upload is accepted.

uploadAuth: async (ctx: HttpActionCtx) => Promise<boolean>;

Return true to allow the upload, false to reject with a 401 response.

See Authn & Authz for details.

Authentication callback for downloads. Called before redirecting to a signed CDN URL.

downloadAuth: async (ctx: HttpActionCtx, blobId: string) => Promise<boolean>;

Return true to allow the download, false to reject with a 401 response.

See Authn & Authz for details.

You can mount multiple ConvexFS instances in the same project. This is useful when you need different storage configurations—for example, separate zones for user uploads vs. static assets with different access control rules, or different retention policies.

In your convex.config.ts, mount the component with different names:

import { defineApp } from "convex/server";
import fs from "convex-fs/convex.config.js";
const app = defineApp();
app.use(fs, { name: "userFiles" });
app.use(fs, { name: "staticAssets" });
export default app;

Create a file for each filesystem with its own configuration:

convex/userFiles.ts
import { ConvexFS } from "convex-fs";
import { components } from "./_generated/api";
export const userFiles = new ConvexFS(components.userFiles, {
storage: {
type: "bunny",
apiKey: process.env.USER_FILES_BUNNY_API_KEY!,
storageZoneName: process.env.USER_FILES_STORAGE_ZONE!,
cdnHostname: process.env.USER_FILES_CDN_HOSTNAME!,
},
blobGracePeriod: 604800, // 7 days for user content
});
convex/staticAssets.ts
import { ConvexFS } from "convex-fs";
import { components } from "./_generated/api";
export const staticAssets = new ConvexFS(components.staticAssets, {
storage: {
type: "bunny",
apiKey: process.env.STATIC_BUNNY_API_KEY!,
storageZoneName: process.env.STATIC_STORAGE_ZONE!,
cdnHostname: process.env.STATIC_CDN_HOSTNAME!,
},
blobGracePeriod: 86400, // 24 hours for static assets
});

Mount each filesystem at a different path prefix:

convex/http.ts
import { httpRouter } from "convex/server";
import { registerRoutes } from "convex-fs";
import { components } from "./_generated/api";
import { userFiles } from "./userFiles";
import { staticAssets } from "./staticAssets";
const http = httpRouter();
registerRoutes(http, components.userFiles, userFiles, {
pathPrefix: "/user-files",
uploadAuth: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
return identity !== null;
},
downloadAuth: async (ctx, blobId) => {
// Check user has access to this blob
return true;
},
});
registerRoutes(http, components.staticAssets, staticAssets, {
pathPrefix: "/static",
uploadAuth: async (ctx) => {
// Only admins can upload static assets
const identity = await ctx.auth.getUserIdentity();
return identity?.role === "admin";
},
downloadAuth: async () => true, // Public access
});
export default http;