@aidalinfo/nuxt-file-storage-s3
| 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 |
@aidalinfo/nuxt-file-storage-s3
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
s3under thefileStoragemodule 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 fromruntimeConfig.fileStorage.s3first, falling back toruntimeConfig.public.fileStorage.s3only 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 theNUXT_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 (
storeFilepipeline), 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:
handleFileInput(event, override)useFileStorage(options)- module global config (
nuxt.config.ts) - internal defaults
V1 behavior:
- Images are compressed when enabled.
- PDF can be optimized server-side with
pdf-libwhencompression.serveris 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 | |
| 0.3.2 | ^3.0 | MIT | |
| 0.3.1 | ^3.0 | MIT | |
| 0.3.0 | ^3.0 | MIT |