Nuxt Module

File Storage

Enable Convex storage scaffolding in nuxt-convex and use the Nuxt storage helpers in app code.

Use this guide when you want the module to scaffold the base Convex storage functions and expose the Nuxt storage helpers.

Before you begin

Make sure:

  • the core module is already installed
  • your app can resolve #convex/api
  • you have run npx convex dev in the project that owns the convex/ directory

Enable storage

Turn on the storage feature in nuxt.config.ts.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-convex'],
  convex: {
    storage: true,
  },
})

When storage: true is enabled, nuxt-convex:

  • adds the #convex/storage alias
  • auto-imports useConvexStorage and useConvexUpload
  • scaffolds convex/_hub/storage.ts if it does not already exist

Review the generated scaffold

The scaffolded file is the starting point for the storage API.

convex/_hub/storage.ts
import { v } from 'convex/values'
import { mutation, query } from '../_generated/server'

export const generateUploadUrl = mutation(async (ctx) => {
  return await ctx.storage.generateUploadUrl()
})

export const getUrl = query({
  args: { storageId: v.id('_storage') },
  handler: async (ctx, { storageId }) => {
    return await ctx.storage.getUrl(storageId)
  },
})

export const remove = mutation({
  args: { storageId: v.id('_storage') },
  handler: async (ctx, { storageId }) => {
    await ctx.storage.delete(storageId)
  },
})

This scaffold only covers upload URLs, public URLs, and deletion. Add your own metadata table and mutation when you need filenames, MIME types, ownership, or domain-specific rules.

Upload a file from Nuxt

Use useConvexUpload for the upload flow and your own mutation to persist metadata.

app/components/FileUpload.vue
<script setup lang="ts">
import { api } from '#convex/api'

const { mutate: saveUpload } = useConvexMutation(api.files.saveUpload)
const { upload, isUploading, error } = useConvexUpload({
  onSuccess: async (storageId, file) => {
    await saveUpload({
      storageId,
      name: file.name,
      type: file.type,
    })
  },
})

async function onSelect(event: Event) {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (file)
    await upload(file)
}
</script>

Verify the result

The setup is working when:

  • the module generates convex/_hub/storage.ts
  • useConvexUpload is auto-imported
  • a selected file uploads successfully and your follow-up mutation stores the metadata you expect

Next steps

Copyright © 2026