Guide

Profile

Handle user profiles and avatar uploads

UNuxt provides user profile management with avatar uploads powered by Cloudinary.

Display user profile

Access user data through the session:

components/ProfileCard.vue
<script setup lang="ts">
const { user } = useUserSession()
</script>

<template>
  <UCard>
    <div class="flex items-center gap-4">
      <UAvatar :src="user.image" :alt="user.name" size="xl" />
      <div>
        <h2 class="font-semibold">{{ user.name }}</h2>
        <p class="text-muted">{{ user.email }}</p>
      </div>
    </div>
  </UCard>
</template>

Update profile information

Allow users to update their profile details:

pages/settings/profile.vue
<script setup lang="ts">
const { user, fetch: refreshSession } = useUserSession()

const state = reactive({
  name: user.value?.name || '',
  email: user.value?.email || ''
})

async function updateProfile() {
  await $fetch('/api/user/profile', {
    method: 'PATCH',
    body: state
  })
  await refreshSession()
}
</script>

<template>
  <UForm :state="state" @submit="updateProfile">
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" />
    </UFormField>

    <UFormField label="Email" name="email">
      <UInput v-model="state.email" type="email" />
    </UFormField>

    <UButton type="submit">Save Changes</UButton>
  </UForm>
</template>

Upload avatars with Cloudinary

Configure Cloudinary for image uploads:

.env
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret

Create an upload handler:

server/api/user/avatar.post.ts
import { v2 as cloudinary } from 'cloudinary'

export default defineEventHandler(async (event) => {
  const formData = await readFormData(event)
  const file = formData.get('avatar') as File

  const buffer = await file.arrayBuffer()
  const base64 = Buffer.from(buffer).toString('base64')

  const result = await cloudinary.uploader.upload(
    `data:${file.type};base64,${base64}`,
    {
      folder: 'avatars',
      transformation: [
        { width: 200, height: 200, crop: 'fill' }
      ]
    }
  )

  // Update user record with new avatar URL
  await updateUserAvatar(event.context.user.id, result.secure_url)

  return { url: result.secure_url }
})

Handle avatar upload in the UI

Create an avatar upload component:

components/AvatarUpload.vue
<script setup lang="ts">
const { user, fetch: refreshSession } = useUserSession()
const uploading = ref(false)

async function handleUpload(event: Event) {
  const input = event.target as HTMLInputElement
  const file = input.files?.[0]
  if (!file) return

  uploading.value = true

  const formData = new FormData()
  formData.append('avatar', file)

  await $fetch('/api/user/avatar', {
    method: 'POST',
    body: formData
  })

  await refreshSession()
  uploading.value = false
}
</script>

<template>
  <div class="flex items-center gap-4">
    <UAvatar :src="user.image" :alt="user.name" size="xl" />

    <label>
      <UButton as="span" :loading="uploading">
        Change Avatar
      </UButton>
      <input
        type="file"
        accept="image/*"
        class="hidden"
        @change="handleUpload"
      />
    </label>
  </div>
</template>

Next steps

Copyright © 2026