diff --git a/packages/core/admin/admin/src/assets/images/icon_offline-cloud.svg b/packages/core/admin/admin/src/assets/images/icon_offline-cloud.svg
new file mode 100644
index 0000000000..bed9d93484
--- /dev/null
+++ b/packages/core/admin/admin/src/assets/images/icon_offline-cloud.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js
index e81ac5bb07..c8dc356f04 100644
--- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js
@@ -269,7 +269,7 @@ const EditViewDataManagerProvider = ({
   );
 
   const createFormData = useCallback(
-    (data) => {
+    data => {
       // First we need to remove the added keys needed for the dnd
       const preparedData = removeKeyInObject(cloneDeep(data), '__temp_key__');
       // Then we need to apply our helper
@@ -293,7 +293,7 @@ const EditViewDataManagerProvider = ({
   }, [hasDraftAndPublish, shouldNotRunValidations]);
 
   const handleSubmit = useCallback(
-    async (e) => {
+    async e => {
       e.preventDefault();
       let errors = {};
 
@@ -363,8 +363,8 @@ const EditViewDataManagerProvider = ({
   }, [allLayoutData, currentContentTypeLayout, isCreatingEntry, modifiedData, onPublish]);
 
   const shouldCheckDZErrors = useCallback(
-    (dzName) => {
-      const doesDZHaveError = Object.keys(formErrors).some((key) => key.split('.')[0] === dzName);
+    dzName => {
+      const doesDZHaveError = Object.keys(formErrors).some(key => key.split('.')[0] === dzName);
       const shouldCheckErrors = !isEmpty(formErrors) && doesDZHaveError;
 
       return shouldCheckErrors;
@@ -418,7 +418,7 @@ const EditViewDataManagerProvider = ({
     });
   }, []);
 
-  const onRemoveRelation = useCallback((keys) => {
+  const onRemoveRelation = useCallback(keys => {
     dispatch({
       type: 'REMOVE_RELATION',
       keys,
diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js
index db7b94d8dc..0d846a4c09 100644
--- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js
+++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js
@@ -10,11 +10,11 @@ import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
 import isFieldTypeNumber from '../../../utils/isFieldTypeNumber';
 
 yup.addMethod(yup.mixed, 'defined', function() {
-  return this.test('defined', errorsTrads.required, (value) => value !== undefined);
+  return this.test('defined', errorsTrads.required, value => value !== undefined);
 });
 
 yup.addMethod(yup.array, 'notEmptyMin', function(min) {
-  return this.test('notEmptyMin', errorsTrads.min, (value) => {
+  return this.test('notEmptyMin', errorsTrads.min, value => {
     if (isEmpty(value)) {
       return true;
     }
@@ -51,7 +51,7 @@ yup.addMethod(yup.string, 'isSuperior', function(message, min) {
   });
 });
 
-const getAttributes = (data) => get(data, ['attributes'], {});
+const getAttributes = data => get(data, ['attributes'], {});
 
 const createYupSchema = (
   model,
@@ -97,7 +97,7 @@ const createYupSchema = (
         if (attribute.repeatable === true) {
           const { min, max, required } = attribute;
 
-          let componentSchema = yup.lazy((value) => {
+          let componentSchema = yup.lazy(value => {
             let baseSchema = yup.array().of(componentFieldSchema);
 
             if (min) {
@@ -123,7 +123,7 @@ const createYupSchema = (
 
           return acc;
         }
-        const componentSchema = yup.lazy((obj) => {
+        const componentSchema = yup.lazy(obj => {
           if (obj !== undefined) {
             return attribute.required === true && !options.isDraft
               ? componentFieldSchema.defined()
@@ -154,7 +154,7 @@ const createYupSchema = (
         if (min) {
           if (attribute.required) {
             dynamicZoneSchema = dynamicZoneSchema
-              .test('min', errorsTrads.min, (value) => {
+              .test('min', errorsTrads.min, value => {
                 if (options.isCreatingEntry) {
                   return value && value.length >= min;
                 }
@@ -165,7 +165,7 @@ const createYupSchema = (
 
                 return value !== null && value.length >= min;
               })
-              .test('required', errorsTrads.required, (value) => {
+              .test('required', errorsTrads.required, value => {
                 if (options.isCreatingEntry) {
                   return value !== null || value !== undefined;
                 }
@@ -180,7 +180,7 @@ const createYupSchema = (
             dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min);
           }
         } else if (attribute.required && !options.isDraft) {
-          dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, (value) => {
+          dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, value => {
             if (options.isCreatingEntry) {
               return value !== null || value !== undefined;
             }
@@ -215,7 +215,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
   if (type === 'json') {
     schema = yup
       .mixed(errorsTrads.json)
-      .test('isJSON', errorsTrads.json, (value) => {
+      .test('isJSON', errorsTrads.json, value => {
         if (value === undefined) {
           return true;
         }
@@ -238,7 +238,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
   if (['number', 'integer', 'float', 'decimal'].includes(type)) {
     schema = yup
       .number()
-      .transform((cv) => (isNaN(cv) ? undefined : cv))
+      .transform(cv => (isNaN(cv) ? undefined : cv))
       .typeError();
   }
 
@@ -250,7 +250,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
     schema = yup.date();
   }
 
-  Object.keys(validations).forEach((validation) => {
+  Object.keys(validations).forEach(validation => {
     const validationValue = validations[validation];
 
     if (
@@ -269,7 +269,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
               if (options.isCreatingEntry) {
                 schema = schema.required(errorsTrads.required);
               } else {
-                schema = schema.test('required', errorsTrads.required, (value) => {
+                schema = schema.test('required', errorsTrads.required, value => {
                   // Field is not touched and the user is editing the entry
                   if (value === undefined && !options.isFromComponent) {
                     return true;
diff --git a/packages/core/admin/admin/src/content-manager/components/Inputs/index.js b/packages/core/admin/admin/src/content-manager/components/Inputs/index.js
index 3f59cd56cf..15a75e80fc 100644
--- a/packages/core/admin/admin/src/content-manager/components/Inputs/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/Inputs/index.js
@@ -157,10 +157,10 @@ function Inputs({
     return disabled;
   }, [disabled, isCreatingEntry, isUserAllowedToEditField, isUserAllowedToReadField]);
 
-  const options = useMemo(
-    () => generateOptions(fieldSchema.enum || [], isRequired),
-    [fieldSchema, isRequired]
-  );
+  const options = useMemo(() => generateOptions(fieldSchema.enum || [], isRequired), [
+    fieldSchema,
+    isRequired,
+  ]);
 
   const { label, description, placeholder, visible } = metadatas;
 
diff --git a/packages/core/admin/admin/src/hooks/useNavigatorOnLine/index.js b/packages/core/admin/admin/src/hooks/useNavigatorOnLine/index.js
new file mode 100644
index 0000000000..f2fed9c827
--- /dev/null
+++ b/packages/core/admin/admin/src/hooks/useNavigatorOnLine/index.js
@@ -0,0 +1,31 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * For more details about this hook see:
+ * https://www.30secondsofcode.org/react/s/use-navigator-on-line
+ */
+const useNavigatorOnLine = () => {
+  const onlineStatus =
+    typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean'
+      ? navigator.onLine
+      : true;
+
+  const [isOnline, setIsOnline] = useState(onlineStatus);
+
+  const setOnline = () => setIsOnline(true);
+  const setOffline = () => setIsOnline(false);
+
+  useEffect(() => {
+    window.addEventListener('online', setOnline);
+    window.addEventListener('offline', setOffline);
+
+    return () => {
+      window.removeEventListener('online', setOnline);
+      window.removeEventListener('offline', setOffline);
+    };
+  }, []);
+
+  return isOnline;
+};
+
+export default useNavigatorOnLine;
diff --git a/packages/core/admin/admin/src/hooks/useNavigatorOnLine/tests/index.test.js b/packages/core/admin/admin/src/hooks/useNavigatorOnLine/tests/index.test.js
new file mode 100644
index 0000000000..16a84eadb4
--- /dev/null
+++ b/packages/core/admin/admin/src/hooks/useNavigatorOnLine/tests/index.test.js
@@ -0,0 +1,48 @@
+import { renderHook, act } from '@testing-library/react-hooks';
+import useNavigatorOnLine from '../index';
+
+describe('useNavigatorOnLine', () => {
+  it('returns the online state', () => {
+    jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(true);
+    const { result } = renderHook(() => useNavigatorOnLine());
+
+    expect(result.current).toEqual(true);
+  });
+
+  it('returns the offline state', () => {
+    jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(false);
+    const { result } = renderHook(() => useNavigatorOnLine());
+
+    expect(result.current).toEqual(false);
+  });
+
+  it('listens for network change online', async () => {
+    // Initialize an offline state
+    jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(false);
+    const { result, waitForNextUpdate } = renderHook(() => useNavigatorOnLine());
+
+    await act(async () => {
+      // Simulate a change from offline to online
+      window.dispatchEvent(new window.Event('online'));
+
+      await waitForNextUpdate();
+    });
+
+    expect(result.current).toEqual(true);
+  });
+
+  it('listens for network change offline', async () => {
+    // Initialize an online state
+    jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(true);
+    const { result, waitForNextUpdate } = renderHook(() => useNavigatorOnLine());
+
+    await act(async () => {
+      // Simulate a change from online to offline
+      window.dispatchEvent(new window.Event('offline'));
+
+      await waitForNextUpdate();
+    });
+
+    expect(result.current).toEqual(false);
+  });
+});
diff --git a/packages/core/admin/admin/src/pages/MarketplacePage/components/PageHeader/index.js b/packages/core/admin/admin/src/pages/MarketplacePage/components/PageHeader/index.js
new file mode 100644
index 0000000000..b68e862098
--- /dev/null
+++ b/packages/core/admin/admin/src/pages/MarketplacePage/components/PageHeader/index.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useIntl } from 'react-intl';
+import { HeaderLayout } from '@strapi/design-system/Layout';
+import { LinkButton } from '@strapi/design-system/LinkButton';
+import Upload from '@strapi/icons/Upload';
+import { useTracking } from '@strapi/helper-plugin';
+
+const PageHeader = ({ isOnline }) => {
+  const { formatMessage } = useIntl();
+  const { trackUsage } = useTracking();
+
+  return (
+    }
+            variant="tertiary"
+            href="https://market.strapi.io/submit-plugin"
+            onClick={() => trackUsage('didSubmitPlugin')}
+          >
+            {formatMessage({
+              id: 'admin.pages.MarketPlacePage.submit.plugin.link',
+              defaultMessage: 'Submit your plugin',
+            })}
+          
+        )
+      }
+    />
+  );
+};
+
+export default PageHeader;
+
+PageHeader.propTypes = {
+  isOnline: PropTypes.bool.isRequired,
+};
diff --git a/packages/core/admin/admin/src/pages/MarketplacePage/index.js b/packages/core/admin/admin/src/pages/MarketplacePage/index.js
index 3f4a703eab..8ba4657f86 100644
--- a/packages/core/admin/admin/src/pages/MarketplacePage/index.js
+++ b/packages/core/admin/admin/src/pages/MarketplacePage/index.js
@@ -13,20 +13,23 @@ import {
   useAppInfos,
 } from '@strapi/helper-plugin';
 import { Grid, GridItem } from '@strapi/design-system/Grid';
-import { Layout, HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
+import { Layout, ContentLayout } from '@strapi/design-system/Layout';
 import { Main } from '@strapi/design-system/Main';
 import { Searchbar } from '@strapi/design-system/Searchbar';
 import { Box } from '@strapi/design-system/Box';
-import { LinkButton } from '@strapi/design-system/LinkButton';
 import { useNotifyAT } from '@strapi/design-system/LiveRegions';
-import Upload from '@strapi/icons/Upload';
+import { Typography } from '@strapi/design-system/Typography';
+import { Flex } from '@strapi/design-system/Flex';
 
 import PluginCard from './components/PluginCard';
 import { EmptyPluginSearch } from './components/EmptyPluginSearch';
+import PageHeader from './components/PageHeader';
 import { fetchAppInformation } from './utils/api';
 import useFetchInstalledPlugins from '../../hooks/useFetchInstalledPlugins';
 import useFetchMarketplacePlugins from '../../hooks/useFetchMarketplacePlugins';
 import adminPermissions from '../../permissions';
+import offlineCloud from '../../assets/images/icon_offline-cloud.svg';
+import useNavigatorOnLine from '../../hooks/useNavigatorOnLine';
 import MissingPluginBanner from './components/MissingPluginBanner';
 
 const matchSearch = (plugins, search) => {
@@ -49,6 +52,7 @@ const MarketPlacePage = () => {
   const toggleNotification = useNotification();
   const [searchQuery, setSearchQuery] = useState('');
   const { autoReload: isInDevelopmentMode } = useAppInfos();
+  const isOnline = useNavigatorOnLine();
 
   useFocusWhenNavigate();
 
@@ -117,6 +121,42 @@ const MarketPlacePage = () => {
     }
   }, [toggleNotification, isInDevelopmentMode]);
 
+  if (!isOnline) {
+    return (
+      
+        
+          
+          
+            
+              
+                {formatMessage({
+                  id: 'admin.pages.MarketPlacePage.offline.title',
+                  defaultMessage: 'You are offline',
+                })}
+              
+            
+            
+              
+                {formatMessage({
+                  id: 'admin.pages.MarketPlacePage.offline.subtitle',
+                  defaultMessage:
+                    'You need to be connected to the Internet to access Strapi Market.',
+                })}
+              
+            
+             +          
+        
+      
+    );
+  }
+
   if (hasFailed) {
     return (
       
@@ -151,29 +191,7 @@ const MarketPlacePage = () => {
             defaultMessage: 'Marketplace - Plugins',
           })}
         />
-        }
-              variant="tertiary"
-              href="https://market.strapi.io/submit-plugin"
-              onClick={() => trackUsage('didSubmitPlugin')}
-            >
-              {formatMessage({
-                id: 'admin.pages.MarketPlacePage.submit.plugin.link',
-                defaultMessage: 'Submit your plugin',
-              })}
-            
-          }
-        />
+        
         
           
              jest.fn(() => true));
+
 jest.mock('@strapi/helper-plugin', () => ({
   ...jest.requireActual('@strapi/helper-plugin'),
   useNotification: jest.fn(() => {
@@ -1664,7 +1667,7 @@ describe('Marketplace page', () => {
     expect(noResult).toBeVisible();
   });
 
-  it('handles production environment', async () => {
+  it('handles production environment', () => {
     // Simulate production environment
     useAppInfos.mockImplementation(() => ({ autoReload: false }));
     const { queryByText } = render(App);
@@ -1678,9 +1681,25 @@ describe('Marketplace page', () => {
       },
       blockTransition: true,
     });
+
     expect(toggleNotification).toHaveBeenCalledTimes(1);
 
     // Should not show install buttons
     expect(queryByText(/copy install command/i)).not.toBeInTheDocument();
   });
+
+  it('shows an online layout', () => {
+    render(App);
+    const offlineText = screen.queryByText('You are offline');
+
+    expect(offlineText).toEqual(null);
+  });
+
+  it('shows the offline layout', () => {
+    useNavigatorOnLine.mockReturnValueOnce(false);
+    render(App);
+    const offlineText = screen.getByText('You are offline');
+
+    expect(offlineText).toBeVisible();
+  });
 });
diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json
index dd1f8d3b0a..c15c833aab 100644
--- a/packages/core/admin/admin/src/translations/en.json
+++ b/packages/core/admin/admin/src/translations/en.json
@@ -205,6 +205,8 @@
   "Users.components.List.empty.withFilters": "There is no users with the applied filters...",
   "Users.components.List.empty.withSearch": "There is no users corresponding to the search ({search})...",
   "admin.pages.MarketPlacePage.helmet": "Marketplace - Plugins",
+  "admin.pages.MarketPlacePage.offline.title": "You are offline",
+  "admin.pages.MarketPlacePage.offline.subtitle": "You need to be connected to the Internet to access Strapi Market.",
   "admin.pages.MarketPlacePage.plugin.copy": "Copy install command",
   "admin.pages.MarketPlacePage.plugin.copy.success": "Install command ready to be pasted in your terminal",
   "admin.pages.MarketPlacePage.plugin.info": "Learn more",
diff --git a/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js b/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
index 92fd58a89b..a71608194b 100644
--- a/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
+++ b/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
@@ -1,8 +1,14 @@
 import { get } from 'lodash';
 
-const getYupInnerErrors = (error) => {
+const getYupInnerErrors = error => {
   return get(error, 'inner', []).reduce((acc, curr) => {
-    acc[curr.path.split('[').join('.').split(']').join('')] = {
+    acc[
+      curr.path
+        .split('[')
+        .join('.')
+        .split(']')
+        .join('')
+    ] = {
       id: curr.message,
       defaultMessage: curr.message,
     };
+          
+        
+      
+    );
+  }
+
   if (hasFailed) {
     return (
       
@@ -151,29 +191,7 @@ const MarketPlacePage = () => {
             defaultMessage: 'Marketplace - Plugins',
           })}
         />
-        }
-              variant="tertiary"
-              href="https://market.strapi.io/submit-plugin"
-              onClick={() => trackUsage('didSubmitPlugin')}
-            >
-              {formatMessage({
-                id: 'admin.pages.MarketPlacePage.submit.plugin.link',
-                defaultMessage: 'Submit your plugin',
-              })}
-            
-          }
-        />
+        
         
           
              jest.fn(() => true));
+
 jest.mock('@strapi/helper-plugin', () => ({
   ...jest.requireActual('@strapi/helper-plugin'),
   useNotification: jest.fn(() => {
@@ -1664,7 +1667,7 @@ describe('Marketplace page', () => {
     expect(noResult).toBeVisible();
   });
 
-  it('handles production environment', async () => {
+  it('handles production environment', () => {
     // Simulate production environment
     useAppInfos.mockImplementation(() => ({ autoReload: false }));
     const { queryByText } = render(App);
@@ -1678,9 +1681,25 @@ describe('Marketplace page', () => {
       },
       blockTransition: true,
     });
+
     expect(toggleNotification).toHaveBeenCalledTimes(1);
 
     // Should not show install buttons
     expect(queryByText(/copy install command/i)).not.toBeInTheDocument();
   });
+
+  it('shows an online layout', () => {
+    render(App);
+    const offlineText = screen.queryByText('You are offline');
+
+    expect(offlineText).toEqual(null);
+  });
+
+  it('shows the offline layout', () => {
+    useNavigatorOnLine.mockReturnValueOnce(false);
+    render(App);
+    const offlineText = screen.getByText('You are offline');
+
+    expect(offlineText).toBeVisible();
+  });
 });
diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json
index dd1f8d3b0a..c15c833aab 100644
--- a/packages/core/admin/admin/src/translations/en.json
+++ b/packages/core/admin/admin/src/translations/en.json
@@ -205,6 +205,8 @@
   "Users.components.List.empty.withFilters": "There is no users with the applied filters...",
   "Users.components.List.empty.withSearch": "There is no users corresponding to the search ({search})...",
   "admin.pages.MarketPlacePage.helmet": "Marketplace - Plugins",
+  "admin.pages.MarketPlacePage.offline.title": "You are offline",
+  "admin.pages.MarketPlacePage.offline.subtitle": "You need to be connected to the Internet to access Strapi Market.",
   "admin.pages.MarketPlacePage.plugin.copy": "Copy install command",
   "admin.pages.MarketPlacePage.plugin.copy.success": "Install command ready to be pasted in your terminal",
   "admin.pages.MarketPlacePage.plugin.info": "Learn more",
diff --git a/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js b/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
index 92fd58a89b..a71608194b 100644
--- a/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
+++ b/packages/core/helper-plugin/lib/src/utils/getYupInnerErrors/index.js
@@ -1,8 +1,14 @@
 import { get } from 'lodash';
 
-const getYupInnerErrors = (error) => {
+const getYupInnerErrors = error => {
   return get(error, 'inner', []).reduce((acc, curr) => {
-    acc[curr.path.split('[').join('.').split(']').join('')] = {
+    acc[
+      curr.path
+        .split('[')
+        .join('.')
+        .split(']')
+        .join('')
+    ] = {
       id: curr.message,
       defaultMessage: curr.message,
     };