diff --git a/src/components/feature/FeatureAttachmentForm.vue b/src/components/feature/FeatureAttachmentForm.vue
index eb9d0ef402eab0ff57f5623e7088c0c10637ee4b..c75905f2db595f7cee272a5d245ede78a522024f 100644
--- a/src/components/feature/FeatureAttachmentForm.vue
+++ b/src/components/feature/FeatureAttachmentForm.vue
@@ -11,7 +11,7 @@
           <i class="ui times icon"></i>
         </button>
       </h4>
-      {{ form.errors }}
+      <!-- {{ form.errors }} -->
       <div class="visible-fields">
         <div class="two fields">
           <div class="required field">
@@ -25,7 +25,11 @@
               v-model="form.title.value"
               @change="updateStore"
             />
-            {{ form.title.errors }}
+            <ul :id="form.title.id_for_error" class="errorlist">
+              <li v-for="error in form.title.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
           <div class="required field">
             <label>Fichier (PDF, PNG, JPEG)</label>
@@ -46,7 +50,11 @@
               :name="form.attachment_file.html_name"
               :id="'attachment_file' + attachmentForm.dataKey"
             />
-            {{ form.attachment_file.errors }}
+            <ul :id="form.attachment_file.id_for_error" class="errorlist">
+              <li v-for="error in form.attachment_file.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
         </div>
         <div class="field">
@@ -57,7 +65,7 @@
             v-model="form.info.value"
             @change="updateStore"
           ></textarea>
-          {{ form.info.errors }}
+          <!-- {{ form.info.errors }} -->
         </div>
       </div>
     </div>
