@aidalinfo/nuxt-file-storage-s3

Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.
8,849
Install
npm install @aidalinfo/nuxt-file-storage-s3
Latest Version:0.3.7
Vue:3
License:MIT
Last Updated:Jun 5, 2026
Links: GitHub  ·  npm
Maintainer: aidalinfo

Nuxt File Storage Banner

@aidalinfo/nuxt-file-storage-s3

Nuxt

Easy solution to store files in your Nuxt apps — supports local storage and S3-compatible storage (AWS S3, Garage, MinIO, DigitalOcean Spaces…).

Features

  • 📁  Handle file inputs on the frontend and serialize them for the backend
  • 🗜️  Optional client-side compression before serialization (images, PDF strategy-ready)
  • 🧾  Optional server-side PDF optimization before local/S3 storage (with safe fallback)
  • 🖴  Store files locally via Nitro server engine
  • ☁️  Store files on S3 or any S3-compatible service
  • 🔀  Unified API — same functions work for both local and S3, automatically detected from config

Quick Setup

npm install @aidalinfo/nuxt-file-storage-s3
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@aidalinfo/nuxt-file-storage-s3'],
  fileStorage: {
    mount: './uploads', // local storage
  },
})

Configuration

Local storage

fileStorage: {
  mount: process.env.mount || './uploads',
}

S3 / S3-compatible (recommended multi-bucket mode)

fileStorage: {
  s3: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
    region: process.env.AWS_REGION || 'us-east-1',
    defaultBucketName: 'private',
    buckets: {
      private: { bucket: process.env.AWS_S3_PRIVATE_BUCKET || 'my-private-bucket' },
      public: { bucket: process.env.AWS_S3_PUBLIC_BUCKET || 'my-public-bucket' },
    },
    endpoint: process.env.AWS_ENDPOINT,  // optional, for S3-compatible services
    forcePathStyle: true,                // required for most S3-compatible services
  },
}

Bucket resolution order is: explicit bucketName argument -> defaultBucketName -> legacy s3.bucket. defaultBucketName is a logical name (private / public) and is used automatically when you do not pass an explicit bucketName to server utils.

Legacy single-bucket mode is still supported:

fileStorage: {
  s3: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
    region: process.env.AWS_REGION || 'us-east-1',
    bucket: process.env.AWS_S3_BUCKET || 'my-bucket',
  },
}

If s3 is configured, all server utilities will automatically use S3. Otherwise they fall back to local storage.

[!IMPORTANT] S3 credentials are kept server-side. When you declare s3 under the fileStorage module config, the module routes it to the private runtime config (runtimeConfig.fileStorage.s3) — it is never serialized into the public runtime config that ships to the browser. The S3 server utilities read from runtimeConfig.fileStorage.s3 first, falling back to runtimeConfig.public.fileStorage.s3 only for backward compatibility.

Do not put credentials under runtimeConfig.public.*. To override them at runtime, use the private env vars (e.g. NUXT_FILE_STORAGE_S3_SECRET_ACCESS_KEY, NUXT_FILE_STORAGE_S3_ACCESS_KEY_ID), not the NUXT_PUBLIC_* ones.

Compression (client + server PDF)

The payload contract between frontend and backend stays unchanged (ServerFile[]).

  • Client compression happens before FileReader.readAsDataURL.
  • Server compression (PDF only) happens right before storage (storeFile pipeline), for both local and S3 backends.
fileStorage: {
  compression: {
    client: {
      enabled: true,
      image: {
        enabled: true,
        quality: 0.8,
        maxWidth: 1920,
        maxHeight: 1920,
      },
      pdf: {
        enabled: true,
      },
    },
    server: {
      enabled: true,
      pdf: {
        enabled: true,
      },
    },
  },
}

Legacy flat client configuration is still supported:

fileStorage: {
  compression: {
    enabled: true,
    image: { enabled: true, quality: 0.8, maxWidth: 1920, maxHeight: 1920 },
    pdf: { enabled: true },
  },
}

Option priority is:

  1. handleFileInput(event, override)
  2. useFileStorage(options)
  3. module global config (nuxt.config.ts)
  4. internal defaults

