Storage

Storage

Upload, serve, and delete files in VitNode with pluggable storage adapters - local disk, AWS S3, Cloudflare R2, or Supabase Storage.

VitNode provides an integrated storage layer accessible via c.get("storage"). It handles file validation, automatic WebP image optimization, database indexing in core_files, and persistent storage across multiple backends.

Quick start

1. Register a Storage Adapter

In apps/api/src/vitnode.api.config.ts, configure the local disk adapter:

apps/api/src/vitnode.api.config.ts
import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local"
import { buildApiConfig } from "@vitnode/core/vitnode.config"

export const vitNodeApiConfig = buildApiConfig({
  storage: {
    adapter: LocalStorageAdapter(), 
  },
})

2. Upload Files in an API Route

Handle uploads in a Hono route using c.get("storage").upload():

plugins/blog/src/api/modules/posts/routes/upload-cover.route.ts
import { z } from "@hono/zod-openapi"
import { buildRoute } from "@vitnode/core/api/lib/route"

export const uploadCoverRoute = buildRoute({
  pluginId: "blog",
  route: {
    method: "post",
    path: "/cover",
    request: {
      body: {
        content: {
          "multipart/form-data": {
            schema: z.object({
              file: z.instanceof(File),
            }),
          },
        },
      },
    },
  },
  handler: async (c) => {
    const { file } = await c.req.parseBody()

    const uploaded = await c.get("storage").upload({
      file: file as File,
      folder: "covers",
      maxBytes: 5 * 1024 * 1024, // 5 MB
      allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
    })

    return c.json(uploaded)
  },
})

upload() saves the file, creates a record in core_files, and returns { id, url, width, height, size }.


Supported Storage Adapters


Image Optimization

The storage service can automatically convert uploaded images to WebP and constrain dimensions:

const uploaded = await c.get("storage").upload({
  file,
  folder: "avatars",
  convertImagesToWebp: true, 
  maxDimensions: { width: 1200, height: 1200 }, 
})

Deleting Files

Remove files and clean up storage using delete():

await c.get("storage").delete({ fileId: 42 })

This removes the file from the configured storage bucket and deletes its row from core_files.


Verifying in AdminCP

Test your storage adapter anytime in the AdminCP:

  1. Navigate to System → Integrations (/admin/core/system/integrations).
  2. On the Storage card, click Test Storage to verify bucket credentials and upload capabilities.
  3. Inspect uploaded files anytime under System → Files (/admin/core/system/files).

Learn More