---
title: Example - Updating your user avatar
description: Updating your user avatar using supabase storage
---

import Image from "next/image";

## Updating your user avatar

Go to `http://localhost:3000/dashboard/account` and you will see a `Avatar Image` form under the `Personal Information` section.

<Image src="/images/supabase-storage-4.png" alt="Supabase Storage" width={600} height={400} />

Now that you have set up your supabase storage, you should be able to upload an image to your supabase storage, and replace your avatar image with your new uploaded image.

<Image src="/images/supabase-storage-5.png" alt="Supabase Storage" width={600} height={400} />

## But wait, how does this work?

### Image Upload Component

The `ImageUpload` component allows users to select and upload a new avatar image. Here's a breakdown of the key parts of the code:

```typescript title="app/(dashboard)/dashboard/account/image-upload.tsx"
'use client'
import { ChangeEvent, useRef, useState, useTransition } from "react";
import { toast } from "@/components/ui/use-toast";
import Image from "next/image";
import { uploadImage } from "@/utils/supabase/storage/client";
import { convertBlobUrlToFile } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { useRouter } from 'next/navigation';

export function ImageUpload({ user }: { user: any }) {
  const [avatarUrl, setAvatarUrl] = useState(user.avatar_url);
  const [imageUrl, setImageUrl] = useState("");
  const imageInputRef = useRef<HTMLInputElement>(null);
  const [isPending, startTransition] = useTransition();
  const router = useRouter();

  const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      const file = e.target.files[0];
      const newImageUrl = URL.createObjectURL(file);
      setImageUrl(newImageUrl);
    }
  };

  const handleClickUploadImagesButton = async () => {
    if (!user) {
      toast({
        title: "You need to be logged in to upload image",
        variant: "destructive",
      });
      return;
    }

    if (!imageUrl.length) {
      toast({
        title: "Please select an image to upload",
        variant: "destructive",
      });
      return;
    }

    startTransition(async () => {
      const imageFile = await convertBlobUrlToFile(imageUrl);
      const { imageUrl: uploadedImageUrl, error } = await uploadImage({
        file: imageFile,
        bucket: "avatar",
        folder: user.id,
      });

      if (error) {
        toast({
          title: error,
          variant: "destructive",
        });
        return;
      }

      if (uploadedImageUrl) {
        setAvatarUrl(uploadedImageUrl);
        await fetch('/api/update-avatar', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId: user.id, avatarUrl: uploadedImageUrl }),
        });

        toast({
          title: "Successfully uploaded image",
          variant: "default",
        });
        setImageUrl("");
        router.refresh();
      }
    });
  };

  return (
    <div className="flex flex-col gap-4 justify-center items-left py-6">
      <span className="text-sm font-medium">Avatar Image</span>
      <div className="ml-1 w-24 h-24 rounded-lg overflow-hidden border-2 border-primary p-0.5">
        <Image
          src={imageUrl || avatarUrl || '/default-avatar.png'}
          width={96}
          height={96}
          alt="User Avatar"
          className="object-cover rounded-lg"
        />
      </div>
      <input
        type="file"
        hidden
        ref={imageInputRef}
        onChange={handleImageChange}
        disabled={isPending}
      />
      <div className="space-y-2">
        <Button
          variant="outline"
          onClick={() => imageInputRef.current?.click()}
          disabled={isPending}
          className="mt-2"
        >
          Select New Image
        </Button>
        {imageUrl && (
          <Button
            onClick={handleClickUploadImagesButton}
            variant="default"
            disabled={isPending}
            className="ml-2"
          >
            {isPending ? "Uploading..." : "Upload Image"}
          </Button>
        )}
      </div>
      <p className="text-sm text-gray-500">
        {!imageUrl && !isPending && "Select a new image to update your avatar."}
        {imageUrl && !isPending && "Click 'Upload Image' to set your new avatar."}
        {isPending && "Uploading your new avatar..."}
      </p>
    </div>
  );
}
```

### API Route for Updating Avatar

To update the user's avatar in the database, we use an API route:

```typescript title="app/api/update-avatar/route.ts"
import { NextResponse } from 'next/server';
import { createClient } from '@/utils/supabase/server';

export async function POST(request: Request) {
  const supabase = createClient();
  const { userId, avatarUrl }: { userId: string; avatarUrl: string } = await request.json();

  const { data, error } = await supabase
    .from('users')
    .update({ avatar_url: avatarUrl })
    .eq('id', userId);

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 400 });
  }

  return NextResponse.json({ data });
}
```

This setup allows users to upload a new avatar image, which is then stored in Supabase Storage and the URL is updated in the user's profile in the database.