Advanced

Storage Adapters

Create your own storage adapters for custom cloud providers.

Custom Storage Adapters

Use defineStorageAdapter to create custom storage providers for uploading files to any cloud or self-hosted storage.

Basic Structure

import { defineStorageAdapter } from "nuxt-upload-kit"

interface MyStorageOptions {
  apiUrl: string
  apiKey: string
}

interface MyStorageResult {
  url: string
  id: string
}

export const PluginMyStorage = defineStorageAdapter<MyStorageOptions, MyStorageResult>((options) => ({
  id: "my-storage",
  hooks: {
    upload: async (file, context) => {
      // Upload implementation
      return { url: "...", id: "..." }
    },
  },
}))

Hook Reference

HookRequiredWhenPurposeReturn
uploadYesDuring uploadSend file to storage{ url, ...data }
getRemoteFileNoLoading existingFetch file metadataFile metadata
removeNoDeleting fileDelete from storagevoid

Complete Example

import { defineStorageAdapter } from "nuxt-upload-kit"

interface MyStorageOptions {
  apiUrl: string
  apiKey: string
}

interface MyStorageResult {
  url: string
  id: string
  etag: string
}

export const PluginMyStorage = defineStorageAdapter<MyStorageOptions, MyStorageResult>((options) => ({
  id: "my-storage",
  hooks: {
    // Required: Upload a file
    upload: async (file, context) => {
      const formData = new FormData()
      formData.append("file", file.data as Blob)

      const response = await fetch(options.apiUrl, {
        method: "POST",
        headers: { "X-API-Key": options.apiKey },
        body: formData,
      })

      const result = await response.json()

      // Must return object with 'url' property
      return {
        url: result.fileUrl,
        id: result.fileId,
        etag: result.etag,
      }
    },

    // Optional: Get file metadata for existing files
    getRemoteFile: async (fileId, context) => {
      const response = await fetch(`${options.apiUrl}/${fileId}`, {
        headers: { "X-API-Key": options.apiKey },
      })

      const metadata = await response.json()

      return {
        size: metadata.size,
        mimeType: metadata.contentType,
        remoteUrl: metadata.url,
        preview: metadata.thumbnailUrl,
      }
    },

    // Optional: Delete a file
    remove: async (file, context) => {
      // Use storageKey for deletion (set after upload or from initialFiles)
      if (!file.storageKey) return

      await fetch(`${options.apiUrl}/${file.storageKey}`, {
        method: "DELETE",
        headers: { "X-API-Key": options.apiKey },
      })
    },
  },
}))

Progress Reporting

Use context.onProgress to report upload progress:

upload: async (file, context) => {
  const xhr = new XMLHttpRequest()

  return new Promise((resolve, reject) => {
    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) {
        context.onProgress(Math.round((e.loaded / e.total) * 100))
      }
    }

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.response))
      } else {
        reject(new Error("Upload failed"))
      }
    }

    xhr.onerror = () => reject(new Error("Network error"))

    const formData = new FormData()
    formData.append("file", file.data as Blob)

    xhr.open("POST", options.apiUrl)
    xhr.setRequestHeader("X-API-Key", options.apiKey)
    xhr.send(formData)
  })
}

Return Value Requirements

The upload hook must return an object containing at least a url property:

// Minimum required
return { url: "https://storage.example.com/file.jpg" }

// With additional metadata (recommended)
return {
  url: "https://storage.example.com/file.jpg",
  storageKey: "uploads/user-123/file.jpg", // Full path for retrieval/deletion
  etag: "abc123",
  bucket: "my-bucket",
}

The returned object becomes available as file.uploadResult after the upload completes.

The storageKey Pattern

The storageKey is the full path used to identify files in storage. It enables:

  • Loading existing files via initialFiles
  • Deleting files with removeFile()
  • Round-trip consistency (upload → store key → retrieve later)
// Upload returns storageKey
upload: async (file, context) => {
  const path = `uploads/${options.folder}/${file.id}`

  await uploadToStorage(path, file.data)

  return {
    url: `https://cdn.example.com/${path}`,
    storageKey: path, // Save this to your database
  }
}

// getRemoteFile receives storageKey
getRemoteFile: async (storageKey, context) => {
  const metadata = await getFromStorage(storageKey)

  return {
    size: metadata.size,
    mimeType: metadata.contentType,
    remoteUrl: `https://cdn.example.com/${storageKey}`,
  }
}

// remove receives file with storageKey
remove: async (file, context) => {
  if (!file.storageKey) return // Not uploaded yet

  await deleteFromStorage(file.storageKey)
}
Store the storageKey in your database alongside other file metadata. Pass it back via initialFiles to reload files.

Remote File Metadata

The getRemoteFile hook must return an object with these properties:

interface RemoteFileMetadata {
  size: number        // File size in bytes
  mimeType: string    // MIME type (e.g., "image/jpeg")
  remoteUrl: string   // URL to access the file
  preview?: string    // Optional thumbnail URL
}

Usage

const uploader = useUploadKit({
  storage: PluginMyStorage({
    apiUrl: "https://api.example.com/files",
    apiKey: "your-api-key",
  }),
})

// After upload, access the result
uploader.on("upload:complete", (files) => {
  files.forEach((file) => {
    console.log("File URL:", file.uploadResult.url)
    console.log("File ID:", file.uploadResult.id)
  })
})

Error Handling

Throw errors to fail the upload:

upload: async (file, context) => {
  const response = await fetch(options.apiUrl, {
    method: "POST",
    body: file.data,
  })

  if (!response.ok) {
    throw new Error(`Upload failed: ${response.statusText}`)
  }

  return await response.json()
}

The error will be captured and the file's status will be set to error with the error message available in file.error.

Copyright © 2026