diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx
index 91aebb485ea7f..be7a2a104bfa4 100644
--- a/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx
+++ b/x-pack/plugins/index_lifecycle_management/integration_tests/helpers/setup_environment.tsx
@@ -17,6 +17,10 @@ import {
   fatalErrorsServiceMock,
   docLinksServiceMock,
   executionContextServiceMock,
+  overlayServiceMock,
+  applicationServiceMock,
+  httpServiceMock,
+  scopedHistoryMock,
 } from '@kbn/core/public/mocks';
 import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
 import { init as initHttp } from '../../public/application/services/http';
@@ -31,8 +35,12 @@ const appContextMock = {
   breadcrumbService,
   license: licensingMock.createLicense({ license: { type: 'enterprise' } }),
   docLinks: docLinksServiceMock.createStartContract(),
-  getUrlForApp: () => {},
+  getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp,
   executionContext: executionContextServiceMock.createSetupContract(),
+  navigateToUrl: applicationServiceMock.createStartContract().navigateToUrl,
+  overlays: overlayServiceMock.createStartContract(),
+  http: httpServiceMock.createSetupContract(),
+  history: scopedHistoryMock.create(),
 };
 
 export const WithAppDependencies =
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
index 6360bee1af571..bb004766d89f7 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
@@ -21,10 +21,8 @@ import {
 import { App } from './app';
 import { BreadcrumbService } from './services/breadcrumbs';
 
-type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
-
 export const renderApp = (
-  startServices: StartServices,
+  startServices: CoreStart,
   element: Element,
   history: ScopedHistory,
   application: ApplicationStart,
@@ -34,7 +32,9 @@ export const renderApp = (
   executionContext: ExecutionContextStart,
   cloud?: CloudSetup
 ): UnmountCallback => {
-  const { getUrlForApp } = application;
+  const { navigateToUrl, getUrlForApp } = application;
+  const { overlays, http } = startServices;
+
   render(
     <KibanaRenderContextProvider {...startServices}>
       <div className={APP_WRAPPER_CLASS}>
@@ -51,6 +51,10 @@ export const renderApp = (
               getUrlForApp,
               docLinks,
               executionContext,
+              navigateToUrl,
+              overlays,
+              http,
+              history,
             }}
           >
             <App history={history} />
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
index b63f0b595a540..83faaa3bf28f7 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
@@ -7,11 +7,10 @@
 
 import { i18n } from '@kbn/i18n';
 import { FormattedMessage } from '@kbn/i18n-react';
+import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
 import React, { Fragment, useEffect, useMemo, useState } from 'react';
 import { get } from 'lodash';
 
-import { useHistory } from 'react-router-dom';
-
 import './edit_policy.scss';
 
 import {
@@ -27,7 +26,13 @@ import {
   EuiTimeline,
 } from '@elastic/eui';
 
-import { TextField, useForm, useFormData, useKibana } from '../../../shared_imports';
+import {
+  TextField,
+  useForm,
+  useFormData,
+  useKibana,
+  useFormIsModified,
+} from '../../../shared_imports';
 import { toasts } from '../../services/notification';
 import { UseField } from './form';
 import { savePolicy } from './save_policy';
@@ -69,10 +74,11 @@ export const EditPolicy: React.FunctionComponent = () => {
   } = useEditPolicyContext();
 
   const {
-    services: { cloud, docLinks },
+    services: { cloud, docLinks, history, navigateToUrl, overlays, http },
   } = useKibana();
 
   const [isClonedPolicy, setIsClonedPolicy] = useState(false);
+  const [hasSubmittedForm, setHasSubmittedForm] = useState<boolean>(false);
   const originalPolicyName: string = isNewPolicy ? '' : policyName!;
   const isAllowedByLicense = license.canUseSearchableSnapshot();
   const isCloudEnabled = Boolean(cloud?.isCloudEnabled);
@@ -105,6 +111,8 @@ export const EditPolicy: React.FunctionComponent = () => {
   });
 
   const [formData] = useFormData({ form, watch: policyNamePath });
+  const isFormDirty = useFormIsModified({ form });
+
   const getPolicyName = () => {
     return isNewPolicy || isClonedPolicy ? get(formData, policyNamePath) : originalPolicyName;
   };
@@ -119,7 +127,6 @@ export const EditPolicy: React.FunctionComponent = () => {
     [originalPolicyName, existingPolicies, isClonedPolicy]
   );
 
-  const history = useHistory();
   const backToPolicyList = () => {
     history.push('/policies');
   };
@@ -134,6 +141,7 @@ export const EditPolicy: React.FunctionComponent = () => {
         })
       );
     } else {
+      setHasSubmittedForm(true);
       const success = await savePolicy(
         {
           ...policy,
@@ -141,6 +149,7 @@ export const EditPolicy: React.FunctionComponent = () => {
         },
         isNewPolicy || isClonedPolicy
       );
+
       if (success) {
         backToPolicyList();
       }
@@ -151,6 +160,21 @@ export const EditPolicy: React.FunctionComponent = () => {
     setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout);
   };
 
+  useUnsavedChangesPrompt({
+    titleText: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.unsavedPrompt.title', {
+      defaultMessage: 'Exit without saving changes?',
+    }),
+    messageText: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.unsavedPrompt.body', {
+      defaultMessage:
+        'The data will be lost if you leave this page without saving the policy changes.',
+    }),
+    hasUnsavedChanges: isFormDirty && hasSubmittedForm === false,
+    openConfirm: overlays.openConfirm,
+    history,
+    http,
+    navigateToUrl,
+  });
+
   return (
     <>
       <EuiPageHeader
diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
index 7538dc66de002..1c907fdd2b5c7 100644
--- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
@@ -28,6 +28,7 @@ export {
   getFieldValidityAndErrorMessage,
   useFormContext,
   UseMultiFields,
+  useFormIsModified,
 } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
 
 export { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts
index 40772dbe12884..6ea4c4d2b18ae 100644
--- a/x-pack/plugins/index_lifecycle_management/public/types.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/types.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { ApplicationStart } from '@kbn/core/public';
+import { ApplicationStart, HttpSetup, OverlayStart, ScopedHistory } from '@kbn/core/public';
 import { DocLinksStart } from '@kbn/core/public';
 import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
 import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
@@ -41,5 +41,9 @@ export interface AppServicesContext {
   license: ILicense;
   cloud?: CloudSetup;
   getUrlForApp: ApplicationStart['getUrlForApp'];
+  navigateToUrl: ApplicationStart['navigateToUrl'];
   docLinks: DocLinksStart;
+  overlays: OverlayStart;
+  http: HttpSetup;
+  history: ScopedHistory;
 }
diff --git a/x-pack/plugins/index_lifecycle_management/tsconfig.json b/x-pack/plugins/index_lifecycle_management/tsconfig.json
index 231a9775bf4ab..b9249bc2212f8 100644
--- a/x-pack/plugins/index_lifecycle_management/tsconfig.json
+++ b/x-pack/plugins/index_lifecycle_management/tsconfig.json
@@ -39,6 +39,7 @@
     "@kbn/shared-ux-link-redirect-app",
     "@kbn/index-management",
     "@kbn/react-kibana-context-render",
+    "@kbn/unsaved-changes-prompt",
   ],
   "exclude": [
     "target/**/*",
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
index 30637d00f495c..80c43af7b7d4d 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
@@ -157,10 +157,11 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
   */
   useUnsavedChangesPrompt({
     titleText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.title', {
-      defaultMessage: `Exit pipeline creation without saving changes?`,
+      defaultMessage: 'Exit without saving changes?',
     }),
     messageText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.body', {
-      defaultMessage: `The data will be lost if you leave this page without saving the pipeline changes`,
+      defaultMessage:
+        'The data will be lost if you leave this page without saving the pipeline changes.',
     }),
     hasUnsavedChanges: (isFormDirty || areProcessorsDirty) && !hasSubmittedForm,
     openConfirm: overlays.openConfirm,
diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts
index 71056c2d836fc..8d32181210fcd 100644
--- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts
+++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts
@@ -18,6 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
   const esClient = getService('es');
   const security = getService('security');
   const deployment = getService('deployment');
+  const testSubjects = getService('testSubjects');
 
   describe('Home page', function () {
     before(async () => {
@@ -80,5 +81,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
 
       expect(createdPolicy.length).to.be(1);
     });
+
+    it('Shows a prompt when trying to navigate away from the creation form when the form is dirty', async () => {
+      await pageObjects.indexLifecycleManagement.clickCreatePolicyButton();
+
+      await pageObjects.indexLifecycleManagement.fillNewPolicyForm({
+        policyName,
+      });
+
+      // Try to navigate to another page
+      await testSubjects.click('logo');
+
+      // Since the form is now dirty it should trigger a confirmation prompt
+      expect(await testSubjects.exists('navigationBlockConfirmModal')).to.be(true);
+    });
   });
 };
diff --git a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts
index b9cfebfbbb40b..a0061dff067d1 100644
--- a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts
+++ b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts
@@ -27,6 +27,9 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider
     async createPolicyButton() {
       return await testSubjects.find('createPolicyButton');
     },
+    async clickCreatePolicyButton() {
+      return await testSubjects.click('createPolicyButton');
+    },
     async fillNewPolicyForm(policy: Policy) {
       const {
         policyName,