diff --git a/dist/js/gutenberg.js b/dist/js/gutenberg.js
index a68761883060d60b8204b79ab08446364213a564..1163520ed3be7415338f06deec7483ac2b4d3b12 100644
--- a/dist/js/gutenberg.js
+++ b/dist/js/gutenberg.js
@@ -4,7 +4,7 @@ object-assign
 (c) Sindre Sorhus
 @license MIT
 */
-var t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable;function o(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},r=0;r<10;r++)t["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach((function(e){n[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var s,c,u=o(e),a=1;a<arguments.length;a++){for(var l in s=Object(arguments[a]))r.call(s,l)&&(u[l]=s[l]);if(t){c=t(s);for(var p=0;p<c.length;p++)n.call(s,c[p])&&(u[c[p]]=s[c[p]])}}return u}},251:function(e,t,r){
+var r=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable;function o(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var r={},t=0;t<10;t++)r["_"+String.fromCharCode(t)]=t;if("0123456789"!==Object.getOwnPropertyNames(r).map((function(e){return r[e]})).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach((function(e){n[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var s,c,a=o(e),u=1;u<arguments.length;u++){for(var l in s=Object(arguments[u]))t.call(s,l)&&(a[l]=s[l]);if(r){c=r(s);for(var p=0;p<c.length;p++)n.call(s,c[p])&&(a[c[p]]=s[c[p]])}}return a}},251:function(e,r,t){
 /** @license React v17.0.2
  * react-jsx-runtime.production.min.js
  *
@@ -13,4 +13,4 @@ var t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,n=Object.pr
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
-r(418);var n=r(804),o=60103;if(60107,"function"==typeof Symbol&&Symbol.for){var i=Symbol.for;o=i("react.element"),i("react.fragment")}var s=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,c=Object.prototype.hasOwnProperty,u={key:!0,ref:!0,__self:!0,__source:!0};function a(e,t,r){var n,i={},a=null,l=null;for(n in void 0!==r&&(a=""+r),void 0!==t.key&&(a=""+t.key),void 0!==t.ref&&(l=t.ref),t)c.call(t,n)&&!u.hasOwnProperty(n)&&(i[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===i[n]&&(i[n]=t[n]);return{$$typeof:o,type:e,key:a,ref:l,props:i,_owner:s.current}}t.jsx=a,t.jsxs=a},893:function(e,t,r){e.exports=r(251)},804:function(e){e.exports=React},190:function(e){e.exports=wp.components},405:function(e){e.exports=wp.compose},440:function(e){e.exports=wp.data},451:function(e){e.exports=wp.editPost},194:function(e){e.exports=wp.i18n},415:function(e){e.exports=wp.plugins}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}!function(){var e=r(893);function t(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var n=r(190),o=n.Button,i=n.TextControl,s=r(405).compose,c=r(440),u=c.withSelect,a=c.withDispatch,l=r(451).PluginDocumentSettingPanel,p=r(194).__,f=r(415).registerPlugin,v="_wp_rev_ctl_limit",g="wp-revisions-control";f("plugin-document-setting-panel-demo",{render:s([u((function(e){var t=e("core/editor"),r=t.getCurrentPostRevisionsCount,n=t.getEditedPostAttribute,o=r(),i=n("meta")[v];return{limit:i,showPurgeButton:Boolean(i)&&o>parseInt(i)}})),a((function(e,r){var n=r.limit;return{manualPurge:function(){console.log("Purging!",n)},updateMeta:function(r){e("core/editor").editPost({meta:t({},v,r)})}}}))])((function(t){var r=t.limit,n=t.manualPurge,s=t.showPurgeButton,c=t.updateMeta;return(0,e.jsxs)(l,{name:g,title:p("WP Revisions Control","wp_revisions_control"),className:g,children:[(0,e.jsx)(i,{label:p("Number of revisions to retain:","wp_revisions_control"),help:p("Leave blank to keep all revisions.","wp_revisions_control"),value:r,onChange:c}),s&&(0,e.jsx)(o,{onClick:n,children:p("Purge excess revisions","wp_revisions_control")})]})})),icon:"backup"})}()}();
\ No newline at end of file
+t(418);var n=t(804),o=60103;if(r.Fragment=60107,"function"==typeof Symbol&&Symbol.for){var i=Symbol.for;o=i("react.element"),r.Fragment=i("react.fragment")}var s=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,c=Object.prototype.hasOwnProperty,a={key:!0,ref:!0,__self:!0,__source:!0};function u(e,r,t){var n,i={},u=null,l=null;for(n in void 0!==t&&(u=""+t),void 0!==r.key&&(u=""+r.key),void 0!==r.ref&&(l=r.ref),r)c.call(r,n)&&!a.hasOwnProperty(n)&&(i[n]=r[n]);if(e&&e.defaultProps)for(n in r=e.defaultProps)void 0===i[n]&&(i[n]=r[n]);return{$$typeof:o,type:e,key:u,ref:l,props:i,_owner:s.current}}r.jsx=u,r.jsxs=u},893:function(e,r,t){e.exports=t(251)},804:function(e){e.exports=React},986:function(e){e.exports=wp.apiFetch},190:function(e){e.exports=wp.components},405:function(e){e.exports=wp.compose},440:function(e){e.exports=wp.data},451:function(e){e.exports=wp.editPost},724:function(e){e.exports=wp.element},194:function(e){e.exports=wp.i18n},415:function(e){e.exports=wp.plugins}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n](i,i.exports,t),i.exports}!function(){var e=t(893);function r(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}function n(e,r){return function(e){if(Array.isArray(e))return e}(e)||function(e,r){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(e)))return;var t=[],n=!0,o=!1,i=void 0;try{for(var s,c=e[Symbol.iterator]();!(n=(s=c.next()).done)&&(t.push(s.value),!r||t.length!==r);n=!0);}catch(e){o=!0,i=e}finally{try{n||null==c.return||c.return()}finally{if(o)throw i}}return t}(e,r)||function(e,r){if(!e)return;if("string"==typeof e)return o(e,r);var t=Object.prototype.toString.call(e).slice(8,-1);"Object"===t&&e.constructor&&(t=e.constructor.name);if("Map"===t||"Set"===t)return Array.from(e);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return o(e,r)}(e,r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(e,r){(null==r||r>e.length)&&(r=e.length);for(var t=0,n=new Array(r);t<r;t++)n[t]=e[t];return n}var i=t(986),s=t(190),c=s.Button,a=s.Modal,u=s.TextControl,l=t(405).compose,p=t(440),f=p.withSelect,v=p.withDispatch,d=t(451).PluginDocumentSettingPanel,m=t(724).useState,b=t(194),h=b.__,y=b._n,w=b.sprintf,_=t(415).registerPlugin,g="_wp_rev_ctl_limit",j="wp-revisions-control",x=function(r,t){var o=n(m(!1),2),i=o[0],s=o[1],u=function(){return s(!1)},l=w(y("This will remove all but the most-recent revision.","This will remove all but the %1$d most-recent revisions.",parseInt(r),"wp_revisions_control"),r);return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)(c,{isSecondary:!0,onClick:function(){return s(!0)},children:h("Purge excess revisions","wp_revisions_control")}),i&&(0,e.jsxs)(a,{title:h("Purge excess revisions","wp_revisions_control"),contentLabel:l,onRequestClose:u,children:[(0,e.jsx)("p",{children:l}),(0,e.jsx)(c,{isSecondary:!0,onClick:u,children:h("Cancel","wp_revisions_control")}),(0,e.jsx)(c,{isPrimary:!0,onClick:function(){u(),t()},children:h("Purge","wp_revisions_control")})]})]})};_("plugin-document-setting-panel-demo",{render:l([f((function(e){var r=e("core/editor"),t=r.getCurrentPostRevisionsCount,n=r.getEditedPostAttribute,o=t(),i=n("meta")[g];return{limit:i,showPurgeButton:Boolean(i)&&o>parseInt(i)}})),v((function(e,t,n){t.limit;var o=n.select;return{manualPurge:function(){var r=o("core/editor").getCurrentPostId();i({path:"/wp-revisions-control/v1/schedule/".concat(r),method:"PUT"}).then((function(r){var t,n;r?(t="success",n=h("Excess revisions scheduled for removal.","wp_revisions_control")):(t="error",n=h("Failed to schedule excess revisions for removal.","wp_revisions_control")),e("core/notices").createNotice(t,n,{id:"wp-revisions-control-scheduled-purge",isDismissible:!0,type:"snackbar"})}))},updateMeta:function(t){e("core/editor").editPost({meta:r({},g,t)})}}}))])((function(r){var t=r.limit,n=r.manualPurge,o=r.showPurgeButton,i=r.updateMeta;return(0,e.jsxs)(d,{name:j,title:h("WP Revisions Control","wp_revisions_control"),className:j,children:[(0,e.jsx)(u,{label:h("Number of revisions to retain:","wp_revisions_control"),help:h("Leave blank to keep all revisions.","wp_revisions_control"),value:t,onChange:i}),o&&x(t,n)]})})),icon:"backup"})}()}();
\ No newline at end of file
diff --git a/languages/wp-revisions-control-gutenberg.pot b/languages/wp-revisions-control-gutenberg.pot
index 22b82f9998fa7d221d52b280d73507cfdffd2960..c2a19eae43e8f7a07dd67b5696995ad28bec695d 100644
--- a/languages/wp-revisions-control-gutenberg.pot
+++ b/languages/wp-revisions-control-gutenberg.pot
@@ -3,18 +3,40 @@ msgstr ""
 "Content-Type: text/plain; charset=utf-8\n"
 "X-Generator: babel-plugin-makepot\n"
 
-#: src/js/gutenberg.js:14
+#: src/js/gutenberg.js:114
+msgid "Excess revisions scheduled for removal."
+msgstr ""
+
+#: src/js/gutenberg.js:117
+msgid "Failed to schedule excess revisions for removal."
+msgstr ""
+
+#: src/js/gutenberg.js:16
 msgid "WP Revisions Control"
 msgstr ""
 
-#: src/js/gutenberg.js:18
+#: src/js/gutenberg.js:20
 msgid "Number of revisions to retain:"
 msgstr ""
 
-#: src/js/gutenberg.js:19
+#: src/js/gutenberg.js:21
 msgid "Leave blank to keep all revisions."
 msgstr ""
 
-#: src/js/gutenberg.js:26
+#: src/js/gutenberg.js:41
+msgid "This will remove all but the most-recent revision."
+msgid_plural "This will remove all but the %1$d most-recent revisions."
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/js/gutenberg.js:58
 msgid "Purge excess revisions"
+msgstr ""
+
+#: src/js/gutenberg.js:67
+msgid "Cancel"
+msgstr ""
+
+#: src/js/gutenberg.js:71
+msgid "Purge"
 msgstr ""
\ No newline at end of file
diff --git a/languages/wp-revisions-control.pot b/languages/wp-revisions-control.pot
index 4fbf584dec9a8fa7e69aa7d1ad95d67352f89941..05ac70526c57e9ae7787a2c1c5e0f90eb35bc37d 100644
--- a/languages/wp-revisions-control.pot
+++ b/languages/wp-revisions-control.pot
@@ -5,7 +5,7 @@ msgstr ""
 "Project-Id-Version: WP Revisions Control 1.3\n"
 "Report-Msgid-Bugs-To: "
 "https://wordpress.org/support/plugin/wp-revisions-control\n"
-"POT-Creation-Date: 2021-03-28 03:24:58+00:00\n"
+"POT-Creation-Date: 2021-03-28 06:19:35+00:00\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -45,17 +45,17 @@ msgstr ""
 msgid "WP Revisions Control encountered an unspecified error."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:162
+#: inc/class-wp-revisions-control.php:170
 msgid ""
 "Set the number of revisions to save for each post type listed. To retain "
 "all revisions for a given post type, leave the field empty."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:163
+#: inc/class-wp-revisions-control.php:171
 msgid "If a post type isn't listed, revisions are not enabled for that post type."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:174
+#: inc/class-wp-revisions-control.php:182
 #. translators: 1. Filter tag.
 msgid ""
 "A local change is causing this plugin's functionality to run at a priority "
@@ -63,64 +63,64 @@ msgid ""
 "please unhook any functions from the %1$s filter."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:299
+#: inc/class-wp-revisions-control.php:307
 msgid "Revisions"
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:332
+#: inc/class-wp-revisions-control.php:340
 msgid "Processing&hellip;"
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:333
+#: inc/class-wp-revisions-control.php:341
 msgid "Are you sure you want to remove revisions from this post?"
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:334
+#: inc/class-wp-revisions-control.php:342
 msgid "Autosave"
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:335
+#: inc/class-wp-revisions-control.php:343
 msgid "There are no revisions to remove."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:336
+#: inc/class-wp-revisions-control.php:344
 msgid "An error occurred. Please refresh the page and try again."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:356
+#: inc/class-wp-revisions-control.php:364
 msgid "Purge these revisions"
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:362
+#: inc/class-wp-revisions-control.php:370
 #. translators: 1. Text input field.
 msgid ""
 "Limit this post to %1$s revisions. Leave this field blank for default "
 "behavior."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:387
+#: inc/class-wp-revisions-control.php:395
 msgid "No post ID was provided. Please refresh the page and try again."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:389
+#: inc/class-wp-revisions-control.php:397
 msgid "Invalid request. Please refresh the page and try again."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:391
+#: inc/class-wp-revisions-control.php:399
 msgid "You are not allowed to edit this post."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:423
+#: inc/class-wp-revisions-control.php:431
 #. translators: 1. Number of removed revisions, already formatted for locale.
 msgid "Removed %1$s revisions associated with this post."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:449
-#: inc/class-wp-revisions-control.php:461
+#: inc/class-wp-revisions-control.php:457
+#: inc/class-wp-revisions-control.php:469
 msgid "No revisions to remove."
 msgstr ""
 
-#: inc/class-wp-revisions-control.php:538
+#: inc/class-wp-revisions-control.php:546
 msgid "Number of revisions to retain."
 msgstr ""
 
diff --git a/src/js/gutenberg.js b/src/js/gutenberg.js
index bf71e6afe37f5506e9d776ba47754098f91ce387..74f8bf8a2cbe63638a8c7dc29467e82cff09eb52 100644
--- a/src/js/gutenberg.js
+++ b/src/js/gutenberg.js
@@ -1,8 +1,10 @@
-const { Button, TextControl } = require( '@wordpress/components' );
+const apiFetch = require( '@wordpress/api-fetch' );
+const { Button, Modal, TextControl } = require( '@wordpress/components' );
 const { compose } = require( '@wordpress/compose' );
 const { withSelect, withDispatch } = require( '@wordpress/data' );
 const { PluginDocumentSettingPanel } = require( '@wordpress/edit-post' );
-const { __ } = require( '@wordpress/i18n' );
+const { useState } = require( '@wordpress/element' );
+const { __, _n, sprintf } = require( '@wordpress/i18n' );
 const { registerPlugin } = require( '@wordpress/plugins' );
 
 const metaKey = '_wp_rev_ctl_limit';
@@ -21,14 +23,58 @@ const Render = ( { limit, manualPurge, showPurgeButton, updateMeta } ) => (
 			onChange={ updateMeta }
 		/>
 
-		{ showPurgeButton && (
-			<Button onClick={ manualPurge }>
+		{ showPurgeButton && PurgeModal( limit, manualPurge ) }
+	</PluginDocumentSettingPanel>
+);
+
+const PurgeModal = ( limit, manualPurge ) => {
+	const [ isOpen, setOpen ] = useState( false );
+	const openModal = () => setOpen( true );
+	const closeModal = () => setOpen( false );
+	const closeModalAndPurge = () => {
+		closeModal();
+		manualPurge();
+	};
+
+	/* translators: 1. Number of revisions to keep. */
+	const modalText = sprintf(
+		_n(
+			'This will remove all but the most-recent revision.',
+			'This will remove all but the %1$d most-recent revisions.',
+			parseInt( limit ),
+			'wp_revisions_control'
+		),
+		limit
+	);
+
+	return (
+		<>
+			<Button isSecondary onClick={ openModal }>
 				{ __( 'Purge excess revisions', 'wp_revisions_control' ) }
 			</Button>
-		) }
 
-	</PluginDocumentSettingPanel>
-);
+			{ isOpen && (
+				<Modal
+					title={ __( 'Purge excess revisions', 'wp_revisions_control' ) }
+					contentLabel={ modalText }
+					onRequestClose={ closeModal }
+				>
+					<p>
+						{ modalText }
+					</p>
+
+					<Button isSecondary onClick={ closeModal }>
+						{ __( 'Cancel', 'wp_revisions_control' ) }
+					</Button>
+
+					<Button isPrimary onClick={ closeModalAndPurge }>
+						{ __( 'Purge', 'wp_revisions_control' ) }
+					</Button>
+				</Modal>
+			) }
+		</>
+	)
+}
 
 const RevisionsControl = compose(
 	[
@@ -51,10 +97,36 @@ const RevisionsControl = compose(
 				showPurgeButton,
 			};
 		} ),