V1 behavior:

  • Images are compressed when enabled.
  • PDF can be optimized server-side with pdf-lib when compression.server is enabled.
  • Non-image / non-PDF files are not processed.
  • Server-side PDF fallback is automatic when optimization fails, output is invalid, or size is not smaller.
  • If client compression fails or is unsupported, upload falls back to original file automatically.
  • No format conversion in V1 (if output MIME differs, original file is used).

Usage

Frontend — useFileStorage()

<script setup>
const { files, handleFileInput } = useFileStorage({ clearOldFiles: true })

const submit = async () => {
  await $fetch('/api/upload', {
    method: 'POST',
    body: { files: files.value },
  })
}
</script>

<template>
  <input type="file" multiple @input="handleFileInput" />
  <button @click="submit">Upload</button>
</template>

Per-upload override example:

<script setup>
const { handleFileInput } = useFileStorage({
  compression: { enabled: true },
})

const onInputNoCompression = (event) => {
  return handleFileInput(event, {
    compression: { enabled: false },
  })
}
</script>

<template>
  <input type="file" @input="onInputNoCompression" />
</template>

Multiple file inputs: create a new instance per input.

<script setup>
const { handleFileInput: handleAvatar, files: avatar } = useFileStorage()
const { handleFileInput: handleBanner, files: banner } = useFileStorage()
</script>

Backend — server utilities

All functions are auto-imported in server routes (server/api/, server/routes/…).

Unified functions (recommended)

Automatically use S3 or local storage based on your config.

// server/api/upload.post.ts
import type { ServerFile } from '@aidalinfo/nuxt-file-storage-s3'

export default defineEventHandler(async (event) => {
  const { files } = await readBody<{ files: ServerFile[] }>(event)

  const keys = await Promise.all(
    files.map(file => storeFile(file, 12, 'uploads'))
  )

  return keys
})
Function Description
storeFile(file, name, folder?, bucketName?) Store a file. name can be a string (fixed name) or number (random ID length). Returns the file key.
getFile(key, expiresIn?, bucketName?) Returns a signed URL (S3) or local path. expiresIn defaults to 3600s.
listFiles(folder?, bucketName?) List all files in a folder / S3 prefix.
removeFile(key, bucketName?) Delete a file.

Example with explicit logical bucket:

await storeFile(file, 12, 'avatars', 'public')
S3-specific functions
storeFileToS3(file, name, folder?, bucketName?)     // → key
getFileFromS3(key, expiresIn?, bucketName?)         // → signed URL
getFileStreamFromS3(key, bucketName?)               // → ReadableStream
listFilesFromS3(prefix?, maxKeys?, bucketName?)     // → string[]
deleteFileFromS3(key, bucketName?)
Local-specific functions
storeFileLocally(file, name, folder?)  // → filename
getFileLocally(filename, folder?)      // → path
getFilesLocally(folder?)               // → string[]
deleteFile(filename, folder?)

Local dev with Garage (S3-compatible)

Garage is a lightweight self-hosted S3-compatible storage server, great for local development.

A docker-compose.yml is included at the root of this repo. Start it with:

docker compose up -d
./docker/init-garage.sh

The init script creates a bucket and an access key, and prints the credentials to use in your .env:

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=garage
AWS_S3_PRIVATE_BUCKET=my-private-bucket
AWS_S3_PUBLIC_BUCKET=my-public-bucket
# Legacy mode:
# AWS_S3_BUCKET=my-bucket
AWS_ENDPOINT=http://localhost:3900

A web UI (s3manager) is also available at http://localhost:8080.


Contribution

Run into a problem? Open a new issue.

Want to add a feature? PRs are welcome!

git clone https://github.com/aidalinfo/nuxt-file-storage-s3 && cd nuxt-file-storage-s3
npm i
npm run dev:prepare
npm run dev

Version History

Version Released Vue License
0.3.7 ^3.0 MIT
0.3.6 ^3.0 MIT
0.3.5 ^3.0 MIT
0.3.4 ^3.0 MIT
0.3.3 ^3.0 MIT