diff --git a/web/avatars/avatar-hooks.react.js b/web/avatars/avatar-hooks.react.js
--- a/web/avatars/avatar-hooks.react.js
+++ b/web/avatars/avatar-hooks.react.js
@@ -2,16 +2,26 @@
 
 import * as React from 'react';
 
-import { uploadMultimedia } from 'lib/actions/upload-actions.js';
-import type { UploadMultimediaResult } from 'lib/types/media-types.js';
+import {
+  uploadMultimedia,
+  useBlobServiceUpload,
+} from 'lib/actions/upload-actions.js';
+import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js';
 import { useServerCall } from 'lib/utils/action-utils.js';
+import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';
 
+import { encryptFile } from '../media/encryption-utils.js';
+import { generateThumbHash } from '../media/image-utils.js';
 import { validateFile } from '../media/media-utils.js';
 
-function useUploadAvatarMedia(): File => Promise<UploadMultimediaResult> {
+// TODO: flip the switch
+const useBlobServiceUploads = false;
+
+function useUploadAvatarMedia(): File => Promise<UpdateUserAvatarRequest> {
   const callUploadMultimedia = useServerCall(uploadMultimedia);
+  const callBlobServiceUpload = useBlobServiceUpload();
   const uploadAvatarMedia = React.useCallback(
-    async (file: File): Promise<UploadMultimediaResult> => {
+    async file => {
       const validatedFile = await validateFile(file);
       const { result } = validatedFile;
       if (!result.success) {
@@ -22,9 +32,49 @@
         ...dimensions,
         loop: false,
       };
-      return await callUploadMultimedia(fixedFile, uploadExtras);
+      if (!useBlobServiceUploads) {
+        const { id } = await callUploadMultimedia(fixedFile, uploadExtras);
+        return { type: 'image', uploadID: id };
+      }
+
+      const encryptionResponse = await encryptFile(fixedFile);
+      const { result: encryptionResult } = encryptionResponse;
+      if (!encryptionResult.success) {
+        throw new Error('Avatar media encryption failed.');
+      }
+      const {
+        file: encryptedFile,
+        sha256Hash: blobHash,
+        encryptionKey,
+      } = encryptionResult;
+
+      const { result: thumbHashResult } = await generateThumbHash(
+        fixedFile,
+        encryptionKey,
+      );
+      const thumbHash = thumbHashResult.success
+        ? thumbHashResult.thumbHash
+        : null;
+
+      const { id } = await callBlobServiceUpload({
+        uploadInput: {
+          blobInput: {
+            type: 'file',
+            file: encryptedFile,
+          },
+          blobHash,
+          encryptionKey,
+          dimensions,
+          loop: false,
+          thumbHash,
+        },
+        keyserverOrThreadID: ashoatKeyserverID,
+        callbacks: {},
+      });
+
+      return { type: 'encrypted_image', uploadID: id };
     },
-    [callUploadMultimedia],
+    [callBlobServiceUpload, callUploadMultimedia],
   );
   return uploadAvatarMedia;
 }
diff --git a/web/avatars/edit-thread-avatar-menu.react.js b/web/avatars/edit-thread-avatar-menu.react.js
--- a/web/avatars/edit-thread-avatar-menu.react.js
+++ b/web/avatars/edit-thread-avatar-menu.react.js
@@ -59,10 +59,7 @@
   const onImageSelected = React.useCallback(
     async event => {
       const uploadResult = await uploadAvatarMedia(event.target.files[0]);
-      baseSetThreadAvatar(threadInfo.id, {
-        type: 'image',
-        uploadID: uploadResult.id,
-      });
+      baseSetThreadAvatar(threadInfo.id, uploadResult);
     },
     [baseSetThreadAvatar, threadInfo.id, uploadAvatarMedia],
   );
diff --git a/web/avatars/edit-user-avatar-menu.react.js b/web/avatars/edit-user-avatar-menu.react.js
--- a/web/avatars/edit-user-avatar-menu.react.js
+++ b/web/avatars/edit-user-avatar-menu.react.js
@@ -71,7 +71,7 @@
   const onImageSelected = React.useCallback(
     async event => {
       const uploadResult = await uploadAvatarMedia(event.target.files[0]);
-      baseSetUserAvatar({ type: 'image', uploadID: uploadResult.id });
+      baseSetUserAvatar(uploadResult);
     },
     [baseSetUserAvatar, uploadAvatarMedia],
   );