-		withDispatch( ( dispatch, { limit } ) => {
+		withDispatch( ( dispatch, { limit }, { select } ) => {
 			const manualPurge = () => {
-				// TODO: reuse the existing Ajax endpoint?
-				console.log( 'Purging!', limit );
+				const postId = select( 'core/editor' ).getCurrentPostId();
+
+				apiFetch( {
+					path: `/wp-revisions-control/v1/schedule/${postId}`,
+					method: 'PUT',
+				} )
+					.then( ( result ) => {
+						let noticeType;
+						let noticeText;
+
+						if ( result ) {
+							noticeType = 'success';
+							noticeText = __( 'Excess revisions scheduled for removal.', 'wp_revisions_control' );
+						} else {
+							noticeType = 'error';
+							noticeText = __( 'Failed to schedule excess revisions for removal.', 'wp_revisions_control' );
+						}
+
+						dispatch( 'core/notices' ).createNotice(
+							noticeType,
+							noticeText,
+							{
+								id: 'wp-revisions-control-scheduled-purge',
+								isDismissible: true,
+								type: 'snackbar',
+							}
+						);
+					} );
 			};
 
 			const updateMeta = ( value ) => {
diff --git a/webpack.mix.js b/webpack.mix.js
index e840360b450e2e703ed28baa31566a1be100d479..83342254bb67dbac275d140db68cda30e9d1e446 100644
--- a/webpack.mix.js
+++ b/webpack.mix.js
@@ -21,10 +21,12 @@ mix
 	} )
 	.webpackConfig( {
 		externals: {
+			'@wordpress/api-fetch': 'wp.apiFetch',
 			'@wordpress/components': 'wp.components',
 			'@wordpress/compose': 'wp.compose',
 			'@wordpress/data': 'wp.data',
 			'@wordpress/edit-post': 'wp.editPost',
+			'@wordpress/element': 'wp.element',
 			'@wordpress/i18n': 'wp.i18n',
 			'@wordpress/plugins': 'wp.plugins',
 			'react': 'React',