diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index d7d495f20f05..7929d2260c86 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -253,6 +253,8 @@ const CONST = {
 
         // Allowed extensions for receipts
         ALLOWED_RECEIPT_EXTENSIONS: ['heif', 'heic', 'jpg', 'jpeg', 'gif', 'png', 'pdf', 'htm', 'html', 'text', 'rtf', 'doc', 'tif', 'tiff', 'msword', 'zip', 'xml', 'message'],
+
+        MAX_FILE_LIMIT: 30,
     },
 
     // Allowed extensions for spreadsheets import
@@ -1879,6 +1881,19 @@ const CONST = {
         // Video MimeTypes allowed by iOS photos app.
         VIDEO: /\.(mov|mp4)$/,
     },
+
+    FILE_VALIDATION_ERRORS: {
+        WRONG_FILE_TYPE: 'wrongFileType',
+        WRONG_FILE_TYPE_MULTIPLE: 'wrongFileTypeMultiple',
+        FILE_TOO_LARGE: 'fileTooLarge',
+        FILE_TOO_LARGE_MULTIPLE: 'fileTooLargeMultiple',
+        FILE_TOO_SMALL: 'fileTooSmall',
+        FILE_CORRUPTED: 'fileCorrupted',
+        FOLDER_NOT_ALLOWED: 'folderNotAllowed',
+        MAX_FILE_LIMIT_EXCEEDED: 'fileLimitExceeded',
+        PROTECTED_FILE: 'protectedFile',
+    },
+
     IOS_CAMERA_ROLL_ACCESS_ERROR: 'Access to photo library was denied',
     ADD_PAYMENT_MENU_POSITION_Y: 226,
     ADD_PAYMENT_MENU_POSITION_X: 356,
diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx
index edcd5e698d67..0560e14ec9c7 100644
--- a/src/components/AttachmentPicker/index.native.tsx
+++ b/src/components/AttachmentPicker/index.native.tsx
@@ -153,45 +153,68 @@ function AttachmentPicker({
                         return reject(new Error(`Error during attachment selection: ${response.errorMessage}`));
                     }
 
-                    const targetAsset = response.assets?.[0];
-                    const targetAssetUri = targetAsset?.uri;
-
-                    if (!targetAssetUri) {
+                    const assets = response.assets;
+                    if (!assets || assets.length === 0) {
                         return resolve();
                     }
 
-                    if (targetAsset?.type?.startsWith('image')) {
-                        verifyFileFormat({fileUri: targetAssetUri, formatSignatures: CONST.HEIC_SIGNATURES})
-                            .then((isHEIC) => {
-                                // react-native-image-picker incorrectly changes file extension without transcoding the HEIC file, so we are doing it manually if we detect HEIC signature
-                                if (isHEIC && targetAssetUri) {
-                                    ImageManipulator.manipulate(targetAssetUri)
-                                        .renderAsync()
-                                        .then((manipulatedImage) => manipulatedImage.saveAsync({format: SaveFormat.JPEG}))
-                                        .then((manipulationResult) => {
-                                            const uri = manipulationResult.uri;
-                                            const convertedAsset = {
-                                                uri,
-                                                name: uri
-                                                    .substring(uri.lastIndexOf('/') + 1)
-                                                    .split('?')
-                                                    .at(0),
-                                                type: 'image/jpeg',
-                                                width: manipulationResult.width,
-                                                height: manipulationResult.height,
-                                            };
-
-                                            return resolve([convertedAsset]);
-                                        })
-                                        .catch((err) => reject(err));
-                                } else {
-                                    return resolve(response.assets);
-                                }
-                            })
-                            .catch((err) => reject(err));
-                    } else {
-                        return resolve(response.assets);
-                    }
+                    const processedAssets: Asset[] = [];
+                    let processedCount = 0;
+
+                    const checkAllProcessed = () => {
+                        processedCount++;
+                        if (processedCount === assets.length) {
+                            resolve(processedAssets.length > 0 ? processedAssets : undefined);
+                        }
+                    };
+
+                    assets.forEach((asset) => {
+                        if (!asset.uri) {
+                            checkAllProcessed();
+                            return;
+                        }
+
+                        if (asset.type?.startsWith('image')) {
+                            verifyFileFormat({fileUri: asset.uri, formatSignatures: CONST.HEIC_SIGNATURES})
+                                .then((isHEIC) => {
+                                    // react-native-image-picker incorrectly changes file extension without transcoding the HEIC file, so we are doing it manually if we detect HEIC signature
+                                    if (isHEIC && asset.uri) {
+                                        ImageManipulator.manipulate(asset.uri)
+                                            .renderAsync()
+                                            .then((manipulatedImage) => manipulatedImage.saveAsync({format: SaveFormat.JPEG}))
+                                            .then((manipulationResult) => {
+                                                const uri = manipulationResult.uri;
+                                                const convertedAsset = {
+                                                    uri,
+                                                    name: uri
+                                                        .substring(uri.lastIndexOf('/') + 1)
+                                                        .split('?')
+                                                        .at(0),
+                                                    type: 'image/jpeg',
+                                                    width: manipulationResult.width,
+                                                    height: manipulationResult.height,
+                                                };
+                                                processedAssets.push(convertedAsset);
+                                                checkAllProcessed();
+                                            })
+                                            .catch((error: Error) => {
+                                                showGeneralAlert(error.message ?? 'An unknown error occurred');
+                                                checkAllProcessed();
+                                            });
+                                    } else {
+                                        processedAssets.push(asset);
+                                        checkAllProcessed();
+                                    }
+                                })
+                                .catch((error: Error) => {
+                                    showGeneralAlert(error.message ?? 'An unknown error occurred');
+                                    checkAllProcessed();
+                                });
+                        } else {
+                            processedAssets.push(asset);
+                            checkAllProcessed();
+                        }
+                    });
                 });
             }),
         [fileLimit, showGeneralAlert, type],
@@ -287,42 +310,20 @@ function AttachmentPicker({
         setIsVisible(false);
     };
 
-    const validateAndCompleteAttachmentSelection = useCallback(
-        (fileData: FileResponse) => {
-            // Check if the file dimensions indicate corruption
-            // The width/height for a corrupted file is -1 on android native and 0 on ios native
-            // We must check only numeric values because the width/height can be undefined for non-image files
-            if ((typeof fileData.width === 'number' && fileData.width <= 0) || (typeof fileData.height === 'number' && fileData.height <= 0)) {
-                showImageCorruptionAlert();
-                return Promise.resolve();
-            }
-            return getDataForUpload(fileData)
-                .then((result) => {
-                    completeAttachmentSelection.current([result]);
-                })
-                .catch((error: Error) => {
-                    showGeneralAlert(error.message);
-                    throw error;
-                });
-        },
-        [showGeneralAlert, showImageCorruptionAlert],
-    );
-
     /**
      * Handles the image/document picker result and
      * sends the selected attachment to the caller (parent component)
      */
     const pickAttachment = useCallback(
-        (attachments: Asset[] | LocalCopy[] | void = []): Promise<void[]> | undefined => {
+        (attachments: Asset[] | LocalCopy[] | void = []): Promise<void> | undefined => {
             if (!attachments || attachments.length === 0) {
                 onCanceled.current();
-                return Promise.resolve([]);
+                return Promise.resolve();
             }
 
             const filesToProcess = attachments.map((fileData) => {
                 if (!fileData) {
-                    onCanceled.current();
-                    return Promise.resolve();
+                    return Promise.resolve(null);
                 }
 
                 /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
@@ -345,20 +346,10 @@ function AttachmentPicker({
                             fileDataObject.height = height;
                             return fileDataObject;
                         })
-                        .then((file) => {
-                            return getDataForUpload(file)
-                                .then((result) => completeAttachmentSelection.current([result]))
-                                .catch((error) => {
-                                    if (error instanceof Error) {
-                                        showGeneralAlert(error.message);
-                                    } else {
-                                        showGeneralAlert('An unknown error occurred');
-                                    }
-                                    throw error;
-                                });
-                        })
+                        .then((file) => getDataForUpload(file))
                         .catch(() => {
                             showImageCorruptionAlert();
+                            return null;
                         });
                 }
 
@@ -370,21 +361,41 @@ function AttachmentPicker({
 
                             if (fileDataObject.width <= 0 || fileDataObject.height <= 0) {
                                 showImageCorruptionAlert();
-                                return Promise.resolve(); // Skip processing this corrupted file
+                                return null;
                             }
 
-                            return validateAndCompleteAttachmentSelection(fileDataObject);
+                            return getDataForUpload(fileDataObject);
                         })
                         .catch(() => {
                             showImageCorruptionAlert();
+                            return null;
                         });
                 }
-                return validateAndCompleteAttachmentSelection(fileDataObject);
+
+                return getDataForUpload(fileDataObject).catch((error: Error) => {
+                    showGeneralAlert(error.message);
+                    return null;
+                });
             });
 
-            return Promise.all(filesToProcess);
+            return Promise.all(filesToProcess)
+                .then((results) => {
+                    const validResults = results.filter((result): result is FileObject => result !== null);
+                    if (validResults.length > 0) {
+                        completeAttachmentSelection.current(validResults);
+                    } else {
+                        onCanceled.current();
+                    }
+                })
+                .catch((error) => {
+                    if (error instanceof Error) {
+                        showGeneralAlert(error.message);
+                    } else {
+                        showGeneralAlert('An unknown error occurred');
+                    }
+                });
         },
-        [shouldValidateImage, validateAndCompleteAttachmentSelection, showGeneralAlert, showImageCorruptionAlert],
+        [shouldValidateImage, showGeneralAlert, showImageCorruptionAlert],
     );
 
     /**
diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx
index 1b11bdcf047e..6b11c82e60b3 100644
--- a/src/components/AttachmentPicker/index.tsx
+++ b/src/components/AttachmentPicker/index.tsx
@@ -60,9 +60,15 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE, a
                         return;
                     }
 
-                    const file = e.target.files[0];
-
-                    if (file) {
+                    if (allowMultiple && e.target.files.length > 1) {
+                        const files = Array.from(e.target.files).map((currentFile) => {
+                            // eslint-disable-next-line no-param-reassign
+                            currentFile.uri = URL.createObjectURL(currentFile);
+                            return currentFile as FileObject;
+                        });
+                        onPicked.current(files);
+                    } else if (e.target.files[0]) {
+                        const file = e.target.files[0];
                         file.uri = URL.createObjectURL(file);
                         onPicked.current([file]);
                     }
diff --git a/src/hooks/useFileValidation.ts b/src/hooks/useFileValidation.ts
deleted file mode 100644
index 88621ef8dbb7..000000000000
--- a/src/hooks/useFileValidation.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import {Str} from 'expensify-common';
-import {useState} from 'react';
-import {resizeImageIfNeeded, validateReceipt} from '@libs/fileDownload/FileUtils';
-import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
-import CONST from '@src/CONST';
-import type {TranslationPaths} from '@src/languages/types';
-
-function useFileValidation() {
-    const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
-    const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths>();
-    const [attachmentInvalidReason, setAttachmentValidReason] = useState<TranslationPaths>();
-    const [pdfFile, setPdfFile] = useState<null | FileObject>(null);
-    const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
-
-    /**
-     * Sets the upload receipt error modal content when an invalid receipt is uploaded
-     */
-    const setUploadReceiptError = (isInvalid: boolean, title: TranslationPaths, reason: TranslationPaths) => {
-        setIsAttachmentInvalid(isInvalid);
-        setAttachmentInvalidReasonTitle(title);
-        setAttachmentValidReason(reason);
-        setPdfFile(null);
-    };
-
-    const validateAndResizeFile = (originalFile: FileObject, setReceiptAndNavigate: (file: FileObject) => void, isPdfValidated?: boolean) => {
-        validateReceipt(originalFile, setUploadReceiptError).then((isFileValid) => {
-            if (!isFileValid) {
-                return;
-            }
-
-            // If we have a pdf file and if it is not validated then set the pdf file for validation and return
-            if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
-                setPdfFile(originalFile);
-                return;
-            }
-
-            // With the image size > 24MB, we use manipulateAsync to resize the image.
-            // It takes a long time so we should display a loading indicator while the resize image progresses.
-            if (Str.isImage(originalFile.name ?? '') && (originalFile?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
-                setIsLoadingReceipt(true);
-            }
-            resizeImageIfNeeded(originalFile).then((resizedFile) => {
-                setIsLoadingReceipt(false);
-                setReceiptAndNavigate(resizedFile);
-            });
-        });
-    };
-
-    return {
-        validateAndResizeFile,
-        isAttachmentInvalid,
-        setIsAttachmentInvalid,
-        attachmentInvalidReason,
-        attachmentInvalidReasonTitle,
-        setUploadReceiptError,
-        pdfFile,
-        setPdfFile,
-        isLoadingReceipt,
-    };
-}
-
-export default useFileValidation;
diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx
new file mode 100644
index 000000000000..a8f7d335559c
--- /dev/null
+++ b/src/hooks/useFilesValidation.tsx
@@ -0,0 +1,298 @@
+import {Str} from 'expensify-common';
+import React, {useCallback, useRef, useState} from 'react';
+import {InteractionManager} from 'react-native';
+import type {ValueOf} from 'type-fest';
+import type {FileObject} from '@components/AttachmentModal';
+import ConfirmModal from '@components/ConfirmModal';
+import {useFullScreenLoader} from '@components/FullScreenLoaderContext';
+import PDFThumbnail from '@components/PDFThumbnail';
+import Text from '@components/Text';
+import TextLink from '@components/TextLink';
+import {getFileValidationErrorText, isHeicOrHeifImage, resizeImageIfNeeded, splitExtensionFromFileName, validateAttachment, validateImageForCorruption} from '@libs/fileDownload/FileUtils';
+import convertHeicImage from '@libs/fileDownload/heicConverter';
+import CONST from '@src/CONST';
+import useLocalize from './useLocalize';
+import useThemeStyles from './useThemeStyles';
+
+type ErrorObject = {
+    error: ValueOf<typeof CONST.FILE_VALIDATION_ERRORS>;
+    fileExtension?: string;
+};
+
+function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => void) {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+    const [isErrorModalVisible, setIsErrorModalVisible] = useState(false);
+    const [fileError, setFileError] = useState<ValueOf<typeof CONST.FILE_VALIDATION_ERRORS> | null>(null);
+    const [pdfFilesToRender, setPdfFilesToRender] = useState<FileObject[]>([]);
+    const [validFilesToUpload, setValidFilesToUpload] = useState([] as FileObject[]);
+    const [isValidatingMultipleFiles, setIsValidatingMultipleFiles] = useState(false);
+    const [invalidFileExtension, setInvalidFileExtension] = useState('');
+    const [errorQueue, setErrorQueue] = useState<ErrorObject[]>([]);
+    const [currentErrorIndex, setCurrentErrorIndex] = useState(0);
+    const {setIsLoaderVisible} = useFullScreenLoader();
+
+    const validatedPDFs = useRef<FileObject[]>([]);
+    const validFiles = useRef<FileObject[]>([]);
+    const filesToValidate = useRef<FileObject[]>([]);
+    const collectedErrors = useRef<ErrorObject[]>([]);
+
+    const resetValidationState = useCallback(() => {
+        setIsErrorModalVisible(false);
+        setPdfFilesToRender([]);
+        setIsLoaderVisible(false);
+        setIsValidatingMultipleFiles(false);
+        setFileError(null);
+        setValidFilesToUpload([]);
+        setInvalidFileExtension('');
+        setErrorQueue([]);
+        setCurrentErrorIndex(0);
+        validatedPDFs.current = [];
+        validFiles.current = [];
+        filesToValidate.current = [];
+        collectedErrors.current = [];
+    }, [setIsLoaderVisible]);
+
+    const hideModalAndReset = useCallback(() => {
+        setIsErrorModalVisible(false);
+        InteractionManager.runAfterInteractions(() => {
+            resetValidationState();
+        });
+    }, [resetValidationState]);
+
+    const setErrorAndOpenModal = (error: ValueOf<typeof CONST.FILE_VALIDATION_ERRORS>) => {
+        setFileError(error);
+        setIsErrorModalVisible(true);
+    };
+
+    const isValidFile = (originalFile: FileObject, isCheckingMultipleFiles?: boolean) => {
+        return validateImageForCorruption(originalFile)
+            .then(() => {
+                const error = validateAttachment(originalFile, isCheckingMultipleFiles, true);
+                if (error) {
+                    const errorData = {
+                        error,
+                        fileExtension: error === CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE ? splitExtensionFromFileName(originalFile.name ?? '').fileExtension : undefined,
+                    };
+                    collectedErrors.current.push(errorData);
+                    return false;
+                }
+                return true;
+            })
+            .catch(() => {
+                collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED});
+                return false;
+            });
+    };
+
+    const convertHeicImageToJpegPromise = (file: FileObject): Promise<FileObject> => {
+        return new Promise((resolve, reject) => {
+            convertHeicImage(file, {
+                onSuccess: (convertedFile) => resolve(convertedFile),
+                onError: (nonConvertedFile) => {
+                    reject(nonConvertedFile);
+                },
+            });
+        });
+    };
+
+    const checkIfAllValidatedAndProceed = useCallback(() => {
+        if (!validatedPDFs.current || !validFiles.current) {
+            return;
+        }
+
+        if (validatedPDFs.current.length !== pdfFilesToRender.length) {
+            return;
+        }
+
+        if (validFiles.current.length > 0) {
+            setValidFilesToUpload(validFiles.current);
+        }
+
+        if (collectedErrors.current.length > 0) {
+            const uniqueErrors = Array.from(new Set(collectedErrors.current.map((error) => JSON.stringify(error)))).map((errorStr) => JSON.parse(errorStr) as ErrorObject);
+            setErrorQueue(uniqueErrors);
+            setCurrentErrorIndex(0);
+            const firstError = uniqueErrors.at(0);
+            if (firstError) {
+                setFileError(firstError.error);
+                if (firstError.fileExtension) {
+                    setInvalidFileExtension(firstError.fileExtension);
+                }
+                setIsErrorModalVisible(true);
+            }
+        } else if (validFiles.current.length > 0) {
+            proceedWithFilesAction(validFiles.current);
+            resetValidationState();
+        }
+    }, [pdfFilesToRender.length, proceedWithFilesAction, resetValidationState]);
+
+    const validateAndResizeFiles = (files: FileObject[]) => {
+        // Reset collected errors for new validation
+        collectedErrors.current = [];
+
+        Promise.all(files.map((file) => isValidFile(file, files.length > 1).then((isValid) => (isValid ? file : null))))
+            .then((validationResults) => {
+                const filteredResults = validationResults.filter((result): result is FileObject => result !== null);
+                const pdfsToLoad = filteredResults.filter((file) => Str.isPDF(file.name ?? ''));
+                const otherFiles = filteredResults.filter((file) => !Str.isPDF(file.name ?? ''));
+
+                // Check if we need to convert images
+                if (otherFiles.some((file) => isHeicOrHeifImage(file))) {
+                    setIsLoaderVisible(true);
+
+                    return Promise.all(otherFiles.map((file) => convertHeicImageToJpegPromise(file))).then((convertedImages) => {
+                        // Check if we need to resize images
+                        if (convertedImages.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) {
+                            return Promise.all(convertedImages.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => {
+                                setIsLoaderVisible(false);
+                                return Promise.resolve({processedFiles, pdfsToLoad});
+                            });
+                        }
+
+                        // No resizing needed, just return the converted images
+                        setIsLoaderVisible(false);
+                        return Promise.resolve({processedFiles: convertedImages, pdfsToLoad});
+                    });
+                }
+
+                // No conversion needed, but check if we need to resize images
+                if (otherFiles.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) {
+                    setIsLoaderVisible(true);
+                    return Promise.all(otherFiles.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => {
+                        setIsLoaderVisible(false);
+                        return Promise.resolve({processedFiles, pdfsToLoad});
+                    });
+                }
+
+                // No conversion or resizing needed, just return the valid images
+                return Promise.resolve({processedFiles: otherFiles, pdfsToLoad});
+            })
+            .then(({processedFiles, pdfsToLoad}) => {
+                if (pdfsToLoad.length) {
+                    validFiles.current = processedFiles;
+                    setPdfFilesToRender(pdfsToLoad);
+                } else {
+                    if (processedFiles.length > 0) {
+                        setValidFilesToUpload(processedFiles);
+                    }
+
+                    if (collectedErrors.current.length > 0) {
+                        const uniqueErrors = Array.from(new Set(collectedErrors.current.map((error) => JSON.stringify(error)))).map((errorStr) => JSON.parse(errorStr) as ErrorObject);
+                        setErrorQueue(uniqueErrors);
+                        setCurrentErrorIndex(0);
+                        const firstError = uniqueErrors.at(0);
+                        if (firstError) {
+                            setFileError(firstError.error);
+                            if (firstError.fileExtension) {
+                                setInvalidFileExtension(firstError.fileExtension);
+                            }
+                            setIsErrorModalVisible(true);
+                        }
+                    } else if (processedFiles.length > 0) {
+                        proceedWithFilesAction(processedFiles);
+                        resetValidationState();
+                    }
+                }
+            });
+    };
+
+    const validateFiles = (files: FileObject[]) => {
+        if (files.length > 1) {
+            setIsValidatingMultipleFiles(true);
+        }
+        if (files.length > CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) {
+            filesToValidate.current = files.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT);
+            setErrorAndOpenModal(CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED);
+        } else {
+            validateAndResizeFiles(files);
+        }
+    };
+
+    const onConfirm = () => {
+        if (fileError === CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED) {
+            setIsErrorModalVisible(false);
+            validateAndResizeFiles(filesToValidate.current);
+            return;
+        }
+
+        if (currentErrorIndex < errorQueue.length - 1) {
+            const nextIndex = currentErrorIndex + 1;
+            const nextError = errorQueue.at(nextIndex);
+            if (nextError) {
+                if (validFiles.current.length === 0) {
+                    setIsValidatingMultipleFiles(false);
+                }
+                setCurrentErrorIndex(nextIndex);
+                setFileError(nextError.error);
+                setInvalidFileExtension(nextError.fileExtension ?? '');
+                return;
+            }
+        }
+
+        proceedWithFilesAction(validFilesToUpload);
+        hideModalAndReset();
+    };
+
+    const PDFValidationComponent = pdfFilesToRender.length
+        ? pdfFilesToRender.map((file) => (
+              <PDFThumbnail
+                  key={file.uri}
+                  style={styles.invisiblePDF}
+                  previewSourceURL={file.uri ?? ''}
+                  onLoadSuccess={() => {
+                      validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
+                      validFiles.current = [...(validFiles.current ?? []), file];
+                      checkIfAllValidatedAndProceed();
+                  }}
+                  onPassword={() => {
+                      validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
+                      collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.PROTECTED_FILE});
+                      checkIfAllValidatedAndProceed();
+                  }}
+                  onLoadError={() => {
+                      validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
+                      collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED});
+                      checkIfAllValidatedAndProceed();
+                  }}
+              />
+          ))
+        : undefined;
+
+    const getModalPrompt = useCallback(() => {
+        if (!fileError) {
+            return '';
+        }
+        const prompt = getFileValidationErrorText(fileError, {fileType: invalidFileExtension}).reason;
+        if (fileError === CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE) {
+            return (
+                <Text>
+                    {prompt}
+                    <TextLink href=""> {translate('attachmentPicker.learnMoreAboutSupportedFiles')}</TextLink>
+                </Text>
+            );
+        }
+        return prompt;
+    }, [fileError, invalidFileExtension, translate]);
+
+    const ErrorModal = (
+        <ConfirmModal
+            title={getFileValidationErrorText(fileError, {fileType: invalidFileExtension}).title}
+            onConfirm={onConfirm}
+            onCancel={hideModalAndReset}
+            isVisible={isErrorModalVisible}
+            prompt={getModalPrompt()}
+            confirmText={translate(isValidatingMultipleFiles ? 'common.continue' : 'common.close')}
+            cancelText={translate('common.cancel')}
+            shouldShowCancelButton={isValidatingMultipleFiles}
+        />
+    );
+
+    return {
+        PDFValidationComponent,
+        validateFiles,
+        ErrorModal,
+    };
+}
+
+export default useFilesValidation;
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 279ff2efdf3e..19213fa7f4fd 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'Lange ID',
         bankAccounts: 'Bankkonten',
         chooseFile: 'Datei auswählen',
