diff --git a/ts/components/CompositionArea.stories.tsx b/ts/components/CompositionArea.stories.tsx
index 9ab84711c73..943fab19ef3 100644
--- a/ts/components/CompositionArea.stories.tsx
+++ b/ts/components/CompositionArea.stories.tsx
@@ -60,6 +60,8 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
   quotedMessageProps: overrideProps.quotedMessageProps,
   onClickQuotedMessage: action('onClickQuotedMessage'),
   setQuotedMessage: action('setQuotedMessage'),
+  // MediaEditor
+  imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
   // MediaQualitySelector
   onSelectMediaQuality: action('onSelectMediaQuality'),
   shouldSendHighQualityAttachments: Boolean(
diff --git a/ts/components/CompositionArea.tsx b/ts/components/CompositionArea.tsx
index ed4b0efec16..4b11a8bafb9 100644
--- a/ts/components/CompositionArea.tsx
+++ b/ts/components/CompositionArea.tsx
@@ -13,6 +13,7 @@ import type {
 import type { ErrorDialogAudioRecorderType } from '../state/ducks/audioRecorder';
 import { RecordingState } from '../state/ducks/audioRecorder';
 import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachmentsProcessing';
+import type { imageToBlurHash } from '../util/imageToBlurHash';
 import { Spinner } from './Spinner';
 import type {
   Props as EmojiButtonProps,
@@ -56,7 +57,6 @@ import {
   useKeyboardShortcuts,
 } from '../hooks/useKeyboardShortcuts';
 import { MediaEditor } from './MediaEditor';
-import { IMAGE_PNG } from '../types/MIME';
 import { isImageTypeSupported } from '../util/GoogleChrome';
 import * as KeyboardLayout from '../services/keyboardLayout';
 
@@ -98,6 +98,7 @@ export type OwnProps = Readonly<{
   groupAdmins: Array<ConversationType>;
   groupVersion?: 1 | 2;
   i18n: LocalizerType;
+  imageToBlurHash: typeof imageToBlurHash;
   isFetchingUUID?: boolean;
   isGroupV1AndDisabled?: boolean;
   isMissingMandatoryProfileSharing?: boolean;
@@ -174,6 +175,7 @@ export const CompositionArea = ({
   conversationId,
   i18n,
   onSendMessage,
+  imageToBlurHash,
   processAttachments,
   removeAttachment,
   theme,
@@ -594,12 +596,14 @@ export const CompositionArea = ({
         <MediaEditor
           i18n={i18n}
           imageSrc={attachmentToEdit.url}
+          imageToBlurHash={imageToBlurHash}
           isSending={false}
           onClose={() => setAttachmentToEdit(undefined)}
-          onDone={data => {
+          onDone={({ data, contentType, blurHash }) => {
             const newAttachment = {
               ...attachmentToEdit,
-              contentType: IMAGE_PNG,
+              contentType,
+              blurHash,
               data,
               size: data.byteLength,
             };
diff --git a/ts/components/MediaEditor.stories.tsx b/ts/components/MediaEditor.stories.tsx
index f155d4beedb..455bc0dc438 100644
--- a/ts/components/MediaEditor.stories.tsx
+++ b/ts/components/MediaEditor.stories.tsx
@@ -28,6 +28,7 @@ const getDefaultProps = (): PropsType => ({
   onClose: action('onClose'),
   onDone: action('onDone'),
   isSending: false,
+  imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
 
   // StickerButtonProps
   installedPacks,
diff --git a/ts/components/MediaEditor.tsx b/ts/components/MediaEditor.tsx
index edd65e2924f..f39fd6c87bb 100644
--- a/ts/components/MediaEditor.tsx
+++ b/ts/components/MediaEditor.tsx
@@ -10,6 +10,8 @@ import { get, has, noop } from 'lodash';
 
 import type { LocalizerType } from '../types/Util';
 import { ThemeType } from '../types/Util';
+import type { MIMEType } from '../types/MIME';
+import { IMAGE_PNG } from '../types/MIME';
 import type { Props as StickerButtonProps } from './stickers/StickerButton';
 import type { ImageStateType } from '../mediaEditor/ImageStateType';
 
@@ -20,6 +22,7 @@ import { Slider } from './Slider';
 import { StickerButton } from './stickers/StickerButton';
 import { Theme } from '../util/theme';
 import { canvasToBytes } from '../util/canvasToBytes';
+import type { imageToBlurHash } from '../util/imageToBlurHash';
 import { useFabricHistory } from '../mediaEditor/useFabricHistory';
 import { usePortal } from '../hooks/usePortal';
 import { useUniqueId } from '../hooks/useUniqueId';
@@ -41,13 +44,21 @@ import { AddNewLines } from './conversation/AddNewLines';
 import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
 import { Spinner } from './Spinner';
 
+export type MediaEditorResultType = Readonly<{
+  data: Uint8Array;
+  contentType: MIMEType;
+  blurHash: string;
+  caption?: string;
+}>;
+
 export type PropsType = {
   doneButtonLabel?: string;
   i18n: LocalizerType;
   imageSrc: string;
   isSending: boolean;
+  imageToBlurHash: typeof imageToBlurHash;
   onClose: () => unknown;
-  onDone: (data: Uint8Array, caption?: string | undefined) => unknown;
+  onDone: (result: MediaEditorResultType) => unknown;
 } & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
   (
     | {
@@ -1115,6 +1126,7 @@ export const MediaEditor = ({
               setIsSaving(true);
 
               let data: Uint8Array;
+              let blurHash: string;
               try {
                 const renderFabricCanvas = await cloneFabricCanvas(
                   fabricCanvas
@@ -1151,6 +1163,12 @@ export const MediaEditor = ({
                 const renderedCanvas = renderFabricCanvas.toCanvasElement();
 
                 data = await canvasToBytes(renderedCanvas);
+
+                const blob = new Blob([data], {
+                  type: IMAGE_PNG,
+                });
+
+                blurHash = await props.imageToBlurHash(blob);
               } catch (err) {
                 onTryClose();
                 throw err;
@@ -1158,7 +1176,12 @@ export const MediaEditor = ({
                 setIsSaving(false);
               }
 
-              onDone(data, caption !== '' ? caption : undefined);
+              onDone({
+                contentType: IMAGE_PNG,
+                data,
+                caption: caption !== '' ? caption : undefined,
+                blurHash,
+              });
             }}
             theme={Theme.Dark}
             variant={ButtonVariant.Primary}
diff --git a/ts/components/StoryCreator.stories.tsx b/ts/components/StoryCreator.stories.tsx
index 53cf3953e0c..b89be2728cc 100644
--- a/ts/components/StoryCreator.stories.tsx
+++ b/ts/components/StoryCreator.stories.tsx
@@ -37,6 +37,7 @@ export default {
       defaultValue: false,
     },
     i18n: { defaultValue: i18n },
+    imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
     installedPacks: {
       defaultValue: [],
     },
diff --git a/ts/components/StoryCreator.tsx b/ts/components/StoryCreator.tsx
index c25fab59a3e..b90a9ce823b 100644
--- a/ts/components/StoryCreator.tsx
+++ b/ts/components/StoryCreator.tsx
@@ -14,8 +14,9 @@ import type { LocalizerType } from '../types/Util';
 import type { Props as StickerButtonProps } from './stickers/StickerButton';
 import type { PropsType as SendStoryModalPropsType } from './SendStoryModal';
 import type { UUIDStringType } from '../types/UUID';
+import type { imageToBlurHash } from '../util/imageToBlurHash';
 
-import { IMAGE_JPEG, TEXT_ATTACHMENT } from '../types/MIME';
+import { TEXT_ATTACHMENT } from '../types/MIME';
 import { isVideoAttachment } from '../types/Attachment';
 import { SendStoryModal } from './SendStoryModal';
 
@@ -38,6 +39,7 @@ export type PropsType = {
     conversationIds: Array<string>,
     attachment: AttachmentType
   ) => unknown;
+  imageToBlurHash: typeof imageToBlurHash;
   processAttachment: (
     file: File
   ) => Promise<void | InMemoryAttachmentDraftType>;
@@ -80,6 +82,7 @@ export const StoryCreator = ({
   groupStories,
   hasFirstStoryPostExperience,
   i18n,
+  imageToBlurHash,
   installedPacks,
   isSending,
   linkPreview,
@@ -107,6 +110,7 @@ export const StoryCreator = ({
   const [draftAttachment, setDraftAttachment] = useState<
     AttachmentType | undefined
   >();
+  const [isReadyToSend, setIsReadyToSend] = useState(false);
   const [attachmentUrl, setAttachmentUrl] = useState<string | undefined>();
 
   useEffect(() => {
@@ -123,11 +127,16 @@ export const StoryCreator = ({
         return;
       }
 
+      setDraftAttachment(attachment);
       if (isVideoAttachment(attachment)) {
-        setDraftAttachment(attachment);
+        setAttachmentUrl(undefined);
+        setIsReadyToSend(true);
       } else if (attachment && has(attachment, 'data')) {
         url = URL.createObjectURL(new Blob([get(attachment, 'data')]));
         setAttachmentUrl(url);
+
+        // Needs editing in MediaEditor
+        setIsReadyToSend(false);
       }
     }
 
@@ -142,12 +151,17 @@ export const StoryCreator = ({
   }, [file, processAttachment]);
 
   useEffect(() => {
-    sendStoryModalOpenStateChanged(Boolean(draftAttachment));
+    if (draftAttachment === undefined) {
+      sendStoryModalOpenStateChanged(false);
+      setIsReadyToSend(false);
+    } else {
+      sendStoryModalOpenStateChanged(true);
+    }
   }, [draftAttachment, sendStoryModalOpenStateChanged]);
 
   return (
     <>
-      {draftAttachment && (
+      {draftAttachment && isReadyToSend && (
         <SendStoryModal
           draftAttachment={draftAttachment}
           candidateConversations={candidateConversations}
@@ -182,7 +196,7 @@ export const StoryCreator = ({
           toggleSignalConnectionsModal={toggleSignalConnectionsModal}
         />
       )}
-      {attachmentUrl && (
+      {draftAttachment && !isReadyToSend && attachmentUrl && (
         <MediaEditor
           doneButtonLabel={i18n('next2')}
           i18n={i18n}
@@ -192,13 +206,17 @@ export const StoryCreator = ({
           onClose={onClose}
           supportsCaption
           renderCompositionTextArea={renderCompositionTextArea}
-          onDone={(data, caption) => {
+          imageToBlurHash={imageToBlurHash}
+          onDone={({ contentType, data, blurHash, caption }) => {
             setDraftAttachment({
-              contentType: IMAGE_JPEG,
+              ...draftAttachment,
+              contentType,
               data,
               size: data.byteLength,
+              blurHash,
               caption,
             });
+            setIsReadyToSend(true);
           }}
           recentStickers={recentStickers}
         />
@@ -216,6 +234,7 @@ export const StoryCreator = ({
               textAttachment,
               size: textAttachment.text?.length || 0,
             });
+            setIsReadyToSend(true);
           }}
         />
       )}
diff --git a/ts/state/smart/CompositionArea.tsx b/ts/state/smart/CompositionArea.tsx
index bb3ca43b597..abf0c41afa1 100644
--- a/ts/state/smart/CompositionArea.tsx
+++ b/ts/state/smart/CompositionArea.tsx
@@ -9,6 +9,7 @@ import { CompositionArea } from '../../components/CompositionArea';
 import type { StateType } from '../reducer';
 import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
 import { dropNull } from '../../util/dropNull';
+import { imageToBlurHash } from '../../util/imageToBlurHash';
 
 import { getPreferredBadgeSelector } from '../selectors/badges';
 import { selectRecentEmojis } from '../selectors/emojis';
@@ -89,6 +90,8 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
     recordingState: state.audioRecorder.recordingState,
     // AttachmentsList
     draftAttachments,
+    // MediaEditor
+    imageToBlurHash,
     // MediaQualitySelector
     shouldSendHighQualityAttachments,
     // StagedLinkPreview
diff --git a/ts/state/smart/StoryCreator.tsx b/ts/state/smart/StoryCreator.tsx
index 4dfd4d5bf62..34bced922a1 100644
--- a/ts/state/smart/StoryCreator.tsx
+++ b/ts/state/smart/StoryCreator.tsx
@@ -26,6 +26,7 @@ import { getHasSetMyStoriesPrivacy } from '../selectors/items';
 import { getLinkPreview } from '../selectors/linkPreviews';
 import { getPreferredBadgeSelector } from '../selectors/badges';
 import { processAttachment } from '../../util/processAttachment';
+import { imageToBlurHash } from '../../util/imageToBlurHash';
 import { useConversationsActions } from '../ducks/conversations';
 import { useGlobalModalActions } from '../ducks/globalModals';
 import { useLinkPreviewActions } from '../ducks/linkPreviews';
@@ -91,6 +92,7 @@ export function SmartStoryCreator(): JSX.Element | null {
       groupStories={groupStories}
       hasFirstStoryPostExperience={!hasSetMyStoriesPrivacy}
       i18n={i18n}
+      imageToBlurHash={imageToBlurHash}
       installedPacks={installedPacks}
       isSending={isSending}
       linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)}