diff --git a/ui/app/models/namespace.js b/ui/app/models/namespace.js
index 0cfc5949d8..5a2b2174b6 100644
--- a/ui/app/models/namespace.js
+++ b/ui/app/models/namespace.js
@@ -5,19 +5,23 @@
 
 import Model, { attr } from '@ember-data/model';
 import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
+import { withModelValidations } from 'vault/decorators/model-validations';
 
 @withExpandedAttributes()
+@withModelValidations({
+  path: [
+    { type: 'presence', message: `Path can't be blank.` },
+    { type: 'endsInSlash', message: `Path can't end in forward slash '/'.` },
+    {
+      type: 'containsWhiteSpace',
+      message: "Path can't contain whitespace.",
+    },
+  ],
+})
 export default class NamespaceModel extends Model {
-  @attr('string', {
-    validationAttr: 'pathIsValid',
-    invalidMessage: 'You have entered and invalid path please only include letters, numbers, -, ., and _.',
-  })
+  @attr('string')
   path;
 
-  get pathIsValid() {
-    return this.path && this.path.match(/^[\w\d-.]+$/g);
-  }
-
   get fields() {
     return ['path'].map((f) => this.allByKey[f]);
   }
diff --git a/ui/lib/core/addon/components/edit-form.js b/ui/lib/core/addon/components/edit-form.js
index e71c09f7d8..12f8b8205d 100644
--- a/ui/lib/core/addon/components/edit-form.js
+++ b/ui/lib/core/addon/components/edit-form.js
@@ -15,6 +15,9 @@ export default Component.extend({
   layout,
   flashMessages: service(),
 
+  // internal validations
+  invalidFormAlert: '',
+  modelValidations: null,
   // public API
   model: null,
   successMessage: 'Saved!',
@@ -38,10 +41,25 @@ export default Component.extend({
   // is the case, set this value to true
   callOnSaveAfterRender: false,
 
+  checkModelValidity(model) {
+    if (model.validate) {
+      const { isValid, state, invalidFormMessage } = model.validate();
+      this.set('modelValidations', state);
+      this.set('invalidFormAlert', invalidFormMessage);
+      return isValid;
+    }
+    // no validations on model; return true
+    return true;
+  },
+
   save: task(
     waitFor(function* (model, options = { method: 'save' }) {
       const { method } = options;
       const messageKey = method === 'save' ? 'successMessage' : 'deleteSuccessMessage';
+      if (method === 'save' && !this.checkModelValidity(model)) {
+        // if saving and model invalid, don't continue
+        return;
+      }
       try {
         yield model[method]();
       } catch (err) {
diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs
index 7c9e4b5af0..31be930f44 100644
--- a/ui/lib/core/addon/components/form-field.hbs
+++ b/ui/lib/core/addon/components/form-field.hbs
@@ -281,12 +281,6 @@
           class="input {{if this.validationError 'has-error-border'}}"
           maxLength={{@attr.options.characterLimit}}
         />
-        {{! TODO: explore removing in favor of new model validations pattern since it is only used on the namespace model }}
-        {{#if @attr.options.validationAttr}}
-          {{#if (and (get @model this.valuePath) (not (get @model @attr.options.validationAttr)))}}
-