+        chooseFiles: 'Dateien auswählen',
         dropTitle: 'Lass es los',
         dropMessage: 'Datei hier ablegen',
         ignore: 'Ignore',
@@ -666,6 +668,12 @@ const translations = {
         attachmentImageTooLarge: 'Dieses Bild ist zu groß, um es vor dem Hochladen in der Vorschau anzuzeigen.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Sie können jeweils nur bis zu ${fileLimit} Dateien hochladen.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Dateien überschreiten ${maxUploadSizeInMB} MB. Bitte versuchen Sie es erneut.`,
+        someFilesCantBeUploaded: 'Einige Dateien können nicht hochgeladen werden',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Dateien müssen unter ${maxUploadSizeInMB} MB sein. Größere Dateien werden nicht hochgeladen.`,
+        maxFileLimitExceeded: 'Sie können bis zu 30 Belege gleichzeitig hochladen. Weitere werden nicht hochgeladen.',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType} Dateien werden nicht unterstützt. Nur unterstützte Dateitypen werden hochgeladen.`,
+        learnMoreAboutSupportedFiles: 'Erfahren Sie mehr über unterstützte Formate.',
+        passwordProtected: 'Passwortgeschützte PDFs werden nicht unterstützt. Nur unterstützte Dateien werden hochgeladen.',
     },
     dropzone: {
         addAttachments: 'Anhänge hinzufügen',
@@ -955,9 +963,13 @@ const translations = {
     },
     receipt: {
         upload: 'Beleg hochladen',
+        uploadMultiple: 'Belege hochladen',
         dragReceiptBeforeEmail: 'Ziehen Sie eine Quittung auf diese Seite oder leiten Sie eine Quittung weiter an',
+        dragReceiptsBeforeEmail: 'Ziehen Sie Quittungen auf diese Seite oder leiten Sie Quittungen weiter an',
         dragReceiptAfterEmail: 'oder wählen Sie unten eine Datei zum Hochladen aus.',
+        dragReceiptsAfterEmail: 'oder wählen Sie unten Dateien zum Hochladen aus.',
         chooseReceipt: 'Wählen Sie eine Quittung zum Hochladen aus oder leiten Sie eine Quittung weiter an',
+        chooseReceipts: 'Wählen Sie Quittungen zum Hochladen aus oder leiten Sie Quittungen weiter an ',
         takePhoto: 'Ein Foto machen',
         cameraAccess: 'Der Kamerazugriff ist erforderlich, um Fotos von Belegen zu machen.',
         deniedCameraAccess: 'Kamerazugriff wurde noch nicht gewährt, bitte folgen Sie',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index e2e46cdbb5c0..f172671260dc 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -87,6 +87,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -553,6 +554,7 @@ const translations = {
         longID: 'Long ID',
         bankAccounts: 'Bank accounts',
         chooseFile: 'Choose file',
+        chooseFiles: 'Choose files',
         dropTitle: 'Let it go',
         dropMessage: 'Drop your file here',
         ignore: 'Ignore',
@@ -656,6 +658,12 @@ const translations = {
         attachmentImageTooLarge: 'This image is too large to preview before uploading.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `You can only upload up to ${fileLimit} files at a time.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Files exceeds ${maxUploadSizeInMB} MB. Please try again.`,
+        someFilesCantBeUploaded: "Some files can't be uploaded",
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Files must be under ${maxUploadSizeInMB} MB. Any larger files won't be uploaded.`,
+        maxFileLimitExceeded: "You can upload up to 30 receipts at a time. Any extras won't be uploaded.",
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType} files aren't supported. Only supported file types will be uploaded.`,
+        learnMoreAboutSupportedFiles: 'Learn more about supported formats.',
+        passwordProtected: "Password-protected PDFs aren't supported. Only supported files will be uploaded.",
     },
     dropzone: {
         addAttachments: 'Add attachments',
@@ -944,9 +952,13 @@ const translations = {
     },
     receipt: {
         upload: 'Upload receipt',
+        uploadMultiple: 'Upload receipts',
         dragReceiptBeforeEmail: 'Drag a receipt onto this page, forward a receipt to ',
+        dragReceiptsBeforeEmail: 'Drag receipts onto this page, forward receipts to ',
         dragReceiptAfterEmail: ' or choose a file to upload below.',
+        dragReceiptsAfterEmail: ' or choose files to upload below.',
         chooseReceipt: 'Choose a receipt to upload or forward a receipt to ',
+        chooseReceipts: 'Choose receipts to upload or forward receipts to ',
         takePhoto: 'Take a photo',
         cameraAccess: 'Camera access is required to take pictures of receipts.',
         deniedCameraAccess: "Camera access still hasn't been granted, please follow ",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 1debf565f16b..027a9cc004f4 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -86,6 +86,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -546,6 +547,7 @@ const translations = {
         longID: 'ID largo',
         bankAccounts: 'Cuentas bancarias',
         chooseFile: 'Elegir archivo',
+        chooseFiles: 'Elegir archivos',
         dropTitle: 'Suéltalo',
         dropMessage: 'Suelta tu archivo aquí',
         enabled: 'Habilitado',
@@ -651,6 +653,12 @@ const translations = {
         attachmentImageTooLarge: 'Esta imagen es demasiado grande para obtener una vista previa antes de subirla.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Solamente puedes suber ${fileLimit} archivos a la vez.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `El archivo supera los ${maxUploadSizeInMB} MB. Por favor, vuelve a intentarlo.`,
+        someFilesCantBeUploaded: 'Algunos archivos no se pueden subir',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Los archivos deben ser menores a ${maxUploadSizeInMB} MB. Los archivos más grandes no se subirán.`,
+        maxFileLimitExceeded: 'Puedes subir hasta 30 recibos a la vez. Los extras no se subirán.',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType} archivos no son compatibles. Solo se subirán los archivos compatibles.`,
+        learnMoreAboutSupportedFiles: 'Obtén más información sobre los formatos compatibles.',
+        passwordProtected: 'Los PDFs con contraseña no son compatibles. Solo se subirán los archivos compatibles',
     },
     dropzone: {
         addAttachments: 'Añadir archivos adjuntos',
@@ -939,9 +947,13 @@ const translations = {
     },
     receipt: {
         upload: 'Subir recibo',
+        uploadMultiple: 'Subir recibos',
         dragReceiptBeforeEmail: 'Arrastra un recibo a esta página, reenvíalo a ',
+        dragReceiptsBeforeEmail: 'Arrastra recibos a esta página, reenvíalos a ',
         dragReceiptAfterEmail: ' o elije un archivo para subir a continuación.',
+        dragReceiptsAfterEmail: ' o elije archivos para subir a continuación.',
         chooseReceipt: 'Elige un recibo para subir o reenvía un recibo a ',
+        chooseReceipts: 'Elige recibos para subir o reenvía recibos a ',
         takePhoto: 'Haz una foto',
         cameraAccess: 'Se requiere acceso a la cámara para hacer fotos de los recibos.',
         deniedCameraAccess: 'No se ha concedido el acceso a la cámara, siga ',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 67384733c9c5..371a593c4133 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'ID long',
         bankAccounts: 'Comptes bancaires',
         chooseFile: 'Choisir un fichier',
+        chooseFiles: 'Choisir des fichiers',
         dropTitle: 'Laisse tomber',
         dropMessage: 'Déposez votre fichier ici',
         ignore: 'Ignore',
@@ -666,6 +668,13 @@ const translations = {
         attachmentImageTooLarge: 'Cette image est trop grande pour être prévisualisée avant le téléchargement.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Vous pouvez télécharger jusqu'à ${fileLimit} fichiers à la fois.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Les fichiers dépassent ${maxUploadSizeInMB} MB. Veuillez réessayer.`,
+        someFilesCantBeUploaded: 'Certains fichiers ne peuvent pas être téléchargés',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) =>
+            `Les fichiers doivent faire moins de ${maxUploadSizeInMB} MB. Les fichiers plus volumineux ne seront pas téléchargés.`,
+        maxFileLimitExceeded: "Vous pouvez télécharger jusqu'à 30 reçus à la fois. Les fichiers supplémentaires ne seront pas téléchargés.",
+        unsupportedFileType: ({fileType}: FileTypeParams) => `Les fichiers ${fileType} ne sont pas pris en charge. Seuls les types de fichiers pris en charge seront téléchargés.`,
+        learnMoreAboutSupportedFiles: 'En savoir plus sur les formats pris en charge.',
+        passwordProtected: 'Les PDF protégés par mot de passe ne sont pas pris en charge. Seuls les fichiers pris en charge seront téléchargés.',
     },
     dropzone: {
         addAttachments: 'Ajouter des pièces jointes',
@@ -955,9 +964,13 @@ const translations = {
     },
     receipt: {
         upload: 'Télécharger le reçu',
+        uploadMultiple: 'Télécharger des reçus',
         dragReceiptBeforeEmail: 'Faites glisser un reçu sur cette page, transférez un reçu à',
+        dragReceiptsBeforeEmail: 'Faites glisser des reçus sur cette page, transférez des reçus à',
         dragReceiptAfterEmail: 'ou choisissez un fichier à télécharger ci-dessous.',
+        dragReceiptsAfterEmail: 'ou choisissez des fichiers à télécharger ci-dessous.',
         chooseReceipt: 'Choisissez un reçu à télécharger ou transférez un reçu à',
+        chooseReceipts: 'Choisissez des reçus à télécharger ou transférez des reçus à',
         takePhoto: 'Prendre une photo',
         cameraAccess: "L'accès à la caméra est requis pour prendre des photos des reçus.",
         deniedCameraAccess: "L'accès à la caméra n'a toujours pas été accordé, veuillez suivre",
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 82906b432e45..b573c0147e49 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'ID lungo',
         bankAccounts: 'Conti bancari',
         chooseFile: 'Scegli file',
+        chooseFiles: 'Scegli file',
         dropTitle: 'Lascia andare',
         dropMessage: 'Trascina qui il tuo file',
         ignore: 'Ignore',
@@ -666,6 +668,12 @@ const translations = {
         attachmentImageTooLarge: 'Questa immagine è troppo grande per essere visualizzata in anteprima prima del caricamento.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Puoi caricare solo fino a ${fileLimit} file alla volta.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `I file superano ${maxUploadSizeInMB} MB. Per favore riprova.`,
+        someFilesCantBeUploaded: 'Alcuni file non possono essere caricati',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `I file devono essere inferiori a ${maxUploadSizeInMB} MB. I file più grandi non verranno caricati.`,
+        maxFileLimitExceeded: 'Puoi caricare fino a 30 ricevute alla volta. Quelle in eccesso non verranno caricate.',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `I file ${fileType} non sono supportati. Verranno caricati solo i tipi di file supportati.`,
+        learnMoreAboutSupportedFiles: 'Scopri di più sui formati supportati.',
+        passwordProtected: 'I PDF protetti da password non sono supportati. Verranno caricati solo i file supportati.',
     },
     dropzone: {
         addAttachments: 'Aggiungi allegati',
@@ -951,9 +959,13 @@ const translations = {
     },
     receipt: {
         upload: 'Carica ricevuta',
+        uploadMultiple: 'Carica ricevute',
         dragReceiptBeforeEmail: 'Trascina una ricevuta su questa pagina, inoltra una ricevuta a',
+        dragReceiptsBeforeEmail: 'Trascina ricevute su questa pagina, inoltra ricevute a',
         dragReceiptAfterEmail: 'oppure scegli un file da caricare qui sotto.',
+        dragReceiptsAfterEmail: 'oppure scegli file da caricare qui sotto.',
         chooseReceipt: 'Scegli una ricevuta da caricare o inoltra una ricevuta a',
+        chooseReceipts: 'Scegli ricevute da caricare o inoltra ricevute a',
         takePhoto: 'Scatta una foto',
         cameraAccess: "L'accesso alla fotocamera è necessario per scattare foto delle ricevute.",
         deniedCameraAccess: "L'accesso alla fotocamera non è ancora stato concesso, si prega di seguire",
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 80d4d4b5d8aa..289f3a842f03 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'Long ID',
         bankAccounts: '銀行口座',
         chooseFile: 'ファイルを選択',
+        chooseFiles: 'ファイルを選択',
         dropTitle: 'そのままにしておく',
         dropMessage: 'ここにファイルをドロップしてください',
         ignore: 'Ignore',
@@ -666,6 +668,12 @@ const translations = {
         attachmentImageTooLarge: 'この画像はアップロード前にプレビューするには大きすぎます。',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `一度にアップロードできるファイルは${fileLimit}個までです。`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `ファイルが ${maxUploadSizeInMB} MB を超えています。もう一度お試しください。`,
+        someFilesCantBeUploaded: '一部のファイルはアップロードできません',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `ファイルは${maxUploadSizeInMB}MB未満である必要があります。それより大きいファイルはアップロードされません。`,
+        maxFileLimitExceeded: '一度に最大30枚の領収書をアップロードできます。超過分はアップロードされません。',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType}ファイルはサポートされていません。サポートされているファイルタイプのみがアップロードされます。`,
+        learnMoreAboutSupportedFiles: 'サポートされているフォーマットについて詳しく知る。',
+        passwordProtected: 'パスワード保護されたPDFはサポートされていません。サポートされているファイルのみがアップロードされます。',
     },
     dropzone: {
         addAttachments: '添付ファイルを追加',
@@ -954,9 +962,13 @@ const translations = {
     },
     receipt: {
         upload: '領収書をアップロード',
+        uploadMultiple: '領収書をアップロード',
         dragReceiptBeforeEmail: '領収書をこのページにドラッグするか、領収書を転送する',
+        dragReceiptsBeforeEmail: '領収書をこのページにドラッグするか、領収書を転送する',
         dragReceiptAfterEmail: 'または、以下にアップロードするファイルを選択してください。',
+        dragReceiptsAfterEmail: 'または、以下にアップロードするファイルを選択してください。',
         chooseReceipt: 'アップロードするレシートを選択するか、レシートを転送してください',
+        chooseReceipts: 'アップロードするレシートを選択するか、レシートを転送してください',
         takePhoto: '写真を撮る',
         cameraAccess: '領収書の写真を撮るためにカメラへのアクセスが必要です。',
         deniedCameraAccess: 'カメラへのアクセスがまだ許可されていません。以下の手順に従ってください。',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 02c3e046c551..d04e35e4e07a 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'Lang ID',
         bankAccounts: 'Bankrekeningen',
         chooseFile: 'Bestand kiezen',
+        chooseFiles: 'Bestanden kiezen',
         dropTitle: 'Laat het gaan',
         dropMessage: 'Sleep hier je bestand in.',
         ignore: 'Ignore',
@@ -665,6 +667,12 @@ const translations = {
         attachmentImageTooLarge: 'Deze afbeelding is te groot om te bekijken voordat deze wordt geüpload.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `U kunt maximaal ${fileLimit} bestanden tegelijk uploaden.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Bestanden overschrijden ${maxUploadSizeInMB} MB. Probeer het opnieuw.`,
+        someFilesCantBeUploaded: 'Sommige bestanden kunnen niet worden geüpload',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Bestanden moeten kleiner zijn dan ${maxUploadSizeInMB} MB. Grotere bestanden worden niet geüpload.`,
+        maxFileLimitExceeded: "U kunt maximaal 30 bonnetjes tegelijk uploaden. Extra's worden niet geüpload.",
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType} bestanden worden niet ondersteund. Alleen ondersteunde bestandstypen worden geüpload.`,
+        learnMoreAboutSupportedFiles: 'Meer informatie over ondersteunde formaten.',
+        passwordProtected: "Met wachtwoord beveiligde PDF's worden niet ondersteund. Alleen ondersteunde bestanden worden geüpload.",
     },
     dropzone: {
         addAttachments: 'Bijlagen toevoegen',
@@ -952,9 +960,13 @@ const translations = {
     },
     receipt: {
         upload: 'Bonnetje uploaden',
+        uploadMultiple: 'Bonnetjes uploaden',
         dragReceiptBeforeEmail: 'Sleep een bon naar deze pagina, stuur een bon door naar',
+        dragReceiptsBeforeEmail: 'Sleep bonnen naar deze pagina, stuur bonnen door naar',
         dragReceiptAfterEmail: 'of kies hieronder een bestand om te uploaden.',
+        dragReceiptsAfterEmail: 'of kies hieronder bestanden om te uploaden.',
         chooseReceipt: 'Kies een bon om te uploaden of stuur een bon door naar',
+        chooseReceipts: 'Kies bonnen om te uploaden of stuur bonnen door naar',
         takePhoto: 'Maak een foto',
         cameraAccess: "Cameratoegang is vereist om foto's van bonnetjes te maken.",
         deniedCameraAccess: 'Camera-toegang is nog steeds niet verleend, volg alstublieft',
diff --git a/src/languages/params.ts b/src/languages/params.ts
index 037ad2b71eab..6a21f85f0ce6 100644
--- a/src/languages/params.ts
+++ b/src/languages/params.ts
@@ -677,6 +677,10 @@ type FileLimitParams = {
     fileLimit: number;
 };
 
+type FileTypeParams = {
+    fileType: string;
+};
+
 type CompanyCardBankName = {
     bankName: string;
 };
@@ -806,6 +810,7 @@ export type {
     AutoPayApprovedReportsLimitErrorParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     SpreadSheetColumnParams,
     SpreadFieldNameParams,
     AssignedCardParams,
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index ea8bad34c374..4420749b8fd0 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'Długi identyfikator',
         bankAccounts: 'Konta bankowe',
         chooseFile: 'Wybierz plik',
+        chooseFiles: 'Wybierz pliki',
         dropTitle: 'Puść to',
         dropMessage: 'Prześlij swój plik tutaj',
         ignore: 'Ignore',
@@ -666,6 +668,12 @@ const translations = {
         attachmentImageTooLarge: 'Ten obraz jest zbyt duży, aby wyświetlić podgląd przed przesłaniem.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Możesz przesłać jednocześnie maksymalnie ${fileLimit} plików.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Plik przekracza ${maxUploadSizeInMB} MB. Proszę spróbować ponownie.`,
+        someFilesCantBeUploaded: 'Niektóre pliki nie mogą zostać przesłane',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Pliki muszą mieć mniej niż ${maxUploadSizeInMB} MB. Większe pliki nie zostaną przesłane.`,
+        maxFileLimitExceeded: 'Możesz przesłać maksymalnie 30 paragonów naraz. Dodatkowe nie zostaną przesłane.',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `Pliki ${fileType} nie są obsługiwane. Tylko obsługiwane typy plików zostaną przesłane.`,
+        learnMoreAboutSupportedFiles: 'Dowiedz się więcej o obsługiwanych formatach.',
+        passwordProtected: 'Pliki PDF chronione hasłem nie są obsługiwane. Tylko obsługiwane pliki zostaną przesłane.',
     },
     dropzone: {
         addAttachments: 'Dodaj załączniki',
@@ -950,9 +958,13 @@ const translations = {
     },
     receipt: {
         upload: 'Prześlij paragon',
+        uploadMultiple: 'Prześlij paragony',
         dragReceiptBeforeEmail: 'Przeciągnij paragon na tę stronę, prześlij paragon do',
+        dragReceiptsBeforeEmail: 'Przeciągnij paragony na tę stronę, prześlij paragony do',
         dragReceiptAfterEmail: 'lub wybierz plik do przesłania poniżej.',
+        dragReceiptsAfterEmail: 'lub wybierz pliki do przesłania poniżej.',
         chooseReceipt: 'Wybierz paragon do przesłania lub prześlij paragon do',
+        chooseReceipts: 'Wybierz paragony do przesłania lub prześlij paragony do',
         takePhoto: 'Zrób zdjęcie',
         cameraAccess: 'Dostęp do aparatu jest wymagany, aby robić zdjęcia paragonów.',
         deniedCameraAccess: 'Dostęp do kamery nadal nie został przyznany, proszę postępować zgodnie z',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 512e58368c27..99bdb3adc726 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'ID longo',
         bankAccounts: 'Contas bancárias',
         chooseFile: 'Escolher arquivo',
+        chooseFiles: 'Escolher arquivos',
         dropTitle: 'Deixe ir',
         dropMessage: 'Solte seu arquivo aqui',
         ignore: 'Ignore',
@@ -665,6 +667,12 @@ const translations = {
         attachmentImageTooLarge: 'Esta imagem é muito grande para pré-visualizar antes de fazer o upload.',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `Você pode enviar até ${fileLimit} arquivos de uma vez.`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `Os arquivos excedem ${maxUploadSizeInMB} MB. Por favor, tente novamente.`,
+        someFilesCantBeUploaded: 'Alguns arquivos não podem ser enviados',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `Os arquivos devem ter menos de ${maxUploadSizeInMB} MB. Arquivos maiores não serão enviados.`,
+        maxFileLimitExceeded: 'Você pode enviar até 30 recibos por vez. Os extras não serão enviados.',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `Arquivos ${fileType} não são suportados. Apenas os tipos de arquivo suportados serão enviados.`,
+        learnMoreAboutSupportedFiles: 'Saiba mais sobre formatos suportados.',
+        passwordProtected: 'PDFs protegidos por senha não são suportados. Apenas arquivos suportados serão enviados.',
     },
     dropzone: {
         addAttachments: 'Adicionar anexos',
@@ -952,9 +960,13 @@ const translations = {
     },
     receipt: {
         upload: 'Fazer upload de recibo',
+        uploadMultiple: 'Fazer upload de recibos',
         dragReceiptBeforeEmail: 'Arraste um recibo para esta página, encaminhe um recibo para',
+        dragReceiptsBeforeEmail: 'Arraste recibos para esta página, encaminhe recibos para',
         dragReceiptAfterEmail: 'ou escolha um arquivo para enviar abaixo.',
+        dragReceiptsAfterEmail: 'ou escolha arquivos para enviar abaixo.',
         chooseReceipt: 'Escolha um recibo para enviar ou encaminhe um recibo para',
+        chooseReceipts: 'Escolha recibos para enviar ou encaminhe recibos para',
         takePhoto: 'Tire uma foto',
         cameraAccess: 'O acesso à câmera é necessário para tirar fotos dos recibos.',
         deniedCameraAccess: 'O acesso à câmera ainda não foi concedido, por favor siga',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index bb5ff6bbf7c0..84e836bb1aff 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -99,6 +99,7 @@ import type {
     ExportIntegrationSelectedParams,
     FeatureNameParams,
     FileLimitParams,
+    FileTypeParams,
     FiltersAmountBetweenParams,
     FlightLayoverParams,
     FlightParams,
@@ -562,6 +563,7 @@ const translations = {
         longID: 'Long ID',
         bankAccounts: '银行账户',
         chooseFile: '选择文件',
+        chooseFiles: '选择文件',
         dropTitle: 'Let it go',
         dropMessage: '在此处拖放您的文件',
         ignore: 'Ignore',
@@ -665,6 +667,12 @@ const translations = {
         attachmentImageTooLarge: '此图像太大，无法在上传前预览。',
         tooManyFiles: ({fileLimit}: FileLimitParams) => `您一次最多只能上传 ${fileLimit} 个文件。`,
         sizeExceededWithValue: ({maxUploadSizeInMB}: SizeExceededParams) => `文件超过 ${maxUploadSizeInMB} MB。请重试。`,
+        someFilesCantBeUploaded: '有些文件无法上传',
+        sizeLimitExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `文件必须小于${maxUploadSizeInMB} MB。较大的文件将不会被上传。`,
+        maxFileLimitExceeded: '您一次最多可上传30张收据。额外的将不会被上传。',
+        unsupportedFileType: ({fileType}: FileTypeParams) => `${fileType} 文件不受支持。只有受支持的文件类型才会被上传。`,
+        learnMoreAboutSupportedFiles: '了解更多关于支持的格式。',
+        passwordProtected: '不支持密码保护的PDF。只有受支持的文件才会被上传。',
     },
     dropzone: {
         addAttachments: '添加附件',
@@ -945,9 +953,13 @@ const translations = {
     },
     receipt: {
         upload: '上传收据',
+        uploadMultiple: '上传收据',
         dragReceiptBeforeEmail: '将收据拖到此页面上，转发收据到',
+        dragReceiptsBeforeEmail: '将收据拖到此页面上，转发收据到',
         dragReceiptAfterEmail: '或选择下面的文件上传。',
+        dragReceiptsAfterEmail: '或选择下面的文件上传。',
         chooseReceipt: '选择要上传的收据或转发收据到',
+        chooseReceipts: '选择要上传的收据或转发收据到',
         takePhoto: '拍照',
         cameraAccess: '需要相机权限来拍摄收据照片。',
         deniedCameraAccess: '相机访问权限仍未授予，请按照以下步骤操作',
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 1e67d9a7367a..d833327b33b5 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -953,9 +953,7 @@ function initMoneyRequest({reportID, policy, isFromGlobalCreate, currentIouReque
         }
     }
 
-    // Store the transaction in Onyx and mark it as not saved so it can be cleaned up later
-    // Use set() here so that there is no way that data will be leaked between objects when it gets reset
-    Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${newTransactionID}`, {
+    const newTransaction = {
         amount: 0,
         comment,
         created,
@@ -967,7 +965,13 @@ function initMoneyRequest({reportID, policy, isFromGlobalCreate, currentIouReque
         isFromGlobalCreate,
         merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
         splitPayerAccountIDs: currentUserPersonalDetails ? [currentUserPersonalDetails.accountID] : undefined,
-    });
+    };
+
+    // Store the transaction in Onyx and mark it as not saved so it can be cleaned up later
+    // Use set() here so that there is no way that data will be leaked between objects when it gets reset
+    Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${newTransactionID}`, newTransaction);
+
+    return newTransaction;
 }
 
 function createDraftTransaction(transaction: OnyxTypes.Transaction) {
diff --git a/src/libs/actions/TransactionEdit.ts b/src/libs/actions/TransactionEdit.ts
index 4e992dc0660b..e791757db78b 100644
--- a/src/libs/actions/TransactionEdit.ts
+++ b/src/libs/actions/TransactionEdit.ts
@@ -1,9 +1,11 @@
+import {format} from 'date-fns';
 import Onyx from 'react-native-onyx';
 import type {Connection, OnyxEntry} from 'react-native-onyx';
+import {formatCurrentUserToAttendee} from '@libs/IOUUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {Transaction} from '@src/types/onyx';
-import {getDraftTransactions} from './Transaction';
+import type {PersonalDetails, Transaction} from '@src/types/onyx';
+import {generateTransactionID, getDraftTransactions} from './Transaction';
 
 let connection: Connection;
 
@@ -117,6 +119,31 @@ function removeTransactionReceipt(transactionID: string | undefined) {
     Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {receipt: null});
 }
 
+type BuildOptimisticTransactionParams = {
+    initialTransaction: Partial<Transaction>;
+    currentUserPersonalDetails: PersonalDetails;
+    reportID: string;
+};
+
+function buildOptimisticTransactionAndCreateDraft({initialTransaction, currentUserPersonalDetails, reportID}: BuildOptimisticTransactionParams): Transaction {
+    const newTransactionID = generateTransactionID();
+    const {currency, iouRequestType, isFromGlobalCreate, splitPayerAccountIDs} = initialTransaction ?? {};
+    const newTransaction = {
+        amount: 0,
+        created: format(new Date(), 'yyyy-MM-dd'),
+        currency,
+        comment: {attendees: formatCurrentUserToAttendee(currentUserPersonalDetails, reportID)},
+        iouRequestType,
+        reportID,
+        transactionID: newTransactionID,
+        isFromGlobalCreate,
+        merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
+        splitPayerAccountIDs,
+    } as Transaction;
+    createDraftTransaction(newTransaction);
+    return newTransaction;
+}
+
 export {
     createBackupTransaction,
     removeBackupTransaction,
@@ -126,4 +153,5 @@ export {
     removeTransactionReceipt,
     removeDraftTransactions,
     removeDraftSplitTransaction,
+    buildOptimisticTransactionAndCreateDraft,
 };
diff --git a/src/libs/deepReplaceKeysAndValues.ts b/src/libs/deepReplaceKeysAndValues.ts
index 7fa7adfaa707..45108215c877 100644
--- a/src/libs/deepReplaceKeysAndValues.ts
+++ b/src/libs/deepReplaceKeysAndValues.ts
@@ -26,6 +26,11 @@ function deepReplaceKeysAndValues<T extends ReplaceableValue>(target: T, oldVal:
     Object.entries(target).forEach(([key, val]) => {
         const newKey = key.replace(oldVal, newVal);
 
+        if (val instanceof File || val instanceof Blob) {
+            newObj[newKey] = val;
+            return;
+        }
+
         if (typeof val === 'object') {
             newObj[newKey] = deepReplaceKeysAndValues(val as T, oldVal, newVal);
             return;
diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts
index 12e9a0c8243b..52fda826f644 100644
--- a/src/libs/fileDownload/FileUtils.ts
+++ b/src/libs/fileDownload/FileUtils.ts
@@ -1,7 +1,7 @@
 import {Str} from 'expensify-common';
 import {Alert, Linking, Platform} from 'react-native';
 import ImageSize from 'react-native-image-size';
-import type {TupleToUnion} from 'type-fest';
+import type {TupleToUnion, ValueOf} from 'type-fest';
 import DateUtils from '@libs/DateUtils';
 import getPlatform from '@libs/getPlatform';
 import {translateLocal} from '@libs/Localize';
@@ -388,6 +388,112 @@ const getConfirmModalPrompt = (attachmentInvalidReason: TranslationPaths | undef
     return translateLocal(attachmentInvalidReason);
 };
 
+const isValidReceiptExtension = (file: FileObject) => {
+    const {fileExtension} = splitExtensionFromFileName(file?.name ?? '');
+    return CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(
+        fileExtension.toLowerCase() as TupleToUnion<typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS>,
+    );
+};
+
+const isHeicOrHeifImage = (file: FileObject) => {
+    return (
+        file?.type?.startsWith('image') &&
+        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+        (file.name?.toLowerCase().endsWith('.heic') || file.name?.toLowerCase().endsWith('.heif'))
+    );
+};
+
+const validateAttachment = (file: FileObject, isCheckingMultipleFiles?: boolean, isValidatingReceipt?: boolean) => {
+    const maxFileSize = isValidatingReceipt ? CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE : CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE;
+    if (!Str.isImage(file.name ?? '') && !isHeicOrHeifImage(file) && (file?.size ?? 0) > maxFileSize) {
+        return isCheckingMultipleFiles ? CONST.FILE_VALIDATION_ERRORS.FILE_TOO_LARGE_MULTIPLE : CONST.FILE_VALIDATION_ERRORS.FILE_TOO_LARGE;
+    }
+
+    if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
+        return CONST.FILE_VALIDATION_ERRORS.FILE_TOO_SMALL;
+    }
+
+    if (isValidatingReceipt && !isValidReceiptExtension(file)) {
+        return isCheckingMultipleFiles ? CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE : CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE;
+    }
+    return '';
+};
+
+type TranslationAdditionalData = {
+    maxUploadSizeInMB?: number;
+    fileLimit?: number;
+    fileType?: string;
+};
+
+const getFileValidationErrorText = (
+    validationError: ValueOf<typeof CONST.FILE_VALIDATION_ERRORS> | null,
+    additionalData: TranslationAdditionalData = {},
+): {
+    title: string;
+    reason: string;
+} => {
+    if (!validationError) {
+        return {
+            title: '',
+            reason: '',
+        };
+    }
+    switch (validationError) {
+        case CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE:
+            return {
+                title: translateLocal('attachmentPicker.wrongFileType'),
+                reason: translateLocal('attachmentPicker.notAllowedExtension'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE:
+            return {
+                title: translateLocal('attachmentPicker.someFilesCantBeUploaded'),
+                reason: translateLocal('attachmentPicker.unsupportedFileType', {fileType: additionalData.fileType ?? ''}),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.FILE_TOO_LARGE:
+            return {
+                title: translateLocal('attachmentPicker.attachmentTooLarge'),
+                reason: translateLocal('attachmentPicker.sizeExceeded'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.FILE_TOO_LARGE_MULTIPLE:
+            return {
+                title: translateLocal('attachmentPicker.someFilesCantBeUploaded'),
+                reason: translateLocal('attachmentPicker.sizeLimitExceeded', {
+                    maxUploadSizeInMB: additionalData.maxUploadSizeInMB ?? CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE / 1024 / 1024,
+                }),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.FILE_TOO_SMALL:
+            return {
+                title: translateLocal('attachmentPicker.attachmentTooSmall'),
+                reason: translateLocal('attachmentPicker.sizeNotMet'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.FOLDER_NOT_ALLOWED:
+            return {
+                title: translateLocal('attachmentPicker.attachmentError'),
+                reason: translateLocal('attachmentPicker.folderNotAllowedMessage'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED:
+            return {
+                title: translateLocal('attachmentPicker.someFilesCantBeUploaded'),
+                reason: translateLocal('attachmentPicker.maxFileLimitExceeded'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED:
+            return {
+                title: translateLocal('attachmentPicker.attachmentError'),
+                reason: translateLocal('attachmentPicker.errorWhileSelectingCorruptedAttachment'),
+            };
+        case CONST.FILE_VALIDATION_ERRORS.PROTECTED_FILE:
+            return {
+                title: translateLocal('attachmentPicker.attachmentError'),
+                reason: translateLocal('attachmentPicker.protectedPDFNotSupported'),
+            };
+        default:
+            return {
+                title: translateLocal('attachmentPicker.attachmentError'),
+                reason: translateLocal('attachmentPicker.errorWhileSelectingCorruptedAttachment'),
+            };
+    }
+};
+
 export {
     showGeneralErrorAlert,
     showSuccessAlert,
@@ -410,5 +516,9 @@ export {
     resizeImageIfNeeded,
     createFile,
     validateReceipt,
+    validateAttachment,
+    isValidReceiptExtension,
+    getFileValidationErrorText,
+    isHeicOrHeifImage,
     getConfirmModalPrompt,
 };
diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx
index 1ac12c860ad8..2b7bc2554c91 100644
--- a/src/pages/Search/SearchPage.tsx
+++ b/src/pages/Search/SearchPage.tsx
@@ -7,9 +7,7 @@ import DecisionModal from '@components/DecisionModal';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
 import DragAndDropProvider from '@components/DragAndDrop/Provider';
 import DropZoneUI from '@components/DropZone/DropZoneUI';
-import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
 import * as Expensicons from '@components/Icon/Expensicons';
-import PDFThumbnail from '@components/PDFThumbnail';
 import ScreenWrapper from '@components/ScreenWrapper';
 import Search from '@components/Search';
 import {useSearchContext} from '@components/Search/SearchContext';
@@ -18,7 +16,8 @@ import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/
 import SearchPageHeader from '@components/Search/SearchPageHeader/SearchPageHeader';
 import type {PaymentData, SearchParams} from '@components/Search/types';
 import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
-import useFileValidation from '@hooks/useFileValidation';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useFilesValidation from '@hooks/useFilesValidation';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
 import useOnyx from '@hooks/useOnyx';
@@ -38,22 +37,24 @@ import {
     search,
     unholdMoneyRequestOnSearch,
 } from '@libs/actions/Search';
-import {getConfirmModalPrompt} from '@libs/fileDownload/FileUtils';
 import {navigateToParticipantPage} from '@libs/IOUUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types';
-import {hasVBBA} from '@libs/PolicyUtils';
-import {generateReportID} from '@libs/ReportUtils';
+import {hasVBBA, isPaidGroupPolicy} from '@libs/PolicyUtils';
+import {generateReportID, getPolicyExpenseChat} from '@libs/ReportUtils';
 import {buildCannedSearchQuery, buildSearchQueryJSON} from '@libs/SearchQueryUtils';
+import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
+import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types';
 import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
 import variables from '@styles/variables';
-import {initMoneyRequest, setMoneyRequestReceipt} from '@userActions/IOU';
+import {initMoneyRequest, setMoneyRequestParticipantsFromReport, setMoneyRequestReceipt} from '@userActions/IOU';
+import {buildOptimisticTransactionAndCreateDraft} from '@userActions/TransactionEdit';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type SCREENS from '@src/SCREENS';
-import type {SearchResults} from '@src/types/onyx';
+import type {SearchResults, Transaction} from '@src/types/onyx';
 import SearchPageNarrow from './SearchPageNarrow';
 
 type SearchPageProps = PlatformStackScreenProps<SearchFullscreenNavigatorParamList, typeof SCREENS.SEARCH.ROOT>;
@@ -67,24 +68,17 @@ function SearchPage({route}: SearchPageProps) {
     const theme = useTheme();
     const {isOffline} = useNetwork();
     const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, isExportMode, setExportMode} = useSearchContext();
+    const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true});
     const [lastPaymentMethods = {}] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true});
 
+    const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false});
+    const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true});
+
     const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
     const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
     const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false);
     const [isDownloadExportModalVisible, setIsDownloadExportModalVisible] = useState(false);
-    const {
-        validateAndResizeFile,
-        setIsAttachmentInvalid,
-        isAttachmentInvalid,
-        attachmentInvalidReason,
-        attachmentInvalidReasonTitle,
-        setUploadReceiptError,
-        pdfFile,
-        setPdfFile,
-        isLoadingReceipt,
-    } = useFileValidation();
 
     const {q} = route.params;
 
@@ -398,33 +392,67 @@ function SearchPage({route}: SearchPageProps) {
         });
     };
 
-    // TODO: to be refactored in step 3
-    const hideReceiptModal = () => {
-        setIsAttachmentInvalid(false);
-    };
-
-    const saveFileAndInitMoneyRequest = (file: FileObject) => {
-        const source = URL.createObjectURL(file as Blob);
+    const saveFileAndInitMoneyRequest = (files: FileObject[]) => {
         const newReportID = generateReportID();
-        initMoneyRequest({
+        const initialTransaction = initMoneyRequest({
+            isFromGlobalCreate: true,
             reportID: newReportID,
             newIouRequestType: CONST.IOU.REQUEST_TYPE.SCAN,
         });
-        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-        setMoneyRequestReceipt(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, source, file.name || '', true);
-        navigateToParticipantPage(CONST.IOU.TYPE.CREATE, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, newReportID);
-    };
 
-    const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
-        validateAndResizeFile(originalFile, saveFileAndInitMoneyRequest, isPdfValidated);
+        const newReceiptFiles: ReceiptFile[] = [];
+
+        files.forEach((file, index) => {
+            const source = URL.createObjectURL(file as Blob);
+            const transaction =
+                index === 0
+                    ? (initialTransaction as Partial<Transaction>)
+                    : buildOptimisticTransactionAndCreateDraft({
+                          initialTransaction: initialTransaction as Partial<Transaction>,
+                          currentUserPersonalDetails,
+                          reportID: newReportID,
+                      });
+            const transactionID = transaction.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
+            newReceiptFiles.push({
+                file,
+                source,
+                transactionID,
+            });
+            setMoneyRequestReceipt(transactionID, source, file.name ?? '', true);
+        });
+
+        if (isPaidGroupPolicy(activePolicy) && activePolicy?.isPolicyExpenseChatEnabled && !shouldRestrictUserBillableActions(activePolicy.id)) {
+            const activePolicyExpenseChat = getPolicyExpenseChat(currentUserPersonalDetails.accountID, activePolicy?.id);
+            const setParticipantsPromises = newReceiptFiles.map((receiptFile) => setMoneyRequestParticipantsFromReport(receiptFile.transactionID, activePolicyExpenseChat));
+            Promise.all(setParticipantsPromises).then(() =>
+                Navigation.navigate(
+                    ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(
+                        CONST.IOU.ACTION.CREATE,
+                        CONST.IOU.TYPE.SUBMIT,
+                        initialTransaction?.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID,
+                        activePolicyExpenseChat?.reportID,
+                    ),
+                ),
+            );
+        } else {
+            navigateToParticipantPage(CONST.IOU.TYPE.CREATE, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, newReportID);
+        }
     };
 
+    const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(saveFileAndInitMoneyRequest);
+
     const initScanRequest = (e: DragEvent) => {
-        const file = e?.dataTransfer?.files[0];
-        if (file) {
-            file.uri = URL.createObjectURL(file);
-            setReceiptAndNavigate(file);
+        const files = Array.from(e?.dataTransfer?.files ?? []);
+
+        if (files.length === 0) {
+            return;
         }
+        files.forEach((file) => {
+            // eslint-disable-next-line no-param-reassign
+            file.uri = URL.createObjectURL(file);
+        });
+
+        validateFiles(files);
     };
 
     const createExportAll = useCallback(() => {
@@ -448,24 +476,6 @@ function SearchPage({route}: SearchPageProps) {
     const {resetVideoPlayerData} = usePlaybackContext();
     const shouldShowOfflineIndicator = currentSearchResults?.data ?? lastNonEmptySearchResults;
 
-    // TODO: to be refactored in step 3
-    const PDFThumbnailView = pdfFile ? (
-        <PDFThumbnail
-            style={styles.invisiblePDF}
-            previewSourceURL={pdfFile.uri ?? ''}
-            onLoadSuccess={() => {
-                setPdfFile(null);
-                setReceiptAndNavigate(pdfFile, true);
-            }}
-            onPassword={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported');
-            }}
-            onLoadError={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment');
-            }}
-        />
-    ) : null;
-
     // Handles video player cleanup:
     // 1. On mount: Resets player if navigating from report screen
     // 2. On unmount: Stops video when leaving this screen
@@ -496,9 +506,8 @@ function SearchPage({route}: SearchPageProps) {
     if (shouldUseNarrowLayout) {
         return (
             <>
-                {isLoadingReceipt && <FullScreenLoadingIndicator />}
                 <DragAndDropProvider isDisabled={!isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP)}>
-                    {PDFThumbnailView}
+                    {PDFValidationComponent}
                     <SearchPageNarrow
                         queryJSON={queryJSON}
                         headerButtonsOptions={headerButtonsOptions}
@@ -515,15 +524,7 @@ function SearchPage({route}: SearchPageProps) {
                             dropWrapperStyles={{marginBottom: variables.bottomTabHeight}}
                         />
                     </DragAndDropConsumer>
-                    <ConfirmModal
-                        title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
-                        onConfirm={hideReceiptModal}
-                        onCancel={hideReceiptModal}
-                        isVisible={isAttachmentInvalid}
-                        prompt={getConfirmModalPrompt(attachmentInvalidReason)}
-                        confirmText={translate('common.close')}
-                        shouldShowCancelButton={false}
-                    />
+                    {ErrorModal}
                 </DragAndDropProvider>
                 {!!selectionMode && selectionMode?.isEnabled && (
                     <View>
@@ -582,9 +583,8 @@ function SearchPage({route}: SearchPageProps) {
                             shouldShowOfflineIndicatorInWideScreen={!!shouldShowOfflineIndicator}
                             offlineIndicatorStyle={styles.mtAuto}
                         >
-                            {isLoadingReceipt && <FullScreenLoadingIndicator />}
                             <DragAndDropProvider isDisabled={!isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP)}>
-                                {PDFThumbnailView}
+                                {PDFValidationComponent}
                                 <SearchPageHeader
                                     queryJSON={queryJSON}
                                     headerButtonsOptions={headerButtonsOptions}
@@ -612,15 +612,7 @@ function SearchPage({route}: SearchPageProps) {
                                 </DragAndDropConsumer>
                             </DragAndDropProvider>
                         </ScreenWrapper>
-                        <ConfirmModal
-                            title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
-                            onConfirm={hideReceiptModal}
-                            onCancel={hideReceiptModal}
-                            isVisible={isAttachmentInvalid}
-                            prompt={getConfirmModalPrompt(attachmentInvalidReason)}
-                            confirmText={translate('common.close')}
-                            shouldShowCancelButton={false}
-                        />
+                        {ErrorModal}
                     </View>
                 )}
                 <ConfirmModal
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 301ea608bf24..d9952578f494 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -8,7 +8,6 @@ import {runOnUI, useSharedValue} from 'react-native-reanimated';
 import type {Emoji} from '@assets/emojis/types';
 import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView';
 import AttachmentModal from '@components/AttachmentModal';
-import ConfirmModal from '@components/ConfirmModal';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
 import DropZoneUI from '@components/DropZone/DropZoneUI';
 import DualDropZone from '@components/DropZone/DualDropZone';
@@ -20,10 +19,9 @@ import type {Mention} from '@components/MentionSuggestions';
 import OfflineIndicator from '@components/OfflineIndicator';
 import OfflineWithFeedback from '@components/OfflineWithFeedback';
 import {usePersonalDetails} from '@components/OnyxProvider';
-import PDFThumbnail from '@components/PDFThumbnail';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useDebounce from '@hooks/useDebounce';
-import useFileValidation from '@hooks/useFileValidation';
+import useFilesValidation from '@hooks/useFilesValidation';
 import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLength';
 import useHandleExceedMaxTaskTitleLength from '@hooks/useHandleExceedMaxTaskTitleLength';
 import useLocalize from '@hooks/useLocalize';
@@ -36,7 +34,6 @@ import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
 import {canUseTouchScreen} from '@libs/DeviceCapabilities';
 import DomUtils from '@libs/DomUtils';
 import {getDraftComment} from '@libs/DraftCommentUtils';
-import {getConfirmModalPrompt} from '@libs/fileDownload/FileUtils';
 import getModalState from '@libs/getModalState';
 import Performance from '@libs/Performance';
 import {
@@ -52,9 +49,11 @@ import {
     isGroupChat,
     isInvoiceReport,
     isReportTransactionThread,
+    isSelfDM,
     isSettled,
     isUserCreatedPolicyRoom,
 } from '@libs/ReportUtils';
+import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
 import {getTransactionID, hasReceipt as hasReceiptTransactionUtils} from '@libs/TransactionUtils';
 import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
 import Navigation from '@navigation/Navigation';
@@ -67,6 +66,7 @@ import {hideEmojiPicker, isActive as isActiveEmojiPickerAction} from '@userActio
 import {initMoneyRequest, replaceReceipt, setMoneyRequestParticipantsFromReport, setMoneyRequestReceipt} from '@userActions/IOU';
 import {addAttachment as addAttachmentReportActions, setIsComposerFullSize} from '@userActions/Report';
 import Timing from '@userActions/Timing';
+import {buildOptimisticTransactionAndCreateDraft} from '@userActions/TransactionEdit';
 import {isBlockedFromConcierge as isBlockedFromConciergeUserAction} from '@userActions/User';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -148,13 +148,11 @@ function ReportActionCompose({
     const personalDetails = usePersonalDetails();
     const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, {canBeMissing: true});
     const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, {canBeMissing: true});
+    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true});
 
     // TODO: remove beta check after the feature is enabled
     const {isBetaEnabled} = usePermissions();
 
-    const {validateAndResizeFile, setIsAttachmentInvalid, isAttachmentInvalid, attachmentInvalidReason, attachmentInvalidReasonTitle, setUploadReceiptError, pdfFile, setPdfFile} =
-        useFileValidation();
-
     /**
      * Updates the Highlight state of the composer
      */
@@ -242,6 +240,8 @@ function ReportActionCompose({
 
     const hasReceipt = useMemo(() => hasReceiptTransactionUtils(transaction), [transaction]);
 
+    const isEditingReceipt = isTransactionThreadView && transactionID && hasReceipt;
+
     // Placeholder to display in the chat input.
     const inputPlaceholder = useMemo(() => {
         if (includesConcierge && userBlockedFromConcierge) {
@@ -391,6 +391,7 @@ function ReportActionCompose({
     const composerRefShared = useSharedValue<{
         clear: (() => void) | undefined;
     }>({clear: undefined});
+
     const handleSendMessage = useCallback(() => {
         'worklet';
 
@@ -473,55 +474,71 @@ function ReportActionCompose({
         [isComposerFullSize, reportID, debouncedValidate],
     );
 
-    // TODO: to be refactored in step 3
-    const hideReceiptModal = () => {
-        setIsAttachmentInvalid(false);
-    };
-
-    const saveFileAndInitMoneyRequest = (file: FileObject) => {
-        const source = URL.createObjectURL(file as Blob);
-
-        if (isTransactionThreadView && transactionID) {
-            replaceReceipt({transactionID, file: file as File, source});
+    const saveFileAndInitMoneyRequest = (files: FileObject[]) => {
+        if (files.length === 0) {
+            return;
+        }
+        if (isEditingReceipt) {
+            const source = URL.createObjectURL(files.at(0) as Blob);
+            replaceReceipt({transactionID, file: files.at(0) as File, source});
         } else {
-            initMoneyRequest({reportID, newIouRequestType: CONST.IOU.REQUEST_TYPE.SCAN});
-            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-            setMoneyRequestReceipt(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, source, file.name || '', true);
-            setMoneyRequestParticipantsFromReport(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report).then(() => {
-                Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.SUBMIT, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID));
+            const initialTransaction = initMoneyRequest({
+                reportID,
+                newIouRequestType: CONST.IOU.REQUEST_TYPE.SCAN,
             });
+
+            files.forEach((file, index) => {
+                const source = URL.createObjectURL(file as Blob);
+                const newTransaction =
+                    index === 0
+                        ? (initialTransaction as Partial<OnyxTypes.Transaction>)
+                        : buildOptimisticTransactionAndCreateDraft({
+                              initialTransaction: initialTransaction as Partial<OnyxTypes.Transaction>,
+                              currentUserPersonalDetails,
+                              reportID,
+                          });
+                const newTransactionID = newTransaction?.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
+                setMoneyRequestReceipt(newTransactionID, source, file.name ?? '', true);
+                setMoneyRequestParticipantsFromReport(newTransactionID, report);
+            });
+            Navigation.navigate(
+                ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(
+                    CONST.IOU.ACTION.CREATE,
+                    isSelfDM(report) ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT,
+                    CONST.IOU.OPTIMISTIC_TRANSACTION_ID,
+                    reportID,
+                ),
+            );
         }
     };
 
-    const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
-        validateAndResizeFile(originalFile, saveFileAndInitMoneyRequest, isPdfValidated);
-    };
+    const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(saveFileAndInitMoneyRequest);
 
     const handleAddingReceipt = (e: DragEvent) => {
-        const file = e?.dataTransfer?.files[0];
-        if (file) {
-            file.uri = URL.createObjectURL(file);
-            setReceiptAndNavigate(file);
+        if (policy && shouldRestrictUserBillableActions(policy.id)) {
+            Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
+            return;
+        }
+        if (isEditingReceipt) {
+            const file = e?.dataTransfer?.files?.[0];
+            if (file) {
+                file.uri = URL.createObjectURL(file);
+                validateFiles([file]);
+                return;
+            }
         }
-    };
 
-    // TODO: to be refactored in step 3
-    const PDFThumbnailView = pdfFile ? (
-        <PDFThumbnail
-            style={styles.invisiblePDF}
-            previewSourceURL={pdfFile.uri ?? ''}
-            onLoadSuccess={() => {
-                setPdfFile(null);
-                setReceiptAndNavigate(pdfFile, true);
-            }}
-            onPassword={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported');
-            }}
-            onLoadError={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment');
-            }}
-        />
-    ) : null;
+        const files = Array.from(e?.dataTransfer?.files ?? []);
+        if (files.length === 0) {
+            return;
+        }
+        files.forEach((file) => {
+            // eslint-disable-next-line no-param-reassign
+            file.uri = URL.createObjectURL(file);
+        });
+
+        validateFiles(files);
+    };
 
     return (
         <View style={[shouldShowReportRecipientLocalTime && !isOffline && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}>
@@ -548,7 +565,7 @@ function ReportActionCompose({
                             !!exceededMaxLength && styles.borderColorDanger,
                         ]}
                     >
-                        {PDFThumbnailView}
+                        {PDFValidationComponent}
                         <AttachmentModal
                             headerTitle={translate('reportActionCompose.sendAttachment')}
                             onConfirm={addAttachment}
@@ -697,15 +714,7 @@ function ReportActionCompose({
                             handleSendMessage={handleSendMessage}
                         />
                     </View>
-                    <ConfirmModal
-                        title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
-                        onConfirm={hideReceiptModal}
-                        onCancel={hideReceiptModal}
-                        isVisible={isAttachmentInvalid}
-                        prompt={getConfirmModalPrompt(attachmentInvalidReason)}
-                        confirmText={translate('common.close')}
-                        shouldShowCancelButton={false}
-                    />
+                    {ErrorModal}
                     <View
                         style={[
                             styles.flexRow,
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index f35dfa17a4eb..af7ce0359b8b 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -1,7 +1,5 @@
-import {Str} from 'expensify-common';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {View} from 'react-native';
-import ConfirmModal from '@components/ConfirmModal';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
 import DragAndDropProvider from '@components/DragAndDrop/Provider';
 import DropZoneUI from '@components/DropZone/DropZoneUI';
@@ -11,12 +9,12 @@ import * as Expensicons from '@components/Icon/Expensicons';
 import LocationPermissionModal from '@components/LocationPermissionModal';
 import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList';
 import {usePersonalDetails} from '@components/OnyxProvider';
-import PDFThumbnail from '@components/PDFThumbnail';
 import PrevNextButtons from '@components/PrevNextButtons';
 import ScreenWrapper from '@components/ScreenWrapper';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useDeepCompareRef from '@hooks/useDeepCompareRef';
 import useFetchRoute from '@hooks/useFetchRoute';
+import useFilesValidation from '@hooks/useFilesValidation';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
 import useOnyx from '@hooks/useOnyx';
@@ -26,7 +24,7 @@ import useThreeDotsAnchorPosition from '@hooks/useThreeDotsAnchorPosition';
 import {completeTestDriveTask} from '@libs/actions/Task';
 import DateUtils from '@libs/DateUtils';
 import {canUseTouchScreen} from '@libs/DeviceCapabilities';
-import {isLocalFile as isLocalFileFileUtils, resizeImageIfNeeded, validateReceipt} from '@libs/fileDownload/FileUtils';
+import {isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils';
 import getCurrentPosition from '@libs/getCurrentPosition';
 import {isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpenseIOUUtils, navigateToStartMoneyRequestStep, shouldUseTransactionDraft} from '@libs/IOUUtils';
 import Log from '@libs/Log';
@@ -36,7 +34,7 @@ import {rand64} from '@libs/NumberUtils';
 import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
 import Performance from '@libs/Performance';
 import {generateReportID, getBankAccountRoute, getReportOrDraftReport, isProcessingReport, isReportOutstanding, isSelectedManagerMcTest} from '@libs/ReportUtils';
-import {getAttendees, getDefaultTaxCode, getRateID, getRequestType, getValidWaypoints, isScanRequest} from '@libs/TransactionUtils';
+import {getAttendees, getDefaultTaxCode, getRateID, getRequestType, getValidWaypoints, hasReceipt, isScanRequest} from '@libs/TransactionUtils';
 import ReceiptDropUI from '@pages/iou/ReceiptDropUI';
 import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
 import type {GpsPoint} from '@userActions/IOU';
@@ -62,7 +60,6 @@ import {
 import {openDraftWorkspaceRequest} from '@userActions/Policy/Policy';
 import {removeDraftTransactions} from '@userActions/TransactionEdit';
 import CONST from '@src/CONST';
-import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type SCREENS from '@src/SCREENS';
@@ -115,7 +112,12 @@ function IOURequestStepConfirmation({
         () => (!isLoadingCurrentTransaction ? (optimisticTransaction ?? existingTransaction) : undefined),
         [existingTransaction, optimisticTransaction, isLoadingCurrentTransaction],
     );
-    const transactionsCategories = useDeepCompareRef(transactions.map(({transactionID, category}) => ({transactionID, category})));
+    const transactionsCategories = useDeepCompareRef(
+        transactions.map(({transactionID, category}) => ({
+            transactionID,
+            category,
+        })),
+    );
 
     const realPolicyID = getIOURequestPolicyID(initialTransaction, reportReal);
     const draftPolicyID = getIOURequestPolicyID(initialTransaction, reportDraft);
@@ -146,12 +148,6 @@ function IOURequestStepConfirmation({
     const [selectedParticipantList, setSelectedParticipantList] = useState<Participant[]>([]);
     const [isDraggingOver, setIsDraggingOver] = useState(false);
 
-    const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
-    const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths>();
-    const [attachmentInvalidReason, setAttachmentValidReason] = useState<TranslationPaths>();
-    const [pdfFile, setPdfFile] = useState<null | FileObject>(null);
-    const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
-
     const [receiptFiles, setReceiptFiles] = useState<Record<string, Receipt | undefined>>({});
     const requestType = getRequestType(transaction);
     const isDistanceRequest = requestType === CONST.IOU.REQUEST_TYPE.DISTANCE;
@@ -160,6 +156,7 @@ function IOURequestStepConfirmation({
 
     const receiptFilename = transaction?.filename;
     const receiptPath = transaction?.receipt?.source;
+    const isEditingReceipt = hasReceipt(transaction);
     const customUnitRateID = getRateID(transaction) ?? '';
     const defaultTaxCode = getDefaultTaxCode(policy, transaction);
     const transactionTaxCode = (transaction?.taxCode ? transaction?.taxCode : defaultTaxCode) ?? '';
@@ -221,21 +218,12 @@ function IOURequestStepConfirmation({
     const isPolicyExpenseChat = useMemo(() => participants?.some((participant) => participant.isPolicyExpenseChat), [participants]);
     const formHasBeenSubmitted = useRef(false);
 
-    const confirmModalPrompt = useMemo(() => {
-        if (!attachmentInvalidReason) {
-            return '';
-        }
-        if (attachmentInvalidReason === 'attachmentPicker.sizeExceededWithLimit') {
-            return translate(attachmentInvalidReason, {maxUploadSizeInMB: CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE / (1024 * 1024)});
-        }
-        return translate(attachmentInvalidReason);
-    }, [attachmentInvalidReason, translate]);
-
     useFetchRoute(transaction, transaction?.comment?.waypoints, action, shouldUseTransactionDraft(action) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT);
 
     useEffect(() => {
         Performance.markEnd(CONST.TIMING.OPEN_CREATE_EXPENSE_APPROVE);
     }, []);
+
     useEffect(() => {
         const policyExpenseChat = participants?.find((participant) => participant.isPolicyExpenseChat);
         if (policyExpenseChat?.policyID && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
@@ -310,51 +298,6 @@ function IOURequestStepConfirmation({
         // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
     }, [transactionIDs, requestType, defaultCategory, policy?.id]);
 
-    /**
-     * Sets the upload receipt error modal content when an invalid receipt is uploaded
-     */
-    const setUploadReceiptError = (isInvalid: boolean, title: TranslationPaths, reason: TranslationPaths) => {
-        setIsAttachmentInvalid(isInvalid);
-        setAttachmentInvalidReasonTitle(title);
-        setAttachmentValidReason(reason);
-        setPdfFile(null);
-    };
-
-    const hideReceiptModal = () => {
-        setIsAttachmentInvalid(false);
-    };
-
-    /**
-     * Sets the Receipt object when dragging and dropping a file
-     */
-    const setReceiptOnDrop = (originalFile: FileObject, isPdfValidated?: boolean) => {
-        validateReceipt(originalFile, setUploadReceiptError).then((isFileValid) => {
-            if (!isFileValid) {
-                return;
-            }
-
-            // If we have a pdf file and if it is not validated then set the pdf file for validation and return
-            if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
-                setPdfFile(originalFile);
-                setIsLoadingReceipt(true);
-                return;
-            }
-
-            // With the image size > 24MB, we use manipulateAsync to resize the image.
-            // It takes a long time so we should display a loading indicator while the resize image progresses.
-            if (Str.isImage(originalFile.name ?? '') && (originalFile?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
-                setIsLoadingReceipt(true);
-            }
-            resizeImageIfNeeded(originalFile).then((file) => {
-                setIsLoadingReceipt(false);
-                // Store the receipt on the transaction object in Onyx
-                const source = URL.createObjectURL(file as Blob);
-                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-                setMoneyRequestReceipt(currentTransactionID, source, file.name || '', true);
-            });
-        });
-    };
-
     const navigateBack = useCallback(() => {
         // If the action is categorize and there's no policies other than personal one, we simply call goBack(), i.e: dismiss the whole flow together
         // We don't need to subscribe to policy_ collection as we only need to check on the latest collection value
@@ -1001,30 +944,32 @@ function IOURequestStepConfirmation({
         setIsConfirming(false);
     };
 
+    /**
+     * Sets the Receipt object when dragging and dropping a file
+     */
+    const setReceiptOnDrop = (files: FileObject[]) => {
+        const file = files.at(0);
+        if (!file) {
+            return;
+        }
+        const source = URL.createObjectURL(file as Blob);
+        setMoneyRequestReceipt(currentTransactionID, source, file.name ?? '', true);
+    };
+
+    const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(setReceiptOnDrop);
+
+    const handleDroppingReceipt = (e: DragEvent) => {
+        const file = e?.dataTransfer?.files[0];
+        if (file) {
+            file.uri = URL.createObjectURL(file);
+            validateFiles([file]);
+        }
+    };
+
     if (isLoadingTransaction) {
         return <FullScreenLoadingIndicator />;
     }
 
-    const PDFThumbnailView = pdfFile ? (
-        <PDFThumbnail
-            style={styles.invisiblePDF}
-            previewSourceURL={pdfFile.uri ?? ''}
-            onLoadSuccess={() => {
-                setPdfFile(null);
-                setIsLoadingReceipt(false);
-                setReceiptOnDrop(pdfFile, true);
-            }}
-            onPassword={() => {
-                setIsLoadingReceipt(false);
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported');
-            }}
-            onLoadError={() => {
-                setIsLoadingReceipt(false);
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment');
-            }}
-        />
-    ) : null;
-
     const showNextTransaction = () => {
         const nextTransaction = transactions.at(currentTransactionIndex + 1);
         if (nextTransaction) {
@@ -1080,47 +1025,23 @@ function IOURequestStepConfirmation({
                             />
                         ) : null}
                     </HeaderWithBackButton>
-                    {(isLoading || isLoadingReceipt || (isScanRequest(transaction) && !Object.values(receiptFiles).length)) && <FullScreenLoadingIndicator />}
-                    {PDFThumbnailView}
+                    {(isLoading || (isScanRequest(transaction) && !Object.values(receiptFiles).length)) && <FullScreenLoadingIndicator />}
+                    {PDFValidationComponent}
                     {/* TODO: remove beta check after the feature is enabled */}
                     {isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP) ? (
-                        <DragAndDropConsumer
-                            onDrop={(e) => {
-                                const file = e?.dataTransfer?.files[0];
-                                if (file) {
-                                    file.uri = URL.createObjectURL(file);
-                                    setReceiptOnDrop(file);
-                                }
-                            }}
-                        >
+                        <DragAndDropConsumer onDrop={handleDroppingReceipt}>
                             <DropZoneUI
-                                icon={Expensicons.ReplaceReceipt}
+                                icon={isEditingReceipt ? Expensicons.ReplaceReceipt : Expensicons.SmartScan}
                                 dropStyles={styles.receiptDropOverlay(true)}
-                                dropTitle={translate('dropzone.replaceReceipt')}
+                                dropTitle={translate(isEditingReceipt ? 'dropzone.replaceReceipt' : 'quickAction.scanReceipt')}
                                 dropTextStyles={styles.receiptDropText}
                                 dropInnerWrapperStyles={styles.receiptDropInnerWrapper(true)}
                             />
                         </DragAndDropConsumer>
                     ) : (
-                        <ReceiptDropUI
-                            onDrop={(e) => {
-                                const file = e?.dataTransfer?.files[0];
-                                if (file) {
-                                    file.uri = URL.createObjectURL(file);
-                                    setReceiptOnDrop(file);
-                                }
-                            }}
-                        />
+                        <ReceiptDropUI onDrop={handleDroppingReceipt} />
                     )}
-                    <ConfirmModal
-                        title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
-                        onConfirm={hideReceiptModal}
-                        onCancel={hideReceiptModal}
-                        isVisible={isAttachmentInvalid}
-                        prompt={confirmModalPrompt}
-                        confirmText={translate('common.close')}
-                        shouldShowCancelButton={false}
-                    />
+                    {ErrorModal}
                     {!!gpsRequired && (
                         <LocationPermissionModal
                             startPermissionFlow={startLocationPermissionFlow}
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
index ae3c6faa69bc..804335e8d50a 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
@@ -1,6 +1,4 @@
 import {useFocusEffect} from '@react-navigation/core';
-import {format} from 'date-fns';
-import {Str} from 'expensify-common';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {ActivityIndicator, Alert, AppState, InteractionManager, StyleSheet, View} from 'react-native';
 import ReactNativeBlobUtil from 'react-native-blob-util';
@@ -10,7 +8,6 @@ import {RESULTS} from 'react-native-permissions';
 import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withDelay, withSequence, withSpring, withTiming} from 'react-native-reanimated';
 import type {Camera, PhotoFile, Point} from 'react-native-vision-camera';
 import {useCameraDevice} from 'react-native-vision-camera';
-import type {TupleToUnion} from 'type-fest';
 import MultiScan from '@assets/images/educational-illustration__multi-scan.svg';
 import TestReceipt from '@assets/images/fake-receipt.png';
 import Hand from '@assets/images/hand.svg';
@@ -23,10 +20,10 @@ import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
 import ImageSVG from '@components/ImageSVG';
 import LocationPermissionModal from '@components/LocationPermissionModal';
-import PDFThumbnail from '@components/PDFThumbnail';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Text from '@components/Text';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
+import useFilesValidation from '@hooks/useFilesValidation';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import usePermissions from '@hooks/usePermissions';
@@ -35,14 +32,13 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import setTestReceipt from '@libs/actions/setTestReceipt';
 import {dismissProductTraining} from '@libs/actions/Welcome';
-import {readFileAsync, resizeImageIfNeeded, showCameraPermissionsAlert, splitExtensionFromFileName} from '@libs/fileDownload/FileUtils';
+import {readFileAsync, showCameraPermissionsAlert} from '@libs/fileDownload/FileUtils';
 import getPhotoSource from '@libs/fileDownload/getPhotoSource';
-import convertHeicImage from '@libs/fileDownload/heicConverter';
 import getCurrentPosition from '@libs/getCurrentPosition';
 import getPlatform from '@libs/getPlatform';
 import getReceiptsUploadFolderPath from '@libs/getReceiptsUploadFolderPath';
 import HapticFeedback from '@libs/HapticFeedback';
-import {formatCurrentUserToAttendee, navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
+import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
 import Log from '@libs/Log';
 import Navigation from '@libs/Navigation/Navigation';
 import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
@@ -66,8 +62,7 @@ import {
     updateLastLocationPermissionPrompt,
 } from '@userActions/IOU';
 import type {GpsPoint} from '@userActions/IOU';
-import {generateTransactionID} from '@userActions/Transaction';
-import {createDraftTransaction, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
+import {buildOptimisticTransactionAndCreateDraft, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -122,8 +117,6 @@ function IOURequestStepScan({
     const [shouldShowMultiScanEducationalPopup, setShouldShowMultiScanEducationalPopup] = useState(false);
     const [cameraKey, setCameraKey] = useState(0);
 
-    const [pdfFile, setPdfFile] = useState<null | FileObject>(null);
-
     const defaultTaxCode = getDefaultTaxCode(policy, initialTransaction);
     const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? '';
     const transactionTaxAmount = initialTransaction?.taxAmount ?? 0;
@@ -256,32 +249,6 @@ function IOURequestStepScan({
         setReceiptFiles([]);
     }, [isMultiScanEnabled]);
 
-    const validateReceipt = (file: FileObject) => {
-        const {fileExtension} = splitExtensionFromFileName(file?.name ?? '');
-        if (
-            !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(
-                fileExtension.toLowerCase() as TupleToUnion<typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS>,
-            )
-        ) {
-            Alert.alert(translate('attachmentPicker.wrongFileType'), translate('attachmentPicker.notAllowedExtension'));
-            return false;
-        }
-
-        if (!Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE) {
-            Alert.alert(
-                translate('attachmentPicker.attachmentTooLarge'),
-                translate('attachmentPicker.sizeExceededWithLimit', {maxUploadSizeInMB: CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE / (1024 * 1024)}),
-            );
-            return false;
-        }
-
-        if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
-            Alert.alert(translate('attachmentPicker.attachmentTooSmall'), translate('attachmentPicker.sizeNotMet'));
-            return false;
-        }
-        return true;
-    };
-
     const navigateBack = () => {
         Navigation.goBack();
     };
@@ -312,7 +279,15 @@ function IOURequestStepScan({
     );
 
     const createTransaction = useCallback(
-        (files: ReceiptFile[], participant: Participant, gpsPoints?: GpsPoint, policyParams?: {policy: OnyxEntry<Policy>}, billable?: boolean) => {
+        (
+            files: ReceiptFile[],
+            participant: Participant,
+            gpsPoints?: GpsPoint,
+            policyParams?: {
+                policy: OnyxEntry<Policy>;
+            },
+            billable?: boolean,
+        ) => {
             files.forEach((receiptFile: ReceiptFile, index) => {
                 const transaction = transactions.find((item) => item.transactionID === receiptFile.transactionID);
                 const receipt: Receipt = receiptFile.file ?? {};
@@ -366,25 +341,6 @@ function IOURequestStepScan({
         [backToReport, currentUserPersonalDetails.accountID, currentUserPersonalDetails.login, iouType, report, transactions],
     );
 
-    const buildOptimisticTransaction = useCallback((): Transaction => {
-        const newTransactionID = generateTransactionID();
-        const {currency, iouRequestType, isFromGlobalCreate, splitPayerAccountIDs} = initialTransaction ?? {};
-        const newTransaction = {
-            amount: 0,
-            created: format(new Date(), 'yyyy-MM-dd'),
-            currency,
-            comment: {attendees: formatCurrentUserToAttendee(currentUserPersonalDetails, reportID)},
-            iouRequestType,
-            reportID,
-            transactionID: newTransactionID,
-            isFromGlobalCreate,
-            merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
-            splitPayerAccountIDs,
-        } as Transaction;
-        createDraftTransaction(newTransaction);
-        return newTransaction;
-    }, [currentUserPersonalDetails, initialTransaction, reportID]);
-
     const navigateToConfirmationStep = useCallback(
         (files: ReceiptFile[], locationPermissionGranted = false, isTestTransaction = false) => {
             if (backTo) {
@@ -398,7 +354,17 @@ function IOURequestStepScan({
                 if (!managerMcTestParticipant.reportID && report?.reportID) {
                     reportIDParam = generateReportID();
                 }
-                setMoneyRequestParticipants(initialTransactionID, [{...managerMcTestParticipant, reportID: reportIDParam, selected: true}], true).then(() => {
+                setMoneyRequestParticipants(
+                    initialTransactionID,
+                    [
+                        {
+                            ...managerMcTestParticipant,
+                            reportID: reportIDParam,
+                            selected: true,
+                        },
+                    ],
+                    true,
+                ).then(() => {
                     navigateToConfirmationPage(true, reportIDParam);
                 });
                 return;
@@ -536,22 +502,6 @@ function IOURequestStepScan({
         });
     }, [initialTransactionID, isEditing, navigateToConfirmationStep]);
 
-    /**
-     * Converts HEIC image to JPEG using promises
-     */
-    const convertHeicImageToJpegPromise = (file: FileObject): Promise<FileObject> => {
-        return new Promise((resolve, reject) => {
-            convertHeicImage(file, {
-                onStart: () => setIsLoaderVisible(true),
-                onSuccess: (convertedFile) => resolve(convertedFile),
-                onError: (nonConvertedFile) => {
-                    reject(nonConvertedFile);
-                },
-                onFinish: () => setIsLoaderVisible(false),
-            });
-        });
-    };
-
     const dismissMultiScanEducationalPopup = () => {
         InteractionManager.runAfterInteractions(() => {
             dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.MULTI_SCAN_EDUCATIONAL_MODAL);
@@ -562,69 +512,57 @@ function IOURequestStepScan({
     /**
      * Sets the Receipt objects and navigates the user to the next page
      */
-    const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
-        if (!validateReceipt(originalFile)) {
+    const setReceiptFilesAndNavigate = (files: FileObject[]) => {
+        if (files.length === 0) {
             return;
         }
+        // Store the receipt on the transaction object in Onyx
+        const newReceiptFiles: ReceiptFile[] = [];
 
-        // If we have a pdf file and if it is not validated then set the pdf file for validation and return
-        if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
-            setPdfFile(originalFile);
+        if (isEditing) {
+            const file = files.at(0);
+            if (!file) {
+                return;
+            }
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+            setMoneyRequestReceipt(initialTransactionID, file.uri || '', file.name || '', !isEditing);
+            updateScanAndNavigate(file, file.uri ?? '');
             return;
         }
 
-        // Helper function to process the file after any conversion
-        const processFile = (file: FileObject) => {
-            resizeImageIfNeeded(file).then((resizedFile) => {
-                // Store the receipt on the transaction object in Onyx
-                // On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file.
-                // So, let us also save the file type in receipt for later use during blob fetch
-                setMoneyRequestReceipt(initialTransactionID, resizedFile?.uri ?? '', resizedFile.name ?? '', !isEditing, resizedFile.type);
-
-                if (isEditing) {
-                    updateScanAndNavigate(resizedFile, resizedFile?.uri ?? '');
-                    return;
-                }
+        files.forEach((file, index) => {
+            const transaction =
+                !isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP) || (index === 0 && transactions.length === 1 && !initialTransaction?.receipt)
+                    ? (initialTransaction as Partial<Transaction>)
+                    : buildOptimisticTransactionAndCreateDraft({
+                          initialTransaction: initialTransaction as Partial<Transaction>,
+                          currentUserPersonalDetails,
+                          reportID,
+                      });
+
+            const transactionID = transaction.transactionID ?? initialTransactionID;
+            newReceiptFiles.push({file, source: file.uri ?? '', transactionID});
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+            setMoneyRequestReceipt(transactionID, file.uri ?? '', file.name || '', true);
+        });
 
-                const newReceiptFiles = [{file: resizedFile, source: resizedFile?.uri ?? '', transactionID: initialTransactionID}];
+        if (shouldSkipConfirmation) {
+            setReceiptFiles(newReceiptFiles);
+            const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length;
+            if (gpsRequired) {
+                const beginLocationPermissionFlow = shouldStartLocationPermissionFlow();
 
-                if (shouldSkipConfirmation) {
-                    setReceiptFiles(newReceiptFiles);
-                    const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && resizedFile;
-
-                    if (gpsRequired) {
-                        const beginLocationPermissionFlow = shouldStartLocationPermissionFlow();
-                        if (beginLocationPermissionFlow) {
-                            setStartLocationPermissionFlow(true);
-                            return;
-                        }
-                    }
+                if (beginLocationPermissionFlow) {
+                    setStartLocationPermissionFlow(true);
+                    return;
                 }
-                navigateToConfirmationStep(newReceiptFiles, false);
-            });
-        };
-
-        // Check if the file is HEIC/HEIF and needs conversion
-        if (
-            originalFile?.type?.startsWith('image') &&
-            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-            (originalFile.name?.toLowerCase().endsWith('.heic') || originalFile.name?.toLowerCase().endsWith('.heif'))
-        ) {
-            convertHeicImageToJpegPromise(originalFile)
-                .then((convertedFile) => {
-                    processFile(convertedFile);
-                })
-                .catch((fallbackFile: FileObject) => {
-                    // Use the original file if conversion fails
-                    processFile(fallbackFile);
-                });
-            return;
+            }
         }
-
-        // Process the file directly if no conversion is needed
-        processFile(originalFile);
+        navigateToConfirmationStep(newReceiptFiles, false);
     };
 
+    const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(setReceiptFilesAndNavigate);
+
     const submitReceipts = useCallback(
         (files: ReceiptFile[]) => {
             if (shouldSkipConfirmation) {
@@ -692,7 +630,14 @@ function IOURequestStepScan({
                     .then((photo: PhotoFile) => {
                         // Store the receipt on the transaction object in Onyx
                         const source = getPhotoSource(photo.path);
-                        const transaction = isMultiScanEnabled && initialTransaction?.receipt?.source ? buildOptimisticTransaction() : initialTransaction;
+                        const transaction =
+                            isMultiScanEnabled && initialTransaction?.receipt?.source
+                                ? buildOptimisticTransactionAndCreateDraft({
+                                      initialTransaction,
+                                      currentUserPersonalDetails,
+                                      reportID,
+                                  })
+                                : initialTransaction;
                         const transactionID = transaction?.transactionID ?? initialTransactionID;
 
                         setMoneyRequestReceipt(transactionID, source, photo.path, !isEditing);
@@ -738,12 +683,13 @@ function IOURequestStepScan({
         flash,
         hasFlash,
         isPlatformMuted,
-        submitReceipts,
-        receiptFiles,
-        buildOptimisticTransaction,
         initialTransaction,
+        currentUserPersonalDetails,
+        reportID,
         initialTransactionID,
         isEditing,
+        receiptFiles,
+        submitReceipts,
         updateScanAndNavigate,
     ]);
 
@@ -780,26 +726,7 @@ function IOURequestStepScan({
                     onLayout(setTestReceiptAndNavigate);
                 }}
             >
-                {!!pdfFile && (
-                    <PDFThumbnail
-                        style={styles.invisiblePDF}
-                        previewSourceURL={pdfFile?.uri ?? ''}
-                        onLoadSuccess={() => {
-                            setPdfFile(null);
-                            if (pdfFile) {
-                                setReceiptAndNavigate(pdfFile, true);
-                            }
-                        }}
-                        onPassword={() => {
-                            setPdfFile(null);
-                            Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.protectedPDFNotSupported'));
-                        }}
-                        onLoadError={() => {
-                            setPdfFile(null);
-                            Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment'));
-                        }}
-                    />
-                )}
+                {PDFValidationComponent}
                 <View style={[styles.flex1]}>
                     {cameraPermissionStatus !== RESULTS.GRANTED && (
                         <View style={[styles.cameraView, styles.permissionView, styles.userSelectNone]}>
@@ -888,7 +815,10 @@ function IOURequestStepScan({
                     />
                 )}
                 <View style={[styles.flexRow, styles.justifyContentAround, styles.alignItemsCenter, styles.pv3]}>
-                    <AttachmentPicker onOpenPicker={() => setIsLoaderVisible(true)}>
+                    <AttachmentPicker
+                        onOpenPicker={() => setIsLoaderVisible(true)}
+                        fileLimit={isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP) ? CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT : 1}
+                    >
                         {({openPicker}) => (
                             <PressableWithFeedback
                                 role={CONST.ROLE.BUTTON}
@@ -896,7 +826,7 @@ function IOURequestStepScan({
                                 style={[styles.alignItemsStart, isMultiScanEnabled && styles.opacity0]}
                                 onPress={() => {
                                     openPicker({
-                                        onPicked: (data) => setReceiptAndNavigate(data.at(0) ?? {}),
+                                        onPicked: (data) => validateFiles(data),
                                         onCanceled: () => setIsLoaderVisible(false),
                                         // makes sure the loader is not visible anymore e.g. when there is an error while uploading a file
                                         onClosed: () => {
@@ -978,6 +908,7 @@ function IOURequestStepScan({
                         }}
                     />
                 )}
+                {ErrorModal}
             </View>
         </StepScreenWrapper>
     );
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
index c79ef1a3fad7..51c0796482c9 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
@@ -1,6 +1,4 @@
 import {useIsFocused} from '@react-navigation/native';
-import {format} from 'date-fns';
-import {Str} from 'expensify-common';
 import React, {useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState} from 'react';
 import {ActivityIndicator, InteractionManager, PanResponder, PixelRatio, StyleSheet, View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
@@ -14,22 +12,20 @@ import ReceiptUpload from '@assets/images/receipt-upload.svg';
 import Shutter from '@assets/images/shutter.svg';
 import AttachmentPicker from '@components/AttachmentPicker';
 import Button from '@components/Button';
-import ConfirmModal from '@components/ConfirmModal';
 import CopyTextToClipboard from '@components/CopyTextToClipboard';
 import DownloadAppBanner from '@components/DownloadAppBanner';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
 import {DragAndDropContext} from '@components/DragAndDrop/Provider';
 import DropZoneUI from '@components/DropZone/DropZoneUI';
 import FeatureTrainingModal from '@components/FeatureTrainingModal';
-import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
 import LocationPermissionModal from '@components/LocationPermissionModal';
-import PDFThumbnail from '@components/PDFThumbnail';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Text from '@components/Text';
 import TextLink from '@components/TextLink';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
+import useFilesValidation from '@hooks/useFilesValidation';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import usePermissions from '@hooks/usePermissions';
@@ -41,17 +37,16 @@ import setTestReceipt from '@libs/actions/setTestReceipt';
 import {clearUserLocation, setUserLocation} from '@libs/actions/UserLocation';
 import {dismissProductTraining} from '@libs/actions/Welcome';
 import {isMobile, isMobileWebKit} from '@libs/Browser';
-import {base64ToFile, isLocalFile as isLocalFileFileUtils, resizeImageIfNeeded, validateReceipt} from '@libs/fileDownload/FileUtils';
-import convertHeicImage from '@libs/fileDownload/heicConverter';
+import {base64ToFile, isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils';
 import getCurrentPosition from '@libs/getCurrentPosition';
-import {formatCurrentUserToAttendee, navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
+import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
 import Log from '@libs/Log';
 import Navigation from '@libs/Navigation/Navigation';
 import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
 import {isPaidGroupPolicy} from '@libs/PolicyUtils';
 import {generateReportID, getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils';
 import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
-import {getDefaultTaxCode} from '@libs/TransactionUtils';
+import {getDefaultTaxCode, hasReceipt} from '@libs/TransactionUtils';
 import ReceiptDropUI from '@pages/iou/ReceiptDropUI';
 import StepScreenDragAndDropWrapper from '@pages/iou/request/step/StepScreenDragAndDropWrapper';
 import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound';
@@ -70,10 +65,8 @@ import {
     trackExpense,
     updateLastLocationPermissionPrompt,
 } from '@userActions/IOU';
-import {generateTransactionID} from '@userActions/Transaction';
-import {createDraftTransaction, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
+import {buildOptimisticTransactionAndCreateDraft, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
 import CONST from '@src/CONST';
-import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type {Policy} from '@src/types/onyx';
@@ -102,11 +95,6 @@ function IOURequestStepScan({
     const styles = useThemeStyles();
     const {isBetaEnabled} = usePermissions();
 
-    // Grouping related states
-    const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
-    const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths>();
-    const [attachmentInvalidReason, setAttachmentValidReason] = useState<TranslationPaths>();
-    const [pdfFile, setPdfFile] = useState<null | FileObject>(null);
     const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
     const [receiptFiles, setReceiptFiles] = useState<ReceiptFile[]>([]);
     const [receiptImageTopPosition, setReceiptImageTopPosition] = useState(0);
@@ -131,9 +119,9 @@ function IOURequestStepScan({
     const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false});
     const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true});
     const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true});
-    const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
     const isEditing = action === CONST.IOU.ACTION.EDIT;
     const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
+    const isReplacingReceipt = isEditing && hasReceipt(initialTransaction);
 
     const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {
         selector: (items) => Object.values(items ?? {}),
@@ -151,6 +139,9 @@ function IOURequestStepScan({
     const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? '';
     const transactionTaxAmount = initialTransaction?.taxAmount ?? 0;
 
+    const canUseMultiDragAndDrop = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP);
+    const shouldAcceptMultipleFiles = canUseMultiDragAndDrop && !isEditing && !backTo;
+
     const blinkOpacity = useSharedValue(0);
     const blinkStyle = useAnimatedStyle(() => ({
         opacity: blinkOpacity.get(),
@@ -321,20 +312,6 @@ function IOURequestStepScan({
         setReceiptFiles([]);
     }, [isMultiScanEnabled]);
 
-    const hideReceiptModal = () => {
-        setIsAttachmentInvalid(false);
-    };
-
-    /**
-     * Sets the upload receipt error modal content when an invalid receipt is uploaded
-     */
-    const setUploadReceiptError = (isInvalid: boolean, title: TranslationPaths, reason: TranslationPaths) => {
-        setIsAttachmentInvalid(isInvalid);
-        setAttachmentInvalidReasonTitle(title);
-        setAttachmentValidReason(reason);
-        setPdfFile(null);
-    };
-
     const navigateBack = useCallback(() => {
         Navigation.goBack(backTo);
     }, [backTo]);
@@ -364,27 +341,16 @@ function IOURequestStepScan({
         [backToReport, iouType, reportID, initialTransactionID],
     );
 
-    const buildOptimisticTransaction = useCallback((): Transaction => {
-        const newTransactionID = generateTransactionID();
-        const {currency, iouRequestType, isFromGlobalCreate, splitPayerAccountIDs} = initialTransaction ?? {};
-        const newTransaction = {
-            amount: 0,
-            created: format(new Date(), 'yyyy-MM-dd'),
-            currency,
-            comment: {attendees: formatCurrentUserToAttendee(currentUserPersonalDetails, reportID)},
-            iouRequestType,
-            reportID,
-            transactionID: newTransactionID,
-            isFromGlobalCreate,
-            merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
-            splitPayerAccountIDs,
-        } as Transaction;
-        createDraftTransaction(newTransaction);
-        return newTransaction;
-    }, [currentUserPersonalDetails, initialTransaction, reportID]);
-
     const createTransaction = useCallback(
-        (files: ReceiptFile[], participant: Participant, gpsPoints?: GpsPoint, policyParams?: {policy: OnyxEntry<Policy>}, billable?: boolean) => {
+        (
+            files: ReceiptFile[],
+            participant: Participant,
+            gpsPoints?: GpsPoint,
+            policyParams?: {
+                policy: OnyxEntry<Policy>;
+            },
+            billable?: boolean,
+        ) => {
             files.forEach((receiptFile: ReceiptFile, index) => {
                 const transaction = transactions.find((item) => item.transactionID === receiptFile.transactionID);
                 const receipt: Receipt = receiptFile.file ?? {};
@@ -585,92 +551,70 @@ function IOURequestStepScan({
         [initialTransactionID, navigateBack],
     );
 
-    /**
-     * Converts HEIC image to JPEG using promises
-     */
-    const convertHeicImageToJpegPromise = (file: FileObject): Promise<FileObject> => {
-        return new Promise((resolve, reject) => {
-            convertHeicImage(file, {
-                onStart: () => setIsLoadingReceipt(true),
-                onSuccess: (convertedFile) => resolve(convertedFile),
-                onError: (nonConvertedFile) => {
-                    reject(nonConvertedFile);
-                },
-                onFinish: () => setIsLoadingReceipt(false),
-            });
-        });
-    };
+    const setReceiptFilesAndNavigate = (files: FileObject[]) => {
+        if (files.length === 0) {
+            return;
+        }
+        // Store the receipt on the transaction object in Onyx
+        const newReceiptFiles: ReceiptFile[] = [];
 
-    /**
-     * Sets the Receipt objects and navigates the user to the next page
-     */
-    const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
-        validateReceipt(originalFile, setUploadReceiptError).then((isFileValid) => {
-            if (!isFileValid) {
+        if (isEditing) {
+            const file = files.at(0);
+            if (!file) {
                 return;
             }
+            const source = URL.createObjectURL(file as Blob);
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+            setMoneyRequestReceipt(initialTransactionID, source, file.name || '', !isEditing);
+            updateScanAndNavigate(file, source);
+            return;
+        }
 
-            // If we have a pdf file and if it is not validated then set the pdf file for validation and return
-            if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
-                setPdfFile(originalFile);
-                return;
-            }
+        files.forEach((file, index) => {
+            const source = URL.createObjectURL(file as Blob);
+            const transaction =
+                !isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP) || (index === 0 && transactions.length === 1 && !initialTransaction?.receipt)
+                    ? (initialTransaction as Partial<Transaction>)
+                    : buildOptimisticTransactionAndCreateDraft({
+                          initialTransaction: initialTransaction as Partial<Transaction>,
+                          currentUserPersonalDetails,
+                          reportID,
+                      });
+
+            const transactionID = transaction.transactionID ?? initialTransactionID;
+            newReceiptFiles.push({file, source, transactionID});
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+            setMoneyRequestReceipt(transactionID, source, file.name || '', true);
+        });
 
-            // Helper function to process the file after any conversion
-            const processFile = (file: FileObject) => {
-                // With the image size > 24MB, we use manipulateAsync to resize the image.
-                // It takes a long time so we should display a loading indicator while the resize image progresses.
-                if (Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
-                    setIsLoadingReceipt(true);
+        if (shouldSkipConfirmation) {
+            setReceiptFiles(newReceiptFiles);
+            const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length;
+            if (gpsRequired) {
+                const beginLocationPermissionFlow = shouldStartLocationPermissionFlow();
+
+                if (beginLocationPermissionFlow) {
+                    setStartLocationPermissionFlow(true);
+                    return;
                 }
-                resizeImageIfNeeded(file).then((resizedFile) => {
-                    setIsLoadingReceipt(false);
-                    // Store the receipt on the transaction object in Onyx
-                    const source = URL.createObjectURL(resizedFile as Blob);
-                    const newReceiptFiles = [{file: resizedFile, source, transactionID: initialTransactionID}];
-                    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-                    setMoneyRequestReceipt(initialTransactionID, source, resizedFile.name || '', !isEditing);
-
-                    if (isEditing) {
-                        updateScanAndNavigate(resizedFile, source);
-                        return;
-                    }
-                    if (shouldSkipConfirmation) {
-                        setReceiptFiles(newReceiptFiles);
-                        const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && resizedFile;
-                        if (gpsRequired) {
-                            const beginLocationPermissionFlow = shouldStartLocationPermissionFlow();
-
-                            if (beginLocationPermissionFlow) {
-                                setStartLocationPermissionFlow(true);
-                                return;
-                            }
-                        }
-                    }
-                    navigateToConfirmationStep(newReceiptFiles, false);
-                });
-            };
-
-            // Check if the file is HEIC/HEIF and needs conversion
-            if (
-                originalFile?.type?.startsWith('image') &&
-                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-                (originalFile.name?.toLowerCase().endsWith('.heic') || originalFile.name?.toLowerCase().endsWith('.heif'))
-            ) {
-                convertHeicImageToJpegPromise(originalFile)
-                    .then((convertedFile) => {
-                        processFile(convertedFile);
-                    })
-                    .catch((fallbackFile: FileObject) => {
-                        // Use the original file if conversion fails
-                        processFile(fallbackFile);
-                    });
-                return;
             }
+        }
+        navigateToConfirmationStep(newReceiptFiles, false);
+    };
 
-            // Process the file directly if no conversion is needed
-            processFile(originalFile);
+    const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(setReceiptFilesAndNavigate);
+
+    const handleDropReceipt = (e: DragEvent) => {
+        const files = Array.from(e?.dataTransfer?.files ?? []);
+        if (files.length === 0) {
+            return;
+        }
+        files.forEach((file) => {
+            // eslint-disable-next-line no-param-reassign
+            file.uri = URL.createObjectURL(file);
         });
+
+        validateFiles(files);
     };
 
     /**
@@ -731,7 +675,14 @@ function IOURequestStepScan({
         const filename = `receipt_${Date.now()}.png`;
         const file = base64ToFile(imageBase64 ?? '', filename);
         const source = URL.createObjectURL(file);
-        const transaction = isMultiScanEnabled && initialTransaction?.receipt?.source ? buildOptimisticTransaction() : initialTransaction;
+        const transaction =
+            isMultiScanEnabled && initialTransaction?.receipt?.source
+                ? buildOptimisticTransactionAndCreateDraft({
+                      initialTransaction,
+                      currentUserPersonalDetails,
+                      reportID,
+                  })
+                : initialTransaction;
         const transactionID = transaction?.transactionID ?? initialTransactionID;
         const newReceiptFiles = [...receiptFiles, {file, source, transactionID}];
 
@@ -749,15 +700,16 @@ function IOURequestStepScan({
 
         submitReceipts(newReceiptFiles);
     }, [
-        receiptFiles,
-        showBlink,
-        buildOptimisticTransaction,
+        isMultiScanEnabled,
         initialTransaction,
+        currentUserPersonalDetails,
+        reportID,
         initialTransactionID,
+        receiptFiles,
         isEditing,
-        isMultiScanEnabled,
         submitReceipts,
         requestCameraPermission,
+        showBlink,
         updateScanAndNavigate,
     ]);
 
@@ -815,33 +767,6 @@ function IOURequestStepScan({
         [],
     );
 
-    const PDFThumbnailView = pdfFile ? (
-        <PDFThumbnail
-            style={styles.invisiblePDF}
-            previewSourceURL={pdfFile.uri ?? ''}
-            onLoadSuccess={() => {
-                setPdfFile(null);
-                setReceiptAndNavigate(pdfFile, true);
-            }}
-            onPassword={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported');
-            }}
-            onLoadError={() => {
-                setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment');
-            }}
-        />
-    ) : null;
-
-    const getConfirmModalPrompt = () => {
-        if (!attachmentInvalidReason) {
-            return '';
-        }
-        if (attachmentInvalidReason === 'attachmentPicker.sizeExceededWithLimit') {
-            return translate(attachmentInvalidReason, {maxUploadSizeInMB: CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE / (1024 * 1024)});
-        }
-        return translate(attachmentInvalidReason);
-    };
-
     const dismissMultiScanEducationalPopup = () => {
         InteractionManager.runAfterInteractions(() => {
             dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.MULTI_SCAN_EDUCATIONAL_MODAL);
@@ -852,7 +777,7 @@ function IOURequestStepScan({
     const mobileCameraView = () => (
         <>
             <View style={[styles.cameraView]}>
-                {PDFThumbnailView}
+                {PDFValidationComponent}
                 {((cameraPermissionState === 'prompt' && !isQueriedPermissionState) || (cameraPermissionState === 'granted' && isEmptyObject(videoConstraints))) && (
                     <ActivityIndicator
                         size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
@@ -931,15 +856,18 @@ function IOURequestStepScan({
             </View>
 
             <View style={[styles.flexRow, styles.justifyContentAround, styles.alignItemsCenter, styles.pv3]}>
-                <AttachmentPicker acceptedFileTypes={[...CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS]}>
+                <AttachmentPicker
+                    acceptedFileTypes={[...CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS]}
+                    allowMultiple={shouldAcceptMultipleFiles}
+                >
                     {({openPicker}) => (
                         <PressableWithFeedback
-                            accessibilityLabel={translate('common.chooseFile')}
+                            accessibilityLabel={translate(shouldAcceptMultipleFiles ? 'common.chooseFiles' : 'common.chooseFile')}
                             role={CONST.ROLE.BUTTON}
                             style={isMultiScanEnabled && styles.opacity0}
                             onPress={() => {
                                 openPicker({
-                                    onPicked: (data) => setReceiptAndNavigate(data.at(0) ?? {}),
+                                    onPicked: (data) => validateFiles(data),
                                 });
                             }}
                         >
@@ -1024,7 +952,7 @@ function IOURequestStepScan({
 
     const desktopUploadView = () => (
         <>
-            {PDFThumbnailView}
+            {PDFValidationComponent}
             <View onLayout={({nativeEvent}) => setReceiptImageTopPosition(PixelRatio.roundToNearestPixel((nativeEvent.layout as DOMRect).top))}>
                 <ReceiptUpload
                     width={CONST.RECEIPT.ICON_SIZE}
@@ -1037,27 +965,29 @@ function IOURequestStepScan({
                 // eslint-disable-next-line react/jsx-props-no-spreading
                 {...panResponder.panHandlers}
             >
-                <Text style={[styles.textFileUpload]}>{translate('receipt.upload')}</Text>
+                <Text style={[styles.textFileUpload]}>{translate(shouldAcceptMultipleFiles ? 'receipt.uploadMultiple' : 'receipt.upload')}</Text>
                 <Text style={[styles.subTextFileUpload]}>
-                    {isSmallScreenWidth ? translate('receipt.chooseReceipt') : translate('receipt.dragReceiptBeforeEmail')}
+                    {isSmallScreenWidth
+                        ? translate(shouldAcceptMultipleFiles ? 'receipt.chooseReceipts' : 'receipt.chooseReceipt')
+                        : translate(shouldAcceptMultipleFiles ? 'receipt.dragReceiptsBeforeEmail' : 'receipt.dragReceiptBeforeEmail')}
                     <CopyTextToClipboard
                         text={CONST.EMAIL.RECEIPTS}
                         textStyles={[styles.textBlue]}
                     />
-                    {isSmallScreenWidth ? null : translate('receipt.dragReceiptAfterEmail')}
+                    {isSmallScreenWidth ? null : translate(shouldAcceptMultipleFiles ? 'receipt.dragReceiptsAfterEmail' : 'receipt.dragReceiptAfterEmail')}
                 </Text>
             </View>
 
-            <AttachmentPicker>
+            <AttachmentPicker allowMultiple={shouldAcceptMultipleFiles}>
                 {({openPicker}) => (
                     <Button
                         success
-                        text={translate('common.chooseFile')}
-                        accessibilityLabel={translate('common.chooseFile')}
+                        text={translate(shouldAcceptMultipleFiles ? 'common.chooseFiles' : 'common.chooseFile')}
+                        accessibilityLabel={translate(shouldAcceptMultipleFiles ? 'common.chooseFiles' : 'common.chooseFile')}
                         style={[styles.p9]}
                         onPress={() => {
                             openPicker({
-                                onPicked: (data) => setReceiptAndNavigate(data.at(0) ?? {}),
+                                onPicked: (data) => validateFiles(data),
                             });
                         }}
                     />
@@ -1074,75 +1004,58 @@ function IOURequestStepScan({
             testID={IOURequestStepScan.displayName}
         >
             {(isDraggingOverWrapper) => (
-                <>
-                    {isLoadingReceipt && <FullScreenLoadingIndicator />}
-                    <View
-                        onLayout={() => {
-                            if (!onLayout) {
-                                return;
-                            }
-                            onLayout(setTestReceiptAndNavigate);
-                        }}
-                        style={[styles.flex1, !isMobile() && styles.uploadFileView(isSmallScreenWidth)]}
-                    >
-                        <View style={[styles.flex1, !isMobile() && styles.alignItemsCenter, styles.justifyContentCenter]}>
-                            {!(isDraggingOver ?? isDraggingOverWrapper) && (isMobile() ? mobileCameraView() : desktopUploadView())}
-                        </View>
-                        {/* TODO: remove beta check after the feature is enabled */}
-                        {isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_FILES_DRAG_AND_DROP) ? (
-                            <DragAndDropConsumer
-                                onDrop={(e) => {
-                                    const file = e?.dataTransfer?.files[0];
-                                    if (file) {
-                                        file.uri = URL.createObjectURL(file);
-                                        setReceiptAndNavigate(file);
-                                    }
-                                }}
-                            >
-                                <DropZoneUI
-                                    icon={isEditing ? Expensicons.ReplaceReceipt : Expensicons.SmartScan}
-                                    dropStyles={styles.receiptDropOverlay(true)}
-                                    dropTitle={isEditing ? translate('dropzone.replaceReceipt') : translate('dropzone.scanReceipts')}
-                                    dropTextStyles={styles.receiptDropText}
-                                    dropInnerWrapperStyles={styles.receiptDropInnerWrapper(true)}
-                                />
-                            </DragAndDropConsumer>
-                        ) : (
-                            <ReceiptDropUI
-                                onDrop={(e) => {
-                                    const file = e?.dataTransfer?.files[0];
-                                    if (file) {
-                                        file.uri = URL.createObjectURL(file);
-                                        setReceiptAndNavigate(file);
-                                    }
-                                }}
-                                receiptImageTopPosition={receiptImageTopPosition}
+                <View
+                    onLayout={() => {
+                        if (!onLayout) {
+                            return;
+                        }
+                        onLayout(setTestReceiptAndNavigate);
+                    }}
+                    style={[styles.flex1, !isMobile() && styles.uploadFileView(isSmallScreenWidth)]}
+                >
+                    <View style={[styles.flex1, !isMobile() && styles.alignItemsCenter, styles.justifyContentCenter]}>
+                        {!(isDraggingOver ?? isDraggingOverWrapper) && (isMobile() ? mobileCameraView() : desktopUploadView())}
+                    </View>
+                    {/* TODO: remove beta check after the feature is enabled */}
+                    {canUseMultiDragAndDrop ? (
+                        <DragAndDropConsumer onDrop={handleDropReceipt}>
+                            <DropZoneUI
+                                icon={isReplacingReceipt ? Expensicons.ReplaceReceipt : Expensicons.SmartScan}
+                                dropStyles={styles.receiptDropOverlay(true)}
+                                dropTitle={
+                                    isReplacingReceipt ? translate('dropzone.replaceReceipt') : translate(shouldAcceptMultipleFiles ? 'dropzone.scanReceipts' : 'quickAction.scanReceipt')
+                                }
+                                dropTextStyles={styles.receiptDropText}
+                                dropInnerWrapperStyles={styles.receiptDropInnerWrapper(true)}
                             />
-                        )}
-                        {/*  We use isMobile() here to explicitly hide DownloadAppBanner component on both mobile web and native apps */}
-                        {!isMobile() && <DownloadAppBanner />}
-                        <ConfirmModal
-                            title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
-                            onConfirm={hideReceiptModal}
-                            onCancel={hideReceiptModal}
-                            isVisible={isAttachmentInvalid}
-                            prompt={getConfirmModalPrompt()}
-                            confirmText={translate('common.close')}
-                            shouldShowCancelButton={false}
+                        </DragAndDropConsumer>
+                    ) : (
+                        <ReceiptDropUI
+                            onDrop={(e) => {
+                                const file = e?.dataTransfer?.files[0];
+                                if (file) {
+                                    file.uri = URL.createObjectURL(file);
+                                    validateFiles([file]);
+                                }
+                            }}
+                            receiptImageTopPosition={receiptImageTopPosition}
                         />
-                        {startLocationPermissionFlow && !!receiptFiles.length && (
-                            <LocationPermissionModal
-                                startPermissionFlow={startLocationPermissionFlow}
-                                resetPermissionFlow={() => setStartLocationPermissionFlow(false)}
-                                onGrant={() => navigateToConfirmationStep(receiptFiles, true)}
-                                onDeny={() => {
-                                    updateLastLocationPermissionPrompt();
-                                    navigateToConfirmationStep(receiptFiles, false);
-                                }}
-                            />
-                        )}
-                    </View>
-                </>
+                    )}
+                    {/*  We use isMobile() here to explicitly hide DownloadAppBanner component on both mobile web and native apps */}
+                    {!isMobile() && <DownloadAppBanner />}
+                    {ErrorModal}
+                    {startLocationPermissionFlow && !!receiptFiles.length && (
+                        <LocationPermissionModal
+                            startPermissionFlow={startLocationPermissionFlow}
+                            resetPermissionFlow={() => setStartLocationPermissionFlow(false)}
+                            onGrant={() => navigateToConfirmationStep(receiptFiles, true)}
+                            onDeny={() => {
+                                updateLastLocationPermissionPrompt();
+                                navigateToConfirmationStep(receiptFiles, false);
+                            }}
+                        />
+                    )}
+                </View>
             )}
         </StepScreenDragAndDropWrapper>
     );
