Account

Avatar Upload

Upload and set a user avatar with presigned S3 URLs

Avatar endpoints require a signed-in user session. See Authentication for the initial sign-in flow.

Avatar upload is a two-step process: request a presigned upload URL, then upload the file directly to S3. The URL is valid for 10 minutes. After upload, the image is processed to WebP. Confirm with client.updateUser({ confirmAvatar: true }) (PATCH /users/me), or poll until avatarUrl is on the profile.

Step 1: Get Upload URL

contentType is image/jpeg, image/png, image/gif, or image/webp.

SDK
const contentType = "image/jpeg";
const { uploadUrl, assetKey, cdnUrl } = await client.createAvatarUploadUrl(contentType);

Get avatar upload URL.

Step 2: Upload the Image

Upload the raw file bytes directly to the presigned URL. The Content-Type on the S3 PUT must be the type you presigned.

const file = inputElement.files[0];

await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": contentType },
  body: file,
});

Step 3: Confirm

Confirming is PATCH /users/me with { "confirmAvatar": true }, which is client.updateUser({ confirmAvatar: true }). Retry while the call returns 400 (not processed yet) until avatarUrl is set.

SDK
async function confirmAvatar(maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const profile = await client.updateUser({ confirmAvatar: true });
      if (profile.avatarUrl) {
        return profile.avatarUrl;
      }
    } catch (err: any) {
      if (err.status !== 400) throw err;
    }

    await new Promise((r) => setTimeout(r, 1000));
  }

  throw new Error("Avatar processing timed out");
}

Update username. The same call sets username and removeAvatar.

If you prefer not to confirm explicitly, poll Get current user until avatarUrl appears:

async function waitForAvatar(maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    const profile = await client.getCurrentUser();
    if (profile.avatarUrl) {
      return profile.avatarUrl;
    }
    await new Promise((r) => setTimeout(r, 1000));
  }
  throw new Error("Avatar processing timed out");
}

const avatarUrl = await waitForAvatar();

Constraints

ConstraintValue
Max file size5 MB
Allowed typesJPEG, PNG, GIF, WebP
Processing timeUsually 1-3 seconds
CDN variants44px, 48px, 60px, original, raw

Removing an Avatar

Same Update username call, with removeAvatar: true.

SDK
await client.updateUser({ removeAvatar: true });

Error Handling

try {
  const { uploadUrl } = await client.createAvatarUploadUrl(file.type);

  const uploadRes = await fetch(uploadUrl, {
    method: "PUT",
    headers: { "Content-Type": file.type },
    body: file,
  });

  if (!uploadRes.ok) {
    throw new Error(`Upload failed: ${uploadRes.status}`);
  }

  const avatarUrl = await confirmAvatar();
  console.log("Avatar ready:", avatarUrl);
} catch (err: any) {
  if (err.status === 415) {
    console.error("Unsupported file type");
  } else {
    console.error("Avatar upload failed:", err);
  }
}

On this page