@@ -75,7 +83,8 @@ export default {
       fileToImport: null,
       form: {
         title: {
-          errors: null,
+          errors: [],
+          id_for_error: `errorlist-title-${this.attachmentForm.dataKey}`,
           id_for_label: "titre",
           field: {
             max_length: 30, // todo : vérifier dans django
@@ -85,10 +94,11 @@ export default {
           value: "",
         },
         attachment_file: {
-          errors: null,
+          errors: [],
+          id_for_error: `errorlist-file-${this.attachmentForm.dataKey}`,
           html_name: "titre",
           label: "Titre",
-          value: "",
+          value: null,
         },
         info: {
           value: "",
@@ -109,19 +119,22 @@ export default {
     initForm(attachmentForm) {
       for (let el in attachmentForm) {
         if (el && this.form[el]) {
-          this.form[el].value =
-            el === "attachment_file"
-              ? attachmentForm[el].split("/").pop() //* keep only the file name, not the path
-              : attachmentForm[el];
+          if (el === "attachment_file" && attachmentForm[el]) {
+            this.form[el].value = attachmentForm[el].split("/").pop(); //* keep only the file name, not the path
+          } else {
+            this.form[el].value = attachmentForm[el];
+          }
         }
       }
     },
+
     removeAttachmentFormset() {
       this.$store.commit(
         "feature/REMOVE_ATTACHMENT_FORM",
         this.attachmentForm.dataKey
       );
     },
+
     updateStore() {
       this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", {
         dataKey: this.attachmentForm.dataKey,
@@ -131,6 +144,7 @@ export default {
         fileToImport: this.fileToImport,
       });
     },
+
     onFileChange(e) {
       const files = e.target.files || e.dataTransfer.files;
       if (!files.length) return;
@@ -138,6 +152,28 @@ export default {
       this.form.attachment_file.value = files[0].name; //* add name to the form for display, in order to match format return from API
       this.updateStore();
     },
+
+    checkForm() {
+      let isValid = true;
+      if (this.form.title.value === "") {
+        this.form.title.errors = ["Veuillez compléter ce champ."];
+        document
+          .getElementById(this.form.title.id_for_error)
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        isValid = false;
+      } else if (this.form.attachment_file.value === null) {
+        this.form.attachment_file.errors = ["Veuillez compléter ce champ."];
+        this.form.title.errors = [];
+        document
+          .getElementById(this.form.attachment_file.id_for_error)
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        isValid = false;
+      } else {
+        this.form.title.errors = [];
+        this.form.attachment_file.errors = [];
+      }
+      return isValid;
+    },
   },
 
   mounted() {
diff --git a/src/components/feature/FeatureLinkedForm.vue b/src/components/feature/FeatureLinkedForm.vue
index 960f63371b388c965bfe623add3ba30b190030e4..80f10d776a4754d01cdcfb0d1568a566964d5e8b 100644
--- a/src/components/feature/FeatureLinkedForm.vue
+++ b/src/components/feature/FeatureLinkedForm.vue
@@ -10,7 +10,9 @@
         <i class="ui times icon"></i>
       </button>
     </h4>
-    {{ form.errors }}
+    <ul id="errorlist-links" class="errorlist">
+      <li v-for="error in form.errors" :key="error" v-html="error"></li>
+    </ul>
     <div class="visible-fields">
       <div class="two fields">
         <div class="required field">
@@ -53,8 +55,10 @@ export default {
   },
 
   computed: {
-    featureOptions: function() {
-      return this.features.map(el=> `${el.title} (${el.display_creator} - ${el.created_on})`) 
+    featureOptions: function () {
+      return this.features.map(
+        (el) => `${el.title} (${el.display_creator} - ${el.created_on})`
+      );
     },
     selected_relation_type: {
       // getter
@@ -92,7 +96,7 @@ export default {
           },
           html_name: "relation_type",
           label: "Type de liaison",
-          value: "",
+          value: "Doublon",
         },
         feature_to: {
           errors: null,
@@ -118,6 +122,19 @@ export default {
         feature_to: this.form.feature_to.value,
       });
     },
+    checkForm() {
+      if (this.form.feature_to.value === "") {
+        this.form.errors = [
+          "<strong>Choisir un signalement lié</strong><br/> Pourriez-vous choisir un signalement pour la nouvelle liaison ?",
+        ];
+        document
+          .getElementById("errorlist-links")
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        return false;
+      }
+      this.form.errors = [];
+      return true;
+    },
   },
 };
 </script>
\ No newline at end of file
diff --git a/src/components/feature_type/FeatureTypeCustomForm.vue b/src/components/feature_type/FeatureTypeCustomForm.vue
index fcdf282ddc87b864f5ade32573d867e6c2cf2455..140a62b0765e06417bb45fb7e03a1283aebac863 100644
--- a/src/components/feature_type/FeatureTypeCustomForm.vue
+++ b/src/components/feature_type/FeatureTypeCustomForm.vue
@@ -271,8 +271,6 @@ export default {
       return string.replace(/\s*,\s*/gi, ",");
     },
     checkCustomForm() {
-      console.log("checkCustomForm");
-      console.log(this.form);
       if (this.form.label.value === null) {
         this.form.label.errors = ["Veuillez compléter ce champ."];
         return false;
diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js
index df638cf266dfce9e82646000d260747c0adbc853..fd9ee2833a5d85f36bf828c538cf948cb6bff8a2 100644
--- a/src/store/modules/feature.js
+++ b/src/store/modules/feature.js
@@ -1,5 +1,5 @@
 const axios = require("axios");
-//import router from '../../router'
+import router from '../../router'
 
 
 const feature = {
@@ -93,19 +93,31 @@ const feature = {
           .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson)
           .then((response) => {
             if (response.status === 200 && response.data) {
-              console.log(response, response.data)
+              router.push({
+                name: "project_detail",
+                params: {
+                  slug: rootState.project_slug,
+                  message: "Le signalement a été mis à jour",
+                },
+              });
             }
           })
           .catch((error) => {
             throw error;
           });
-      } else {
-        axios
+        } else {
+          axios
           .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson)
           .then((response) => {
             if (response.status === 201 && response.data) {
-              console.log(response, response.data)
               dispatch("SEND_ATTACHMENTS", response.data.id)
+              router.push({
+                name: "project_detail",
+                params: {
+                  slug: rootState.project_slug,
+                  message: "Le signalement a été crée",
+                },
+              });
             }
           })
           .catch((error) => {
diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue
index c62a5b7c91bc52af32a0f6be8cdc0688ec9b9918..c13fb8bfeb046d852fde7a6f9c5d7e4c653b490f 100644
--- a/src/views/feature/Feature_edit.vue
+++ b/src/views/feature/Feature_edit.vue
@@ -30,7 +30,11 @@
               v-model="form.title.value"
               @blur="updateStore"
             />
-            {{ form.title.errors }}
+            <ul id="errorlist-title" class="errorlist">
+              <li v-for="error in form.title.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
           <div class="required field">
             <label :for="form.status.id_for_label">{{
@@ -41,8 +45,6 @@
               :selected="selected_status.name"
               :selection.sync="selected_status"
             />
-
-            {{ form.status.errors }}
           </div>
         </div>
         <div class="field">
@@ -55,7 +57,6 @@
             v-model="form.description.value"
             @blur="updateStore"
           ></textarea>
-          {{ form.description.errors }}
         </div>
 
         <!-- Geom Field -->
@@ -138,8 +139,11 @@
               <br />
             </span>
           </div>
-
-          {{ form.geom.errors }}
+          <ul id="errorlist-geom" class="errorlist">
+            <li v-for="error in form.geom.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
           <!-- Map -->
           <input
             type="hidden"
@@ -172,14 +176,12 @@
 
         <!-- Pièces jointes -->
         <div class="ui horizontal divider">PIÈCES JOINTES</div>
-        <!-- {{ attachment_formset.non_form_errors }} -->
-
         <div id="formsets-attachment">
-          <!-- {{ attachment_formset.management_form }} -->
           <FeatureAttachmentForm
             v-for="form in attachmentFormset"
             :key="form.dataKey"
             :attachmentForm="form"
+            ref="attachementForm"
           />
         </div>
 
@@ -194,15 +196,13 @@
 
         <!-- Signalements liés -->
         <div class="ui horizontal divider">SIGNALEMENTS LIÉS</div>
-        <!-- {{ linked_formset.non_form_errors }} -->
         <div id="formsets-link">
-          <!-- {{ linked_formset.management_form }} -->
-
           <FeatureLinkedForm
             v-for="form in linkedFormset"
             :key="form.dataKey"
             :linkedForm="form"
             :features="features"
+            ref="linkedForm"
           />
         </div>
         <button
@@ -275,7 +275,7 @@ export default {
       ],
       form: {
         title: {
-          errors: null,
+          errors: [],
           id_for_label: "name",
           field: {
             max_length: 30,
@@ -285,23 +285,25 @@ export default {
           value: "",
         },
         status: {
-          errors: null,
           id_for_label: "status",
           html_name: "status",
           label: "Statut",
-          value: "Brouillon",
+          value: {
+            value: "draft",
+            name: "Brouillon",
+          },
         },
         description: {
-          errors: null,
+          errors: [],
           id_for_label: "description",
           html_name: "description",
           label: "Description",
           value: "",
         },
         geom: {
+          errors: [],
           label: "Localisation",
           value: null,
-          errors: null,
         },
       },
     };
@@ -318,13 +320,13 @@ export default {
     ]),
     ...mapGetters("feature_type", ["feature_type"]),
 
-    field_title(){
-      if(this.feature_type){
-        if(this.feature_type.title_optional){
-          return 'field';
+    field_title() {
+      if (this.feature_type) {
+        if (this.feature_type.title_optional) {
+          return "field";
         }
       }
-      return 'required field';
+      return "required field";
     },
 
     currentRouteName() {
@@ -365,15 +367,15 @@ export default {
   methods: {
     initForm() {
       if (this.currentRouteName === "editer-signalement") {
-        for (let el in this.feature) {
-          if (el && this.form[el]) {
-            if (el === "status") {
-              const value = this.feature[el];
-              this.form[el].value = this.statusChoices.find(
-                (el) => el.value === value
+        for (let key in this.feature) {
+          if (key && this.form[key]) {
+            if (key === "status") {
+              const value = this.feature[key];
+              this.form[key].value = this.statusChoices.find(
+                (key) => key.value === value
               );
             } else {
-              this.form[el].value = this.feature[el];
+              this.form[key].value = this.feature[key];
             }
           }
         }
@@ -481,6 +483,56 @@ export default {
         feature_id: this.feature ? this.feature.feature_id : "",
       });
     },
+
+    checkFormTitle() {
+      if (this.form.title.value) {
+        this.form.title.errors = [];
+        return true;
+      } else if (
+        !this.form.title.errors.includes("Veuillez compléter ce champ.")
+      ) {
+        this.form.title.errors.push("Veuillez compléter ce champ.");
+        document
+          .getElementById("errorlist-title")
+          .scrollIntoView({ block: "end", inline: "nearest" });
+      }
+      return false;
+    },
+
+    checkFormGeom() {
+      if (this.form.geom.value) {
+        this.form.geom.errors = [];
+        return true;
+      } else if (
+        !this.form.geom.errors.includes("Valeur géométrique non valide.")
+      ) {
+        this.form.geom.errors.push("Valeur géométrique non valide.");
+        document
+          .getElementById("errorlist-geom")
+          .scrollIntoView({ block: "end", inline: "nearest" });
+      }
+      return false;
+    },
+
+    checkAddedForm() {
+      let isValid = true; //* fallback if all customForms returned true
+      if (this.$refs.attachementForm) {
+        for (const attachementForm of this.$refs.attachementForm) {
+          if (attachementForm.checkForm() === false) {
+            isValid = false;
+          }
+        }
+      }
+      if (this.$refs.linkedForm) {
+        for (const linkedForm of this.$refs.linkedForm) {
+          if (linkedForm.checkForm() === false) {
+            isValid = false;
+          }
+        }
+      }
+      return isValid;
+    },
+
     goBackToProject(message) {
       this.$router.push({
         name: "project_detail",
@@ -490,35 +542,25 @@ export default {
         },
       });
     },
-    async postForm() {
-      if (!this.feature_type.title_optional){
-        if (this.form.title.value && this.form.geom.value) {
-          this.form.title.errors = null;
-          await
-            this.$store.dispatch("feature/SEND_FEATURE")
-            .then(() => {
-              this.goBackToProject("Le signalement a été crée");
-          });
-        } else {
-          this.form.title.errors = "Veuillez compléter ce champ.";
-          this.form.geom.errors = "Veuillez compléter ce champ.";
-        }
+
+    postForm() {
+      let is_valid = true;
+      if (!this.feature_type.title_optional) {
+        is_valid =
+          this.checkFormTitle() &&
+          this.checkFormGeom() &&
+          this.checkAddedForm();
+      } else {
+        is_valid = this.checkFormGeom() && this.checkAddedForm();
       }
-      else{
-        if (this.form.geom.value) {
-          this.form.title.errors = null;
-          await
-            this.$store.dispatch("feature/SEND_FEATURE")
-            .then(() => {
-              this.goBackToProject("Le signalement a été crée");
-          });
-        } else {
-          this.form.geom.errors = "Veuillez compléter ce champ.";
-        }
-        
+
+      if (is_valid) {
+        this.$store.dispatch("feature/SEND_FEATURE", this.currentRouteName);
       }
     },
 
+    //* ************* MAP *************** *//
+
     onFeatureTypeLoaded() {
       var geomLeaflet = {
         point: "circlemarker",
@@ -797,13 +839,14 @@ export default {
       "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
       this.$route.params.slug_type_signal
     );
-    // todo : mutualize in store with feature_detail.vue
 
+    // todo : mutualize in store with feature_detail.vue
     if (this.$route.params.slug_signal) {
       featureAPI
         .getFeatureAttachments(this.$route.params.slug_signal)
         .then((data) => this.addExistingAttachementFormset(data));
-    } else { //* be sure that previous attachemntFormset has been cleared for creation
+    } else {
+      //* be sure that previous attachemntFormset has been cleared for creation
       this.$store.commit("feature/CLEAR_ATTACHMENT_FORM");
     }
   },
@@ -813,8 +856,6 @@ export default {
     this.initMap();
   },
 };
-
-// TODO : add script from django and convert:
 </script>
 
 <style>
diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue
index bd5791b9de65c0ca081ea1eb7ee8f1a3030b7fd2..af4f1bb3af32c801c6e2525cfcb350b4725dda71 100644
--- a/src/views/feature_type/Feature_type_edit.vue
+++ b/src/views/feature_type/Feature_type_edit.vue
@@ -325,7 +325,7 @@ export default {
         // * find feature_type and fill form values
         if (this.form[el]) this.form[el].value = formData[el];
       }
-      //! add custom fields using ONLY this function, incrementing dataKey for Vue updating correctly components
+      //! add custom fields using ONLY this function, incrementing dataKey for Vue to correctly update components
       formData.customfield_set.forEach((el) => this.addCustomForm(el));
       this.updateStore();
     },
@@ -359,7 +359,7 @@ export default {
         this.form.title.errors = [];
         return this.checkCustomForms(); //* if customForms are ok, validate, if get out function
       } else if (
-        !this.form.title.errors.includes("Veuillez compléter ce champ.") // TODO : Gérer les autres champs
+        !this.form.title.errors.includes("Veuillez compléter ce champ.")
       ) {
         this.form.title.errors.push("Veuillez compléter ce champ.");
         document
@@ -519,10 +519,10 @@ export default {
       }
       if (this.action === "duplicate") {
         //* replace original name with new default title
-        this.form.title.value += ` (Copie ${new Date()
+        this.form.title.value += ` (Copie-${new Date()
           .toLocaleString()
           .slice(0, -3)
-          .replace(",", "")} )`;
+          .replace(",", "")})`;
         this.updateStore(); // * initialize form in store in case this.form would not be modified
       }
     }
diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue
index 5f809f620d8295acfcbadd826439c8a331f377fa..980c4466ae1b23c98de00b13326a4908b01a2c9a 100644
--- a/src/views/project/Project_edit.vue
+++ b/src/views/project/Project_edit.vue
@@ -13,8 +13,6 @@
       <div class="two fields">
         <div class="required field">
           <label for="title">Titre</label>
-          <!-- <small>{{ form.title.help_text }}</small
-          > --><!-- | safe  // ? utile ?  -->
           <input
             type="text"
             required
@@ -23,7 +21,11 @@
             id="title"
             v-model="form.title"
           />
-          <!-- {{ form.title.errors }} -->
+          <ul id="errorlist-title" class="errorlist">
+            <li v-for="error in errors.title" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
         <div class="field">
           <label>Illustration du projet</label>
@@ -81,7 +83,6 @@
             />
             <div class="ui label">jour(s)</div>
           </div>
-          <!-- {{ form.archive_feature.errors }} -->
         </div>
         <div class="field">
           <label for="delete_feature">Délai avant suppression</label>
@@ -97,7 +98,6 @@
             />
             <div class="ui label">jour(s)</div>
           </div>
-          <!-- {{ form.delete_feature.errors }} -->
         </div>
         <div class="required field">
           <label for="access_level_pub_feature"
@@ -108,6 +108,11 @@
             :selected="form.access_level_pub_feature.name"
             :selection.sync="form.access_level_pub_feature"
           />
+          <ul id="errorlist-access_level_pub_feature" class="errorlist">
+            <li v-for="error in errors.access_level_pub_feature" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
         <div class="required field">
           <label for="access_level_arch_feature">
@@ -118,6 +123,11 @@
             :selected="form.access_level_arch_feature.name"
             :selection.sync="form.access_level_arch_feature"
           />
+          <ul id="errorlist-access_level_arch_feature" class="errorlist">
+            <li v-for="error in errors.access_level_arch_feature" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
       </div>
 
@@ -181,6 +191,11 @@ export default {
         name: "Sélectionner une image ...",
         size: 0,
       },
+      errors: {
+        title: [],
+        access_level_pub_feature: [],
+        access_level_arch_feature: [],
+      },
       form: {
         title: "",
         slug: "",
@@ -208,7 +223,7 @@ export default {
 
   computed: {
     ...mapGetters(["project"]),
-    DJANGO_BASE_URL:function () {
+    DJANGO_BASE_URL: function () {
       return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
     },
   },
@@ -224,8 +239,8 @@ export default {
       }
     },
     truncate(n, len) {
-      var ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
-      var filename = n.replace("." + ext, "");
+      let ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
+      let filename = n.replace("." + ext, "");
       if (filename.length <= len) {
         return n;
       }
@@ -288,7 +303,27 @@ export default {
       }
     },
 
+    checkForm() {
+      for (const key in this.errors) {
+        if ((key === "title" && this.form[key]) || this.form[key].value) {
+          this.errors[key] = [];
+        } else if (!this.errors[key].length) {
+          this.errors[key].push(
+            key === "title"
+              ? "Veuillez compléter ce champ."
+              : "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles."
+          );
+          document
+            .getElementById(`errorlist-${key}`)
+            .scrollIntoView({ block: "end", inline: "nearest" });
+          return false;
+        }
+      }
+      return true;
+    },
+
     async postForm() {
+      if (!this.checkForm()) return;
       // todo: check form
       //let url = `${configuration.VUE_APP_DJANGO_API_BASE}projects/`;
       const projectData = {
@@ -304,13 +339,16 @@ export default {
 
       if (this.action === "create" || this.action === "duplicate") {
         await axios
-          .post(`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`, projectData)
+          .post(
+            `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`,
+            projectData
+          )
           .then((response) => {
             if (response && response.status === 201 && response.data) {
               //* send thumbnail after feature_type was created
-              if (this.fileToImport.size > 0){
+              if (this.fileToImport.size > 0) {
                 this.postProjectThumbnail(response.data.slug);
-              }else {
+              } else {
                 this.goBackNrefresh(response.data.slug);
               }
             }
@@ -342,15 +380,23 @@ export default {
 
   created() {
     this.definePageType();
+    console.log(this.action);
     if (this.action === "create") {
       this.thumbnailFileSrc = require("@/assets/img/default.png");
-    } else if (this.action === "edit") {
+    } else if (this.action === "edit" || this.action === "create_from") {
       if (!this.project) {
         this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
       }
-      this.form = this.project;
-      /* this.form.thumbnail = //* add api base to display image src
-        configuration.VUE_APP_DJANGO_BASE + this.form.thumbnail; */
+      this.form = { ...this.project }; //* create a new object to avoid modifying original one
+      if (this.action === "create_from") {
+        this.form.title =
+          this.project.title +
+          ` (Copie-${new Date()
+            .toLocaleString()
+            .slice(0, -3)
+            .replace(",", "")})`;
+        this.form.is_project_type = false;
+      }
       //* transform string values to objects for dropdowns display (could be in a computed)
       this.form.access_level_pub_feature = {
         name: this.project.access_level_pub_feature,
diff --git a/src/views/project/Project_type_list.vue b/src/views/project/Project_type_list.vue
index 3909eab7ac7c1f54d274d63744467eda05df91a0..1af8b5d584bdd52d5995c7d18c5824b4da86128d 100644
--- a/src/views/project/Project_type_list.vue
+++ b/src/views/project/Project_type_list.vue
@@ -21,7 +21,7 @@
                 :to="{
                   name: 'project_create_from',
                   params: {
-                    slug: project.title,
+                    slug: project.slug,
                   },
                 }"
                 >{{ project.title }}</router-link