Skip to main content

Building an Avatar Upload Form in React with Imgwire

This tutorial shows how to upload an avatar from a React app, preview it as a square image, and store the Imgwire image ID with the user's profile.

Use this pattern for profile photos, team member avatars, account icons, and small identity images.

Install the React SDK

yarn add @imgwire/react @imgwire/js

Create an Imgwire Client Key for the frontend. If your app uses signed uploads, also create the Next.js upload token endpoint or an equivalent backend route.

Add the provider

Wrap the part of your app that uploads or renders Imgwire images:

import { ImgwireProvider } from '@imgwire/react';

export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<ImgwireProvider
config={{
apiKey: import.meta.env.VITE_IMGWIRE_CLIENT_KEY,
getUploadToken: async () => {
const response = await fetch('/api/imgwire/upload-token', {
method: 'POST',
});

if (!response.ok) {
throw new Error('Unable to create Imgwire upload token');
}

const { uploadToken } = await response.json();
return uploadToken;
},
}}
>
{children}
</ImgwireProvider>
);
}

Omit getUploadToken only when your Client Key allows unsigned uploads and that tradeoff is acceptable for the avatar flow.

Build the avatar uploader

Use useUpload to send the file to Imgwire and image.url(...) to generate a square preview.

import { ChangeEvent, useState } from 'react';
import { useUpload } from '@imgwire/react';

type AvatarValue = {
imageId: string;
previewUrl: string;
};

export function AvatarUploadForm() {
const [avatar, setAvatar] = useState<AvatarValue | null>(null);
const [upload, progress] = useUpload();
const [error, setError] = useState<string | null>(null);

async function onFileChange(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) return;

if (!file.type.startsWith('image/')) {
setError('Choose an image file.');
return;
}

setError(null);

const image = await upload(file, {
purpose: 'user avatar',
customMetadata: {
surface: 'profile-avatar',
},
});

setAvatar({
imageId: image.id,
previewUrl: image.url({
width: 256,
height: 256,
resizing_type: 'cover',
gravity: 'attention',
format: 'auto',
quality: 'auto',
}),
});
}

async function saveAvatar() {
if (!avatar) return;

await fetch('/api/me/avatar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
imageId: avatar.imageId,
}),
});
}

return (
<section>
<label htmlFor="avatar">Avatar</label>
<input id="avatar" type="file" accept="image/*" onChange={onFileChange} />

{progress.percent !== null ? (
<p>{Math.round(progress.percent)}% uploaded</p>
) : null}

{error ? <p role="alert">{error}</p> : null}

{avatar ? (
<img
src={avatar.previewUrl}
width={128}
height={128}
alt="Avatar preview"
style={{ borderRadius: '50%' }}
/>
) : null}

<button type="button" onClick={saveAvatar} disabled={!avatar}>
Save avatar
</button>
</section>
);
}

The upload creates the original Imgwire image. The preview URL creates a delivered variant with a square crop and automatic output format.

Render the saved avatar

When the profile already has an image ID, render it with the React Image component:

import { Image } from '@imgwire/react';

export function Avatar({ imageId, name }: { imageId: string; name: string }) {
return (
<Image
id={imageId}
width={128}
height={128}
resizing_type="cover"
gravity="attention"
format="auto"
quality="auto"
alt={`${name} avatar`}
style={{ borderRadius: '50%' }}
/>
);
}

The component resolves the image ID through the configured Imgwire client and applies the transformation options to the delivery URL.

Avatar crop guidance

For avatars, a centered crop is not always enough. User uploads can include faces, illustrations, or objects that are slightly off-center. Start with:

image.url({
width: 256,
height: 256,
resizing_type: 'cover',
gravity: 'attention',
format: 'auto',
quality: 'auto',
});

Use an explicit crop only when your app has a separate crop UI and stores the selected coordinates.

Best practices

  • Store the Imgwire image ID on the user profile.
  • Use a small fixed square variant for avatar display.
  • Set upload limits on the Client Key to match your UI.
  • Use signed uploads when avatars should only be uploaded by signed-in users.
  • Keep alt text tied to the user or entity name, not the file name.

Last updated at: May 8, 2026