diff --git a/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateService.java b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateService.java
index bc678a0455..42c03c8a55 100644
--- a/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateService.java
+++ b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateService.java
@@ -1,6 +1,10 @@
 package se.su.dsv.scipro.grading;
 
+import se.su.dsv.scipro.report.DuplicateDateException;
 import se.su.dsv.scipro.report.GradingReportTemplate;
+import se.su.dsv.scipro.report.ValidDateMustBeInTheFutureException;
+import se.su.dsv.scipro.report.NoSuchTemplateException;
+import se.su.dsv.scipro.report.TemplateLockedException;
 import se.su.dsv.scipro.system.ProjectType;
 
 import java.time.LocalDate;
@@ -22,4 +26,11 @@ public interface GradingReportTemplateService {
     LocalDate getEndDate(GradingReportTemplate gradingReportTemplate);
 
     List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType);
+
+    GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
+            throws
+            ValidDateMustBeInTheFutureException,
+            NoSuchTemplateException,
+            DuplicateDateException,
+            TemplateLockedException;
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateUpdate.java b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateUpdate.java
new file mode 100644
index 0000000000..357ea9a04e
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateUpdate.java
@@ -0,0 +1,54 @@
+package se.su.dsv.scipro.grading;
+
+import jakarta.annotation.Nullable;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Objects;
+
+public record GradingReportTemplateUpdate(
+        LocalDate validFrom,
+        @Nullable String note,
+        String defaultGrade,
+        List<Grade> grades,
+        List<Criteria> criteria)
+{
+    public GradingReportTemplateUpdate {
+        Objects.requireNonNull(validFrom, "Valid from must not be null");
+        Objects.requireNonNull(defaultGrade, "Default grade must not be null");
+        Objects.requireNonNull(grades, "Grades must not be null");
+        Objects.requireNonNull(criteria, "Criteria must not be null");
+
+        for (Grade grade1 : grades) {
+            for (Grade grade2 : grades) {
+                if (grade1 != grade2 && grade1.minimumPoints() == grade2.minimumPoints()) {
+                    throw new IllegalArgumentException("Duplicate minimum points on grades: %s and %s".formatted(
+                            grade1.grade(),
+                            grade2.grade()));
+                }
+            }
+        }
+    }
+
+    public record Grade(String grade, int minimumPoints) {
+        public Grade {
+            Objects.requireNonNull(grade, "Grade must not be null");
+        }
+    }
+
+    public record Criteria(LocalizedString title, @Nullable Flag flag, List<Requirement> requirements)
+    {
+        public enum Flag {OPPOSITION, REFLECTION}
+
+        public Criteria {
+            Objects.requireNonNull(title, "Title must not be null");
+            Objects.requireNonNull(requirements, "Requirements must not be null");
+        }
+
+        public record Requirement(int points, LocalizedString description) {
+            public Requirement {
+                Objects.requireNonNull(description, "Description must not be null");
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/grading/LocalizedString.java b/core/src/main/java/se/su/dsv/scipro/grading/LocalizedString.java
new file mode 100644
index 0000000000..cb777b5c65
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/grading/LocalizedString.java
@@ -0,0 +1,10 @@
+package se.su.dsv.scipro.grading;
+
+import java.util.Objects;
+
+public record LocalizedString(String english, String swedish) {
+    public LocalizedString {
+        Objects.requireNonNull(english, "English must not be null");
+        Objects.requireNonNull(swedish, "Swedish must not be null");
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/DuplicateDateException.java b/core/src/main/java/se/su/dsv/scipro/report/DuplicateDateException.java
new file mode 100644
index 0000000000..6a1959586d
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/DuplicateDateException.java
@@ -0,0 +1,23 @@
+package se.su.dsv.scipro.report;
+
+import se.su.dsv.scipro.system.ProjectType;
+
+import java.time.LocalDate;
+
+public class DuplicateDateException extends Throwable {
+    private final LocalDate validFrom;
+    private final ProjectType projectType;
+
+    public DuplicateDateException(LocalDate validFrom, ProjectType projectType) {
+        this.validFrom = validFrom;
+        this.projectType = projectType;
+    }
+
+    public LocalDate validFrom() {
+        return validFrom;
+    }
+
+    public ProjectType projectType() {
+        return projectType;
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java
index 83f61b9362..7c06a09631 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java
@@ -5,6 +5,7 @@ import com.google.inject.persist.Transactional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
 import se.su.dsv.scipro.grading.GradingBasis;
 import se.su.dsv.scipro.grading.GradingReportTemplateService;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
 import se.su.dsv.scipro.grading.ThesisSubmissionHistoryService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.Language;
@@ -237,4 +238,37 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
             return gradingReportTemplateRepo.getTemplatesValidAfter(projectType, current.getValidFrom());
         }
     }
+
+    @Override
+    public GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
+            throws ValidDateMustBeInTheFutureException,
+            NoSuchTemplateException,
+            DuplicateDateException,
+            TemplateLockedException
+    {
+        LocalDate today = LocalDate.now(clock);
+        if (!update.validFrom().isAfter(today)) {
+            throw new ValidDateMustBeInTheFutureException(update.validFrom(), today.plusDays(1));
+        }
+
+        GradingReportTemplate template = gradingReportTemplateRepo.getTemplateById(templateId);
+        if (template == null) {
+            throw new NoSuchTemplateException();
+        }
+
+        GradingReportTemplate currentTemplate = gradingReportTemplateRepo.getCurrentTemplate(
+                template.getProjectType(),
+                update.validFrom());
+        if (currentTemplate.getId() != templateId &&
+            Objects.equals(currentTemplate.getValidFrom(), update.validFrom()))
+        {
+            throw new DuplicateDateException(update.validFrom(), template.getProjectType());
+        }
+
+        if (!template.getValidFrom().isAfter(today)) {
+            throw new TemplateLockedException(template.getValidFrom());
+        }
+
+        return gradingReportTemplateRepo.updateTemplate(templateId, update);
+    }
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplate.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplate.java
index 9816ddacdb..0bd1da6bfb 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplate.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplate.java
@@ -67,7 +67,7 @@ public class GradingReportTemplate extends DomainObject {
         return gradingCriterionTemplate;
     }
 
-    public Iterable<GradingCriterionTemplate> getCriteria() {
+    public Collection<GradingCriterionTemplate> getCriteria() {
         return criteria;
     }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepo.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepo.java
index 0f5a8f2332..320f8150e4 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepo.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepo.java
@@ -1,6 +1,7 @@
 package se.su.dsv.scipro.report;
 
 import org.springframework.data.jpa.repository.JpaRepository;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.ProjectType;
 
@@ -15,4 +16,8 @@ public interface GradingReportTemplateRepo extends JpaRepository<GradingReportTe
     GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate);
 
     List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date);
+
+    GradingReportTemplate getTemplateById(long templateId);
+
+    GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update);
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepoImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepoImpl.java
index 4fd2548fa2..6da82c20d6 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepoImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateRepoImpl.java
@@ -1,7 +1,9 @@
 package se.su.dsv.scipro.report;
 
+import com.google.inject.persist.Transactional;
 import com.querydsl.jpa.JPAExpressions;
 import com.querydsl.jpa.JPQLQuery;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.GenericRepo;
 
@@ -53,4 +55,16 @@ public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemp
         return findAll(QGradingReportTemplate.gradingReportTemplate.projectType.eq(projectType)
                 .and(QGradingReportTemplate.gradingReportTemplate.validFrom.gt(date)));
     }
+
+    @Override
+    public GradingReportTemplate getTemplateById(long templateId) {
+        return findOne(templateId);
+    }
+
+    @Override
+    @Transactional
+    public GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update) {
+        GradingReportTemplate gradingReportTemplate = findOne(templateId);
+        return gradingReportTemplate;
+    }
 }
\ No newline at end of file
diff --git a/core/src/main/java/se/su/dsv/scipro/report/NoSuchTemplateException.java b/core/src/main/java/se/su/dsv/scipro/report/NoSuchTemplateException.java
new file mode 100644
index 0000000000..0013f826d2
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/NoSuchTemplateException.java
@@ -0,0 +1,4 @@
+package se.su.dsv.scipro.report;
+
+public class NoSuchTemplateException extends Exception {
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/TemplateLockedException.java b/core/src/main/java/se/su/dsv/scipro/report/TemplateLockedException.java
new file mode 100644
index 0000000000..217cdff1da
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/TemplateLockedException.java
@@ -0,0 +1,15 @@
+package se.su.dsv.scipro.report;
+
+import java.time.LocalDate;
+
+public class TemplateLockedException extends Exception {
+    private final LocalDate becameValidAt;
+
+    public TemplateLockedException(LocalDate becameValidAt) {
+        this.becameValidAt = becameValidAt;
+    }
+
+    public LocalDate becameValidAt() {
+        return becameValidAt;
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/ValidDateMustBeInTheFutureException.java b/core/src/main/java/se/su/dsv/scipro/report/ValidDateMustBeInTheFutureException.java
new file mode 100644
index 0000000000..dcaee0092b
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/ValidDateMustBeInTheFutureException.java
@@ -0,0 +1,21 @@
+package se.su.dsv.scipro.report;
+
+import java.time.LocalDate;
+
+public class ValidDateMustBeInTheFutureException extends Exception {
+    private final LocalDate validFrom;
+    private final LocalDate earliestAllowedValidFrom;
+
+    public ValidDateMustBeInTheFutureException(LocalDate validFrom, LocalDate earliestAllowedValidFrom) {
+        this.validFrom = validFrom;
+        this.earliestAllowedValidFrom = earliestAllowedValidFrom;
+    }
+
+    public LocalDate validFrom() {
+        return validFrom;
+    }
+
+    public LocalDate earliestAllowedValidFrom() {
+        return earliestAllowedValidFrom;
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.html
index c61838afca..9e28766880 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.html
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.html
@@ -2,6 +2,7 @@
 <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
 <body>
 <wicket:extend>
+    <div wicket:id="feedback"></div>
     <form wicket:id="form">
         <div wicket:id="editing" class="mb-3"></div>
         <div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.java
index eb61e470a1..d9ab2a9ab7 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.java
@@ -1,15 +1,29 @@
 package se.su.dsv.scipro.admin.pages.grading;
 
 import jakarta.inject.Inject;
+import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
 import se.su.dsv.scipro.grading.GradingReportTemplateService;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
+import se.su.dsv.scipro.grading.LocalizedString;
+import se.su.dsv.scipro.report.DuplicateDateException;
 import se.su.dsv.scipro.report.GradingReportTemplate;
+import se.su.dsv.scipro.report.ValidDateMustBeInTheFutureException;
+import se.su.dsv.scipro.report.NoSuchTemplateException;
+import se.su.dsv.scipro.report.TemplateLockedException;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
     private static final String GRADING_REPORT_TEMPLATE_ID_PARAMETER = "id";
+    public static final GradingReportTemplateUpdate.Criteria.Requirement ZERO_REQUIREMENT = new GradingReportTemplateUpdate.Criteria.Requirement(
+            0,
+            new LocalizedString("", ""));
 
     @Inject
     GradingReportTemplateService gradingReportTemplateService;
@@ -23,10 +37,27 @@ public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage imple
 
         editingGradingTemplate = new EditingGradingTemplate(template);
 
+        add(new FeedbackPanel("feedback"));
+
         Form<EditingGradingTemplate> form = new Form<>("form") {
             @Override
             protected void onSubmit() {
                 super.onSubmit();
+                try {
+                    GradingReportTemplateUpdate update = toUpdate(
+                            editingGradingTemplate);
+
+                    gradingReportTemplateService.update(id, update);
+                    success(getString("template_updated"));
+                } catch (ValidDateMustBeInTheFutureException e) {
+                    error(getString("valid_from_must_be_in_the_future", () -> e));
+                } catch (NoSuchTemplateException e) {
+                    throw new RestartResponseException(AdminGradingTemplatesOverviewPage.class);
+                } catch (DuplicateDateException e) {
+                    error(getString("another_template_exists_for_the_given_date_date", () -> e));
+                } catch (TemplateLockedException e) {
+                    error(getString("template_is_locked", () -> e));
+                }
                 System.out.println(editingGradingTemplate);
             }
         };
@@ -35,6 +66,58 @@ public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage imple
         add(form);
     }
 
+    private GradingReportTemplateUpdate toUpdate(EditingGradingTemplate editingGradingTemplate) {
+        List<GradingReportTemplateUpdate.Grade> grades = editingGradingTemplate
+                .getGradeLimits()
+                .getGradeLimits()
+                .stream()
+                .map(this::toGrade)
+                .toList();
+        List<GradingReportTemplateUpdate.Criteria> criteria = editingGradingTemplate
+                .getCriteria()
+                .stream()
+                .map(this::toCriteria)
+                .toList();
+        return new GradingReportTemplateUpdate(
+                editingGradingTemplate.getValidFrom(),
+                editingGradingTemplate.getNote(),
+                editingGradingTemplate.getGradeLimits().getDefaultGrade(),
+                grades,
+                criteria);
+    }
+
+    private GradingReportTemplateUpdate.Criteria toCriteria(EditingGradingTemplate.Criteria criteria) {
+        ArrayList<GradingReportTemplateUpdate.Criteria.Requirement> requirements = new ArrayList<>();
+        requirements.add(ZERO_REQUIREMENT);
+        for (int i = 0; i < criteria.getPoints().size(); i++) {
+            EditingGradingTemplate.Criteria.Point point = criteria.getPoints().get(i);
+            requirements.add(new GradingReportTemplateUpdate.Criteria.Requirement(
+                    i + 1,
+                    new LocalizedString(point.getRequirementEn(), point.getRequirementSv())));
+        }
+        return new GradingReportTemplateUpdate.Criteria(
+                new LocalizedString(criteria.getTitleEn(), criteria.getTitleSv()),
+                getFlag(criteria),
+                requirements);
+    }
+
+    private static GradingReportTemplateUpdate.Criteria.Flag getFlag(EditingGradingTemplate.Criteria criteria) {
+        if (criteria.getFlag() == null) {
+            return null;
+        }
+        return switch (criteria.getFlag()) {
+            case OPPOSITION -> GradingReportTemplateUpdate.Criteria.Flag.OPPOSITION;
+            case REFLECTION -> GradingReportTemplateUpdate.Criteria.Flag.REFLECTION;
+            //case null -> null; sigh old versions of Java
+        };
+    }
+
+    private GradingReportTemplateUpdate.Grade toGrade(GradeLimits.GradeLimit gradeLimit) {
+        return new GradingReportTemplateUpdate.Grade(
+                gradeLimit.getGrade(),
+                gradeLimit.getLowerLimit());
+    }
+
     public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
         PageParameters pageParameters = new PageParameters();
         pageParameters.add(GRADING_REPORT_TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.utf8.properties
new file mode 100644
index 0000000000..a9420cf6bd
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.utf8.properties
@@ -0,0 +1,4 @@
+template_updated=Template updated
+valid_from_must_be_in_the_future=The templates valid date must be in the future. The given date was ${validFrom} but it must be at least ${earliestAllowedValidFrom}.
+another_template_exists_for_the_given_date_date=There is already another ${projectType.name} template that becomes valid at ${validFrom}, please pick another date.
+template_is_locked=You can not edit templates that have become current. The template you are trying to edit became current at ${validFrom}.