File Lifecycle
Plugin Lifecycle Diagram
╔═══════════════════════════════════════════════════════════════════════╗
║ ADDING FILES ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ addFile()/addFiles() ║
║ │ ║
║ ▼ ║
║ ┌──────────┐ ┌─────────────┐ ┌─────────────┐ ║
║ │ validate │────▶│ preprocess │────▶│ file:added │ ║
║ └──────────┘ └─────────────┘ └─────────────┘ ║
║ │ │ ║
║ ▼ (fail) ▼ ║
║ ┌────────────┐ autoUpload? ║
║ │ file:error │ / \ ║
║ └────────────┘ yes no ║
║ │ │ ║
║ ▼ ▼ ║
║ upload() [waiting] ║
╚═══════════════════════════════════════════════════════════════════════╝
│
▼
╔═══════════════════════════════════════════════════════════════════════╗
║ UPLOAD PROCESS ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌──────────────┐ ║
║ │ upload:start │ ║
║ └──────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌─────────┐ ┌────────────────┐ ┌────────────────┐ ║
║ │ process │────▶│ storage.upload │────▶│ upload:progress│ ║
║ └─────────┘ └────────────────┘ └────────────────┘ ║
║ │ │ │ ║
║ ▼ (fail) ▼ (fail) ▼ ║
║ ┌────────────┐ ┌────────────┐ ┌──────────┐ ║
║ │ file:error │ │ file:error │ │ complete │ ║
║ └────────────┘ └────────────┘ └──────────┘ ║
║ │ ║
║ ▼ ║
║ ┌────────────────┐ ║
║ │upload:complete │ ║
║ └────────────────┘ ║
║ │ ║
║ ▼ ║
║ all files done? ║
║ │ yes ║
║ ▼ ║
║ ┌────────────────┐ ║
║ │ files:uploaded │ ║
║ └────────────────┘ ║
╚═══════════════════════════════════════════════════════════════════════╝
Hook Execution Order
| Stage | Hook | Description |
|---|---|---|
| 1 | validate | Check file validity (type, size, count) |
| 2 | preprocess | Immediate transformations (thumbnails) |
| 3 | process | Pre-upload transformations (compression) |
| 4 | upload | Storage plugin uploads file |
| 5 | complete | Post-upload processing |
File States
A file goes through these statuses:
| Status | Description |
|---|---|
waiting | File added, ready for upload |
preprocessing | Running preprocess hooks |
uploading | Currently uploading |
postprocessing | Running complete hooks |
complete | Successfully uploaded |
error | Failed at any stage |
File Sources
Files can originate from different sources:
type FileSource = 'local' | 'storage' | 'instagram' | 'dropbox' | ...
| Source | Description | Has data | Has remoteUrl |
|---|---|---|---|
local | Selected from device | Yes (File/Blob) | After upload |
storage | Loaded from storage | No (null) | Yes |
Local vs Remote Files
Local Files
Created when user selects files:
const localFile: LocalUploadFile = {
source: "local",
data: File | Blob, // Has binary data
remoteUrl: undefined, // Not uploaded yet
status: "waiting",
}
After upload:
// data is still available
// remoteUrl is now set
// status is 'complete'
Remote Files
Loaded from existing storage:
const remoteFile: RemoteUploadFile = {
source: "storage",
data: null, // No local data
remoteUrl: "https://...", // Already uploaded
status: "complete",
}
Working with Both Types
Use the source property to handle files differently:
if (file.source === "local") {
// TypeScript knows: file.data is File | Blob
const blob = file.data
const objectUrl = URL.createObjectURL(blob)
} else {
// TypeScript knows: file.data is null, file.remoteUrl exists
const url = file.remoteUrl
}
Or use the helper methods:
// Works for both local and remote
const url = await uploader.getFileURL(file.id)
const data = await uploader.getFileData(file.id) // Fetches remote if needed
Replacing File Data
When you edit a file (crop, rotate), call replaceFileData:
const cropped = await cropImage(originalBlob)
await uploader.replaceFileData(file.id, cropped, "cropped-image.jpg")
This:
- Updates
file.datawith new content - Changes
sourceto'local'(even if it was remote) - Resets
statusto'waiting' - Clears
remoteUrl - Re-runs preprocess hooks (regenerates thumbnail)
- Emits
file:replacedevent
Initializing Existing Files
Load files from your database:
// Replaces the file list with these remote files
await uploader.initializeExistingFiles([
{ storageKey: "path/to/file1.jpg" },
{ storageKey: "path/to/file2.png" },
])
The storage plugin's getRemoteFile hook fetches metadata:
// Storage plugin provides:
{
size: number,
mimeType: string,
remoteUrl: string,
preview?: string
}
Appending Existing Files
Use appendExistingFiles when you need to add remote files alongside files the user has already selected — for example, when a user picks items from a media library:
// User already added local files via file picker
await uploader.addFiles(localFiles)
// Later, user picks files from a media library
const added = await uploader.appendExistingFiles([
{ storageKey: "library/photo-1.jpg" },
{ storageKey: "library/photo-2.jpg" },
])
Key differences from initializeExistingFiles:
initializeExistingFiles | appendExistingFiles | |
|---|---|---|
| Behavior | Replaces all files | Adds to existing files |
| Deduplication | No | Skips files already present by storageKey |
| maxFiles | Not checked | Respected (truncates to fit) |
| Events | None | Emits file:added per file |
| Returns | void | UploadFile[] (files actually added) |
This lets all files — local, remote, from any source — live as first-class citizens in a single files ref, eliminating the need for separate tracking arrays.
Memory Management
Nuxt Upload Kit automatically manages memory:
Object URLs
- Created object URLs are tracked
- Automatically revoked when file is removed
- Cleaned up on component unmount
Large Files
For large files (>100MB), prefer streaming:
// Loads into memory (avoid for large files)
const blob = await uploader.getFileData(file.id)
// More efficient - returns URL
const url = await uploader.getFileURL(file.id)
// Most efficient - streams data
const stream = await uploader.getFileStream(file.id)
Event Timeline
Here's the complete event sequence:
addFiles([file])
│
├─ (validation passes)
│
├─ file:added ──────────── File visible in UI
│
upload()
│
├─ upload:start ────────── Upload begins
│
├─ upload:progress ─────── Progress updates (0-100%)
│ upload:progress
│ upload:progress
│ ...
│
├─ upload:complete ─────── All files done
│
└─ (check file.uploadResult for results)
Error events can occur at any point:
addFiles([invalid])
│
└─ file:error ──────────── Validation failed
upload()
│
├─ upload:start
│
└─ file:error ──────────── Upload failed
Debugging Lifecycle
Enable verbose logging:
if (import.meta.dev) {
const events = [
"file:added",
"file:removed",
"file:replaced",
"file:error",
"upload:start",
"upload:progress",
"upload:complete",
"image-compressor:start",
"image-compressor:complete",
]
events.forEach((event) => {
uploader.on(event, (data) => {
console.log(`[Upload] ${event}:`, data)
})
})
}

