=> savedOptions;
diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts
new file mode 100644
index 0000000000..9c2826981d
--- /dev/null
+++ b/packages/core/src/js/feedback/utils.ts
@@ -0,0 +1,46 @@
+import { Alert } from 'react-native';
+
+import { isFabricEnabled, isWeb } from '../utils/environment';
+import { RN_GLOBAL_OBJ } from '../utils/worldwide';
+import { ReactNativeLibraries } from './../utils/rnlibraries';
+
+declare global {
+  // Declaring atob function to be used in web environment
+  function atob(encodedString: string): string;
+}
+
+/**
+ * Modal is not supported in React Native < 0.71 with Fabric renderer.
+ * ref: https://github.com/facebook/react-native/issues/33652
+ */
+export function isModalSupported(): boolean {
+  const { major, minor } = ReactNativeLibraries.ReactNativeVersion?.version || {};
+  return !(isFabricEnabled() && major === 0 && minor < 71);
+}
+
+export const isValidEmail = (email: string): boolean => {
+  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+  return emailRegex.test(email);
+};
+
+/**
+ * Converts base64 string to Uint8Array on the web
+ * @param base64 base64 string
+ * @returns Uint8Array data
+ */
+export const base64ToUint8Array = (base64: string): Uint8Array => {
+  if (typeof atob !== 'function' || !isWeb()) {
+    throw new Error('atob is not available in this environment.');
+  }
+
+  const binaryString = atob(base64);
+  return new Uint8Array([...binaryString].map(char => char.charCodeAt(0)));
+};
+
+export const feedbackAlertDialog = (title: string, message: string): void => {
+  if (isWeb() && typeof RN_GLOBAL_OBJ.alert !== 'undefined') {
+    RN_GLOBAL_OBJ.alert(`${title}\n${message}`);
+  } else {
+    Alert.alert(title, message);
+  }
+};
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index e7c5411613..4508684156 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -84,3 +84,6 @@ export {
 export type { TimeToDisplayProps } from './tracing';
 
 export { Mask, Unmask } from './replay/CustomMask';
+
+export { FeedbackWidget } from './feedback/FeedbackWidget';
+export { showFeedbackWidget } from './feedback/FeedbackWidgetManager';
diff --git a/packages/core/src/js/integrations/exports.ts b/packages/core/src/js/integrations/exports.ts
index dfc9e2c3e1..f5fabb397e 100644
--- a/packages/core/src/js/integrations/exports.ts
+++ b/packages/core/src/js/integrations/exports.ts
@@ -13,6 +13,7 @@ export { viewHierarchyIntegration } from './viewhierarchy';
 export { expoContextIntegration } from './expocontext';
 export { spotlightIntegration } from './spotlight';
 export { mobileReplayIntegration } from '../replay/mobilereplay';
+export { feedbackIntegration } from '../feedback/integration';
 export { browserReplayIntegration } from '../replay/browserReplay';
 export { appStartIntegration } from '../tracing/integrations/appStart';
 export { nativeFramesIntegration, createNativeFramesIntegrations } from '../tracing/integrations/nativeFrames';
diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx
index 3c6fdff90c..5edba50b48 100644
--- a/packages/core/src/js/sdk.tsx
+++ b/packages/core/src/js/sdk.tsx
@@ -8,6 +8,7 @@ import {
 import * as React from 'react';
 
 import { ReactNativeClient } from './client';
+import { FeedbackWidgetProvider } from './feedback/FeedbackWidgetManager';
 import { getDevServer } from './integrations/debugsymbolicatorutils';
 import { getDefaultIntegrations } from './integrations/default';
 import type { ReactNativeClientOptions, ReactNativeOptions, ReactNativeWrapperOptions } from './options';
@@ -163,7 +164,9 @@ export function wrap>(
     return (
       
         
-          
+          
+            
+          
         
       
     );
diff --git a/packages/core/src/js/utils/worldwide.ts b/packages/core/src/js/utils/worldwide.ts
index c1a4ae5dbb..03327bac36 100644
--- a/packages/core/src/js/utils/worldwide.ts
+++ b/packages/core/src/js/utils/worldwide.ts
@@ -25,6 +25,7 @@ export interface ReactNativeInternalGlobal extends InternalGlobal {
   __BUNDLE_START_TIME__?: number;
   nativePerformanceNow?: () => number;
   TextEncoder?: TextEncoder;
+  alert?: (message: string) => void;
 }
 
 type TextEncoder = {
diff --git a/packages/core/src/js/wrapper.ts b/packages/core/src/js/wrapper.ts
index 4db45f0855..9b37a9b87b 100644
--- a/packages/core/src/js/wrapper.ts
+++ b/packages/core/src/js/wrapper.ts
@@ -120,6 +120,8 @@ interface SentryNativeWrapper {
 
   crashedLastRun(): Promise;
   getNewScreenTimeToDisplay(): Promise;
+
+  getDataFromUri(uri: string): Promise;
 }
 
 const EOL = utf8ToBytes('\n');
@@ -702,6 +704,19 @@ export const NATIVE: SentryNativeWrapper = {
     return RNSentry.getNewScreenTimeToDisplay();
   },
 
+  async getDataFromUri(uri: string): Promise {
+    if (!this.enableNative || !this._isModuleLoaded(RNSentry)) {
+      return null;
+    }
+    try {
+      const data: number[] = await RNSentry.getDataFromUri(uri);
+      return new Uint8Array(data);
+    } catch (error) {
+      logger.error('Error:', error);
+      return null;
+    }
+  },
+
   /**
    * Gets the event from envelopeItem and applies the level filter to the selected event.
    * @param data An envelope item containing the event.
diff --git a/packages/core/test/feedback/FeedbackWidget.test.tsx b/packages/core/test/feedback/FeedbackWidget.test.tsx
new file mode 100644
index 0000000000..fb5a394fa3
--- /dev/null
+++ b/packages/core/test/feedback/FeedbackWidget.test.tsx
@@ -0,0 +1,404 @@
+import { captureFeedback } from '@sentry/core';
+import { fireEvent, render, waitFor } from '@testing-library/react-native';
+import * as React from 'react';
+import { Alert } from 'react-native';
+
+import { FeedbackWidget } from '../../src/js/feedback/FeedbackWidget';
+import type { FeedbackWidgetProps, FeedbackWidgetStyles, ImagePicker } from '../../src/js/feedback/FeedbackWidget.types';
+
+const mockOnFormClose = jest.fn();
+const mockOnAddScreenshot = jest.fn();
+const mockOnSubmitSuccess = jest.fn();
+const mockOnFormSubmitted = jest.fn();
+const mockOnSubmitError = jest.fn();
+const mockGetUser = jest.fn(() => ({
+  email: 'test@example.com',
+  name: 'Test User',
+}));
+
+jest.spyOn(Alert, 'alert');
+
+jest.mock('@sentry/core', () => ({
+  ...jest.requireActual('@sentry/core'),
+  captureFeedback: jest.fn(),
+  getCurrentScope: jest.fn(() => ({
+    getUser: mockGetUser,
+  })),
+  lastEventId: jest.fn(),
+}));
+
+const defaultProps: FeedbackWidgetProps = {
+  onFormClose: mockOnFormClose,
+  onAddScreenshot: mockOnAddScreenshot,
+  onSubmitSuccess: mockOnSubmitSuccess,
+  onFormSubmitted: mockOnFormSubmitted,
+  onSubmitError: mockOnSubmitError,
+  addScreenshotButtonLabel: 'Add Screenshot',
+  formTitle: 'Feedback Form',
+  nameLabel: 'Name Label',
+  namePlaceholder: 'Name Placeholder',
+  emailLabel: 'Email Label',
+  emailPlaceholder: 'Email Placeholder',
+  messageLabel: 'Message Label',
+  messagePlaceholder: 'Message Placeholder',
+  submitButtonLabel: 'Submit Button Label',
+  cancelButtonLabel: 'Cancel Button Label',
+  isRequiredLabel: '(is required label)',
+  errorTitle: 'Error',
+  formError: 'Please fill out all required fields.',
+  emailError: 'The email address is not valid.',
+  successMessageText: 'Feedback success',
+  genericError: 'Generic error',
+};
+
+const customStyles: FeedbackWidgetStyles = {
+  container: {
+    backgroundColor: '#ffffff',
+  },
+  title: {
+    fontSize: 20,
+    color: '#ff0000',
+  },
+  label: {
+    fontSize: 15,
+    color: '#00ff00',
+  },
+  input: {
+    height: 50,
+    borderColor: '#0000ff',
+    fontSize: 13,
+    color: '#000000',
+  },
+  textArea: {
+    height: 50,
+    color: '#00ff00',
+  },
+  submitButton: {
+    backgroundColor: '#ffff00',
+  },
+  submitText: {
+    color: '#ff0000',
+    fontSize: 12,
+  },
+  cancelButton: {
+    paddingVertical: 10,
+  },
+  cancelText: {
+    color: '#ff0000',
+    fontSize: 10,
+  },
+  screenshotButton: {
+    backgroundColor: '#00ff00',
+  },
+  screenshotText: {
+    color: '#0000ff',
+    fontSize: 13,
+  },
+};
+
+describe('FeedbackWidget', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('matches the snapshot with default configuration', () => {
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('matches the snapshot with custom texts', () => {
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('matches the snapshot with custom styles', () => {
+    const customStyleProps = {styles: customStyles};
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('matches the snapshot with default configuration and screenshot button', () => {
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('matches the snapshot with custom texts and screenshot button', () => {
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('matches the snapshot with custom styles and screenshot button', () => {
+    const customStyleProps = {styles: customStyles};
+    const { toJSON } = render();
+    expect(toJSON()).toMatchSnapshot();
+  });
+
+  it('renders correctly', () => {
+    const { getByPlaceholderText, getByText, getByTestId, queryByText } = render();
+
+    expect(getByText(defaultProps.formTitle)).toBeTruthy();
+    expect(getByTestId('sentry-logo')).toBeTruthy(); // default showBranding is true
+    expect(getByText(defaultProps.nameLabel)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.namePlaceholder)).toBeTruthy();
+    expect(getByText(defaultProps.emailLabel)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.emailPlaceholder)).toBeTruthy();
+    expect(getByText(`${defaultProps.messageLabel } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.messagePlaceholder)).toBeTruthy();
+    expect(queryByText(defaultProps.addScreenshotButtonLabel)).toBeNull(); // default false
+    expect(getByText(defaultProps.submitButtonLabel)).toBeTruthy();
+    expect(getByText(defaultProps.cancelButtonLabel)).toBeTruthy();
+  });
+
+  it('renders attachment button when the enableScreenshot is true', () => {
+    const { getByText } = render();
+
+    expect(getByText(defaultProps.addScreenshotButtonLabel)).toBeTruthy();
+  });
+
+  it('does not render the sentry logo when showBranding is false', () => {
+    const { queryByTestId } = render();
+
+    expect(queryByTestId('sentry-logo')).toBeNull();
+  });
+
+  it('name and email are prefilled when sentry user is set', () => {
+    const { getByPlaceholderText } = render();
+
+    const nameInput = getByPlaceholderText(defaultProps.namePlaceholder);
+    const emailInput = getByPlaceholderText(defaultProps.emailPlaceholder);
+
+    expect(nameInput.props.value).toBe('Test User');
+    expect(emailInput.props.value).toBe('test@example.com');
+  });
+
+  it('ensure getUser is called only after the component is rendered', () => {
+    // Ensure getUser is not called before render
+    expect(mockGetUser).not.toHaveBeenCalled();
+
+    // Render the component
+    render();
+
+    // After rendering, check that getUser was called twice (email and name)
+    expect(mockGetUser).toHaveBeenCalledTimes(2);
+  });
+
+  it('shows an error message if required fields are empty', async () => {
+    const { getByText } = render();
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.formError);
+    });
+  });
+
+  it('shows an error message if the email is not valid and the email is required', async () => {
+    const withEmailProps = {...defaultProps, ...{isEmailRequired: true}};
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'not-an-email');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.emailError);
+    });
+  });
+
+  it('calls captureFeedback when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(captureFeedback).toHaveBeenCalledWith({
+        message: 'This is a feedback message.',
+        name: 'John Doe',
+        email: 'john.doe@example.com',
+      }, undefined);
+    });
+  });
+
+  it('shows success message when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.successMessageText, '');
+    });
+  });
+
+  it('shows an error message when there is a an error in captureFeedback', async () => {
+    (captureFeedback as jest.Mock).mockImplementationOnce(() => {
+      throw new Error('Test error');
+    });
+
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.genericError);
+    });
+  });
+
+  it('calls onSubmitError when there is an error', async () => {
+    (captureFeedback as jest.Mock).mockImplementationOnce(() => {
+      throw new Error('Test error');
+    });
+
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(mockOnSubmitError).toHaveBeenCalled();
+    });
+  });
+
+  it('calls onSubmitSuccess when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(mockOnSubmitSuccess).toHaveBeenCalled();
+    });
+  });
+
+  it('calls onFormSubmitted when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(mockOnFormSubmitted).toHaveBeenCalled();
+    });
+  });
+
+  it('calls onAddScreenshot when the screenshot button is pressed and no image picker library is integrated', async () => {
+    const { getByText } = render();
+
+    fireEvent.press(getByText(defaultProps.addScreenshotButtonLabel));
+
+    await waitFor(() => {
+      expect(mockOnAddScreenshot).toHaveBeenCalled();
+    });
+  });
+
+  it('calls launchImageLibraryAsync when the expo-image-picker library is integrated', async () => {
+    const mockLaunchImageLibrary = jest.fn().mockResolvedValue({
+      assets: [{ fileName: "mock-image.jpg", uri: "file:///mock/path/image.jpg" }],
+    });
+    const mockImagePicker: jest.Mocked = {
+      launchImageLibraryAsync: mockLaunchImageLibrary,
+    };
+
+    const { getByText } = render();
+
+    fireEvent.press(getByText(defaultProps.addScreenshotButtonLabel));
+
+    await waitFor(() => {
+      expect(mockLaunchImageLibrary).toHaveBeenCalled();
+    });
+  });
+
+  it('calls launchImageLibrary when the react-native-image-picker library is integrated', async () => {
+    const mockLaunchImageLibrary = jest.fn().mockResolvedValue({
+      assets: [{ fileName: "mock-image.jpg", uri: "file:///mock/path/image.jpg" }],
+    });
+    const mockImagePicker: jest.Mocked = {
+      launchImageLibrary: mockLaunchImageLibrary,
+    };
+
+    const { getByText } = render();
+
+    fireEvent.press(getByText(defaultProps.addScreenshotButtonLabel));
+
+    await waitFor(() => {
+      expect(mockLaunchImageLibrary).toHaveBeenCalled();
+    });
+  });
+
+  it('calls onFormClose when the cancel button is pressed', () => {
+    const { getByText } = render();
+
+    fireEvent.press(getByText(defaultProps.cancelButtonLabel));
+
+    expect(mockOnFormClose).toHaveBeenCalled();
+  });
+
+  it('onUnmount the input is saved and restored when the form reopens', async () => {
+    const { getByPlaceholderText, unmount } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    unmount();
+    const { queryByPlaceholderText } = render();
+
+    expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe');
+    expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com');
+    expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.');
+  });
+  
+  it('onCancel the input is saved and restored when the form reopens', async () => {
+    const { getByPlaceholderText, getByText, unmount } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.cancelButtonLabel));
+    unmount();
+    const { queryByPlaceholderText } = render();
+
+    expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe');
+    expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com');
+    expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.');
+  });
+
+  it('onSubmit the saved input is cleared and not restored when the form reopens', async () => {
+    const { getByPlaceholderText, getByText, unmount } = render();
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+    unmount();
+    const { queryByPlaceholderText } = render();
+
+    expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('Test User');
+    expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('test@example.com');
+    expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('');
+  });
+});
diff --git a/packages/core/test/feedback/FeedbackWidgetManager.test.tsx b/packages/core/test/feedback/FeedbackWidgetManager.test.tsx
new file mode 100644
index 0000000000..2d9bfcc926
--- /dev/null
+++ b/packages/core/test/feedback/FeedbackWidgetManager.test.tsx
@@ -0,0 +1,96 @@
+import { logger } from '@sentry/core';
+import { render } from '@testing-library/react-native';
+import * as React from 'react';
+import { Text } from 'react-native';
+
+import { defaultConfiguration } from '../../src/js/feedback/defaults';
+import { FeedbackWidgetProvider, showFeedbackWidget } from '../../src/js/feedback/FeedbackWidgetManager';
+import { feedbackIntegration } from '../../src/js/feedback/integration';
+import { isModalSupported } from '../../src/js/feedback/utils';
+
+jest.mock('../../src/js/feedback/utils', () => ({
+  isModalSupported: jest.fn(),
+}));
+
+const mockedIsModalSupported = isModalSupported as jest.MockedFunction;
+
+beforeEach(() => {
+  logger.error = jest.fn();
+});
+
+describe('FeedbackWidgetManager', () => {
+  it('showFeedbackWidget displays the form when FeedbackWidgetProvider is used', () => {
+    mockedIsModalSupported.mockReturnValue(true);
+    const { getByText, getByTestId } = render(
+      
+        App Components
+      
+    );
+
+    showFeedbackWidget();
+
+    expect(getByTestId('feedback-form-modal')).toBeTruthy();
+    expect(getByText('App Components')).toBeTruthy();
+  });
+
+  it('showFeedbackWidget does not display the form when Modal is not available', () => {
+    mockedIsModalSupported.mockReturnValue(false);
+    const { getByText, queryByTestId } = render(
+      
+        App Components
+      
+    );
+
+    showFeedbackWidget();
+
+    expect(queryByTestId('feedback-form-modal')).toBeNull();
+    expect(getByText('App Components')).toBeTruthy();
+    expect(logger.error).toHaveBeenLastCalledWith(
+      'FeedbackWidget Modal is not supported in React Native < 0.71 with Fabric renderer.',
+    );
+  });
+
+  it('showFeedbackWidget does not throw an error when FeedbackWidgetProvider is not used', () => {
+    expect(() => {
+      showFeedbackWidget();
+    }).not.toThrow();
+  });
+
+  it('showFeedbackWidget displays the form with the feedbackIntegration options', () => {
+    mockedIsModalSupported.mockReturnValue(true);
+    const { getByPlaceholderText, getByText } = render(
+      
+        App Components
+      
+    );
+
+    feedbackIntegration({
+      messagePlaceholder: 'Custom Message Placeholder',
+      submitButtonLabel: 'Custom Submit Button',
+    });
+
+    showFeedbackWidget();
+
+    expect(getByPlaceholderText('Custom Message Placeholder')).toBeTruthy();
+    expect(getByText('Custom Submit Button')).toBeTruthy();
+  });
+
+  it('showFeedbackWidget displays the form with the feedbackIntegration options merged with the defaults', () => {
+    mockedIsModalSupported.mockReturnValue(true);
+    const { getByPlaceholderText, getByText, queryByText } = render(
+      
+        App Components
+      
+    );
+
+    feedbackIntegration({
+      submitButtonLabel: 'Custom Submit Button',
+    }),
+
+    showFeedbackWidget();
+
+    expect(queryByText(defaultConfiguration.submitButtonLabel)).toBeFalsy(); // overridden value
+    expect(getByText('Custom Submit Button')).toBeTruthy(); // overridden value
+    expect(getByPlaceholderText(defaultConfiguration.messagePlaceholder)).toBeTruthy(); // default configuration value
+  });
+});
diff --git a/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap b/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap
new file mode 100644
index 0000000000..9f71d72ceb
--- /dev/null
+++ b/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap
@@ -0,0 +1,1700 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FeedbackWidget matches the snapshot with custom styles 1`] = `
+
+  
+    
+      Report a Bug
+    
+    
+  
+  
+    Name
+  
+  
+  
+    Email
+  
+  
+  
+    Description
+     (required)
+  
+  
+  
+    
+      Send Bug Report
+    
+  
+  
+    
+      Cancel
+    
+  
+
+`;
+
+exports[`FeedbackWidget matches the snapshot with custom styles and screenshot button 1`] = `
+
+  
+    
+      Report a Bug
+    
+    
+  
+  
+    Name
+  
+  
+  
+    Email
+  
+  
+  
+    Description
+     (required)
+  
+  
+  
+    
+      
+        Add a screenshot
+      
+    
+  
+  
+    
+      Send Bug Report
+    
+  
+  
+    
+      Cancel
+    
+  
+
+`;
+
+exports[`FeedbackWidget matches the snapshot with custom texts 1`] = `
+
+  
+    
+      Feedback Form
+    
+    
+  
+  
+    Name Label
+  
+  
+  
+    Email Label
+  
+  
+  
+    Message Label
+     (is required label)
+  
+  
+  
+    
+      Submit Button Label
+    
+  
+  
+    
+      Cancel Button Label
+    
+  
+
+`;
+
+exports[`FeedbackWidget matches the snapshot with custom texts and screenshot button 1`] = `
+
+  
+    
+      Feedback Form
+    
+    
+  
+  
+    Name Label
+  
+  
+  
+    Email Label
+  
+  
+  
+    Message Label
+     (is required label)
+  
+  
+  
+    
+      
+        Add Screenshot
+      
+    
+  
+  
+    
+      Submit Button Label
+    
+  
+  
+    
+      Cancel Button Label
+    
+  
+
+`;
+
+exports[`FeedbackWidget matches the snapshot with default configuration 1`] = `
+
+  
+    
+      Report a Bug
+    
+    
+  
+  
+    Name
+  
+  
+  
+    Email
+  
+  
+  
+    Description
+     (required)
+  
+  
+  
+    
+      Send Bug Report
+    
+  
+  
+    
+      Cancel
+    
+  
+
+`;
+
+exports[`FeedbackWidget matches the snapshot with default configuration and screenshot button 1`] = `
+
+  
+    
+      Report a Bug
+    
+    
+  
+  
+    Name
+  
+  
+  
+    Email
+  
+  
+  
+    Description
+     (required)
+  
+  
+  
+    
+      
+        Add a screenshot
+      
+    
+  
+  
+    
+      Send Bug Report
+    
+  
+  
+    
+      Cancel
+    
+  
+
+`;
diff --git a/packages/core/test/mockWrapper.ts b/packages/core/test/mockWrapper.ts
index 82b2b9194c..fe6a611394 100644
--- a/packages/core/test/mockWrapper.ts
+++ b/packages/core/test/mockWrapper.ts
@@ -59,6 +59,7 @@ const NATIVE: MockInterface = {
 
   crashedLastRun: jest.fn(),
   getNewScreenTimeToDisplay: jest.fn().mockResolvedValue(42),
+  getDataFromUri: jest.fn(),
 };
 
 NATIVE.isNativeAvailable.mockReturnValue(true);
diff --git a/samples/expo/app.json b/samples/expo/app.json
index 694ba5382f..1f1c89980d 100644
--- a/samples/expo/app.json
+++ b/samples/expo/app.json
@@ -54,6 +54,12 @@
           }
         }
       ],
+      [
+        "expo-image-picker",
+        {
+          "photosPermission": "The app accesses your photos to let you share them with your friends."
+        }
+      ],
       [
         "expo-router",
         {
diff --git a/samples/expo/app/(tabs)/index.tsx b/samples/expo/app/(tabs)/index.tsx
index c6c5cc0b5a..947b22e471 100644
--- a/samples/expo/app/(tabs)/index.tsx
+++ b/samples/expo/app/(tabs)/index.tsx
@@ -58,6 +58,12 @@ export default function TabOneScreen() {
           Sentry.nativeCrash();
         }}
       />
+