diff --git a/core/src/main/java/modules/RepositoryModule.java b/core/src/main/java/modules/RepositoryModule.java
index 83f7f4539b..2ae13bc0cb 100644
--- a/core/src/main/java/modules/RepositoryModule.java
+++ b/core/src/main/java/modules/RepositoryModule.java
@@ -25,6 +25,8 @@ import se.su.dsv.scipro.report.GradingReportTemplateRepo;
 import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
 import se.su.dsv.scipro.report.OppositionReportRepo;
 import se.su.dsv.scipro.report.OppositionReportRepoImpl;
+import se.su.dsv.scipro.report.SupervisorGradingReportRepository;
+import se.su.dsv.scipro.report.SupervisorGradingReportRepositoryImpl;
 import se.su.dsv.scipro.reviewing.DecisionRepository;
 import se.su.dsv.scipro.reviewing.DecisionRepositoryImpl;
 import se.su.dsv.scipro.reviewing.ReviewerTargetRepository;
@@ -59,5 +61,6 @@ public class RepositoryModule extends AbstractModule {
         bind(FinalSeminarRepository.class).to(FinalSeminarRepositoryImpl.class);
         bind(ReviewerTargetRepository.class).to(ReviewerTargetRepositoryImpl.class);
         bind(DecisionRepository.class).to(DecisionRepositoryImpl.class);
+        bind(SupervisorGradingReportRepository.class).to(SupervisorGradingReportRepositoryImpl.class);
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
index 3a878fc72b..6720ffac7e 100644
--- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
+++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
@@ -10,6 +10,7 @@ import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
 import se.su.dsv.scipro.profiles.CurrentProfile;
 import se.su.dsv.scipro.profiles.Profiles;
 import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.report.AbstractGradingCriterion;
 import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
 import se.su.dsv.scipro.report.GradingReportTemplate;
 import se.su.dsv.scipro.security.auth.roles.Roles;
@@ -20,6 +21,7 @@ import jakarta.inject.Provider;
 import jakarta.persistence.EntityManager;
 import java.time.LocalDate;
 import java.time.LocalTime;
+import java.time.Month;
 import java.util.*;
 
 public class DataInitializer implements Lifecycle {
@@ -232,19 +234,20 @@ public class DataInitializer implements Lifecycle {
     }
 
     private void createGradingCriterionTemplateIfNotDone() {
-        save(getBachelorTemplate(0));
-        save(getBachelorTemplate(ProjectType.HP_15));
-        save(getBachelorTemplate(ProjectType.HP_30));
-        save(getMasterTemplate(0));
-        save(getMasterTemplate(ProjectType.HP_15));
-        save(getMasterTemplate(ProjectType.HP_30));
-        save(getMagisterTemplate(0));
-        save(getMagisterTemplate(ProjectType.HP_15));
-        save(getMagisterTemplate(ProjectType.HP_30));
+        save(getBachelorTemplate());
+        save(getBachelorTemplate());
+        save(getBachelorTemplate());
+        save(getMasterTemplate());
+        save(getMasterTemplate());
+        save(getMasterTemplate());
+        save(getMagisterTemplate());
+        save(getMagisterTemplate());
+        save(getMagisterTemplate());
     }
 
-    private GradingReportTemplate getBachelorTemplate(int credits) {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelorClass, credits);
+    private GradingReportTemplate getBachelorTemplate() {
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelorClass,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -401,7 +404,7 @@ public class DataInitializer implements Lifecycle {
                 .description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
                 .descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
                 .build());
-        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.OPPOSITION);
 
         gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -441,13 +444,14 @@ public class DataInitializer implements Lifecycle {
                 .description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
                 .descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
                 .build());
-        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, gradingCriterionPointTemplates).setFx(false);
+        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplate;
     }
 
-    private GradingReportTemplate getMasterTemplate(int credits) {
-        GradingReportTemplate gradingReportTemplateMaster = new GradingReportTemplate(masterClass, credits);
+    private GradingReportTemplate getMasterTemplate() {
+        GradingReportTemplate gradingReportTemplateMaster = new GradingReportTemplate(masterClass,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -629,7 +633,8 @@ public class DataInitializer implements Lifecycle {
                 .description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
                 .descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
                 .build());
-        gradingReportTemplateMaster.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
+        gradingReportTemplateMaster.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates,
+                AbstractGradingCriterion.Flag.OPPOSITION);
 
         gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -669,13 +674,14 @@ public class DataInitializer implements Lifecycle {
                 .description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
                 .descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
                 .build());
-        gradingReportTemplateMaster.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates).setFx(false);
+        gradingReportTemplateMaster.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplateMaster;
     }
 
-    private GradingReportTemplate getMagisterTemplate(int credits) {
-        GradingReportTemplate gradingReportTemplateMagister = new GradingReportTemplate(magisterClass, credits);
+    private GradingReportTemplate getMagisterTemplate() {
+        GradingReportTemplate gradingReportTemplateMagister = new GradingReportTemplate(magisterClass,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -837,7 +843,8 @@ public class DataInitializer implements Lifecycle {
                 .description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
                 .descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
                 .build());
-        gradingReportTemplateMagister.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
+        gradingReportTemplateMagister.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates,
+                AbstractGradingCriterion.Flag.OPPOSITION);
 
         gradingCriterionPointTemplates = initPointTemplates();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
@@ -877,7 +884,7 @@ public class DataInitializer implements Lifecycle {
                 .description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
                 .descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
                 .build());
-        gradingReportTemplateMagister.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates).setFx(false);
+        gradingReportTemplateMagister.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplateMagister;
     }
diff --git a/core/src/main/java/se/su/dsv/scipro/grading/GradingModule.java b/core/src/main/java/se/su/dsv/scipro/grading/GradingModule.java
index 7f0448f149..e67d559b77 100644
--- a/core/src/main/java/se/su/dsv/scipro/grading/GradingModule.java
+++ b/core/src/main/java/se/su/dsv/scipro/grading/GradingModule.java
@@ -3,6 +3,7 @@ package se.su.dsv.scipro.grading;
 import com.google.inject.Key;
 import com.google.inject.PrivateModule;
 import com.google.inject.name.Names;
+import se.su.dsv.scipro.report.GradingReportServiceImpl;
 
 public class GradingModule extends PrivateModule {
     @Override
@@ -29,5 +30,8 @@ public class GradingModule extends PrivateModule {
         bind(NationalSubjectCategoryRepository.class).to(NationalSubjectCategoryRepositoryImpl.class);
         bind(NationalSubjectCategoryService.class).to(NationalSubjectCategoryServiceImpl.class);
         expose(NationalSubjectCategoryService.class);
+
+        bind(GradingReportTemplateService.class).to(GradingReportServiceImpl.class);
+        expose(GradingReportTemplateService.class);
     }
 }
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
new file mode 100644
index 0000000000..1c23b7c0a5
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateService.java
@@ -0,0 +1,41 @@
+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;
+import java.util.List;
+
+public interface GradingReportTemplateService {
+    List<ProjectType> getProjectTypes();
+
+    GradingReportTemplate getCurrentTemplate(ProjectType projectType);
+
+    GradingReportTemplate getTemplate(long templateId);
+
+    /**
+     * Returns the end date of this grading report template.
+     * End date is specified as the date before another template takes over.
+     *
+     * @return the end date of this grading report template, possibly {@code null}
+     */
+    LocalDate getEndDate(GradingReportTemplate gradingReportTemplate);
+
+    List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType);
+
+    GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
+            throws
+            ValidDateMustBeInTheFutureException,
+            NoSuchTemplateException,
+            DuplicateDateException,
+            TemplateLockedException;
+
+    GradingReportTemplate create(ProjectType projectType, GradingReportTemplateUpdate update)
+            throws
+            ValidDateMustBeInTheFutureException,
+            DuplicateDateException;
+}
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..ce7a1b70aa
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/grading/GradingReportTemplateUpdate.java
@@ -0,0 +1,61 @@
+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 failingGrade,
+        List<GradeLimit> gradeLimits,
+        List<Criteria> criteria)
+{
+    public GradingReportTemplateUpdate {
+        Objects.requireNonNull(validFrom, "Valid from must not be null");
+        Objects.requireNonNull(failingGrade, "Failing grade must not be null");
+        Objects.requireNonNull(gradeLimits, "Grades must not be null");
+        Objects.requireNonNull(criteria, "Criteria must not be null");
+
+        for (GradeLimit gradeLimit1 : gradeLimits) {
+            for (GradeLimit gradeLimit2 : gradeLimits) {
+                if (gradeLimit1 != gradeLimit2 && gradeLimit1.minimumPoints() == gradeLimit2.minimumPoints()) {
+                    throw new IllegalArgumentException("Duplicate minimum points on grades: %s and %s".formatted(
+                            gradeLimit1.grade(),
+                            gradeLimit2.grade()));
+                }
+            }
+        }
+    }
+
+    public record GradeLimit(String grade, int minimumPoints) {
+        public GradeLimit {
+            Objects.requireNonNull(grade, "Grade must not be null");
+        }
+    }
+
+    public record Criteria(
+            LocalizedString title,
+            Type type,
+            int minimumPointsRequiredToPass,
+            @Nullable Flag flag,
+            List<Requirement> requirements)
+    {
+        public enum Type {THESIS, INDIVIDUAL}
+        public enum Flag {OPPOSITION, REFLECTION}
+
+        public Criteria {
+            Objects.requireNonNull(title, "Title must not be null");
+            Objects.requireNonNull(type, "Type 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/AbstractGradingCriterion.java b/core/src/main/java/se/su/dsv/scipro/report/AbstractGradingCriterion.java
index cda224a3b7..64d2f0433b 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/AbstractGradingCriterion.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/AbstractGradingCriterion.java
@@ -1,17 +1,42 @@
 package se.su.dsv.scipro.report;
 
 import jakarta.persistence.Basic;
+import jakarta.persistence.Column;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
 import jakarta.persistence.MappedSuperclass;
 
 @MappedSuperclass
 public abstract class AbstractGradingCriterion extends AbstractCriterion {
 
+    public enum Flag {
+        /**
+         * Criterion marked with this flag will add extra functionality related
+         * to the authors submitted reflection. It should only be used on
+         * individual criteria.
+         */
+        REFLECTION,
+        /**
+         * Criterion marked with this flag will add extra functionality related
+         * to the authors performed opposition. For example, it will have its
+         * points and feedback filled in when the seminar supervisor assesses
+         * their opposition and submitted report. It should only be used on
+         * individual criteria.
+         */
+        OPPOSITION
+    }
+
     @Basic(optional = false)
     protected int pointsRequiredToPass;
 
     @Basic
     private boolean fx = true;
 
+    @Basic
+    @Column(name = "flag")
+    @Enumerated(EnumType.STRING)
+    private Flag flag;
+
     protected AbstractGradingCriterion() {
 
     }
@@ -21,6 +46,17 @@ public abstract class AbstractGradingCriterion extends AbstractCriterion {
         this.pointsRequiredToPass = pointsRequiredToPass;
     }
 
+    protected AbstractGradingCriterion(
+            String title,
+            String titleEn,
+            Integer sortOrder,
+            int pointsRequiredToPass,
+            Flag flag)
+    {
+        this(title, titleEn, sortOrder, pointsRequiredToPass);
+        this.flag = flag;
+    }
+
     public abstract boolean isProjectCriterion();
 
     public abstract boolean isIndividualCriterion();
@@ -35,6 +71,14 @@ public abstract class AbstractGradingCriterion extends AbstractCriterion {
         return this.fx;
     }
 
+    public Flag getFlag() {
+        return flag;
+    }
+
+    public void setFlag(Flag flag) {
+        this.flag = flag;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (o == this) return true;
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..1957362903
--- /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 Exception {
+    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/GradeCalculatorServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradeCalculatorServiceImpl.java
index 569afb79b7..c2a6362c83 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradeCalculatorServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradeCalculatorServiceImpl.java
@@ -1,5 +1,6 @@
 package se.su.dsv.scipro.report;
 
+import jakarta.inject.Inject;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.report.calculators.original.SupervisorBachelorGradeCalculator;
 import se.su.dsv.scipro.report.calculators.original.SupervisorMaster15GradeCalculator;
@@ -8,8 +9,21 @@ import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
 
 public class GradeCalculatorServiceImpl implements GradeCalculatorService {
+
+    private final GradingReportService gradingReportTemplateService;
+
+    @Inject
+    public GradeCalculatorServiceImpl(GradingReportService gradingReportService) {
+        this.gradingReportTemplateService = gradingReportService;
+    }
+
     @Override
     public GradeCalculator getSupervisorCalculator(final Project project) {
+        GradingReportTemplate template = gradingReportTemplateService.getTemplate(project);
+        if (!template.getGradeLimits().isEmpty()) {
+            return new GradingReportTemplateGradeCalculator(template);
+        }
+
         DegreeType degreeType = project.getProjectType().getDegreeType();
         if (degreeType == DegreeType.BACHELOR) {
             if (getYear(project) >= 2017) {
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradeLimit.java b/core/src/main/java/se/su/dsv/scipro/report/GradeLimit.java
new file mode 100644
index 0000000000..49d6b60097
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradeLimit.java
@@ -0,0 +1,46 @@
+package se.su.dsv.scipro.report;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "grading_report_template_grade_limits")
+public class GradeLimit {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(name = "grade")
+    private String grade;
+
+    @Column(name = "lower_limit")
+    private int lowerLimit;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getGrade() {
+        return grade;
+    }
+
+    public void setGrade(String grade) {
+        this.grade = grade;
+    }
+
+    public int getLowerLimit() {
+        return lowerLimit;
+    }
+
+    public void setLowerLimit(int lowerLimit) {
+        this.lowerLimit = lowerLimit;
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingCriterion.java b/core/src/main/java/se/su/dsv/scipro/report/GradingCriterion.java
index d54ff820ad..aad898db4c 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingCriterion.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingCriterion.java
@@ -30,7 +30,7 @@ public abstract class GradingCriterion extends AbstractGradingCriterion {
     }
 
     GradingCriterion(GradingReport gradingReport, GradingCriterionTemplate gradingCriterionTemplate) {
-        super(gradingCriterionTemplate.getTitle(), gradingCriterionTemplate.getTitleEn(), gradingCriterionTemplate.getSortOrder(), gradingCriterionTemplate.getPointsRequiredToPass());
+        super(gradingCriterionTemplate.getTitle(), gradingCriterionTemplate.getTitleEn(), gradingCriterionTemplate.getSortOrder(), gradingCriterionTemplate.getPointsRequiredToPass(), gradingCriterionTemplate.getFlag());
         this.gradingReport = gradingReport;
         for (GradingCriterionPointTemplate pointTemplate : gradingCriterionTemplate.getGradingCriterionPointTemplates()) {
             gradingCriterionPoints.add(new GradingCriterionPoint(
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReport.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReport.java
index 4c4b70397c..0c036ede48 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReport.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReport.java
@@ -15,8 +15,14 @@ import java.util.stream.Collectors;
 @Entity
 public abstract class GradingReport extends Report {
 
-    public enum Grade {
-        A, B, C, D, E, F, FX
+    public record Grade(String name) {
+        public static final Grade A = new Grade("A");
+        public static final Grade B = new Grade("B");
+        public static final Grade C = new Grade("C");
+        public static final Grade D = new Grade("D");
+        public static final Grade E = new Grade("E");
+        public static final Grade F = new Grade("F");
+        public static final Grade FX = new Grade("FX");
     }
 
     public enum State { INITIAL, REVIEWING, FINALIZED }
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java
index a2c1ac5c4e..c82432c25e 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java
@@ -3,14 +3,13 @@ package se.su.dsv.scipro.report;
 import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
 import se.su.dsv.scipro.grading.GradingBasis;
 import se.su.dsv.scipro.project.Project;
-import se.su.dsv.scipro.system.GenericService;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.Either;
 
 import java.time.Instant;
 import java.util.List;
 
-public interface GradingReportService extends GenericService<GradingReport, Long> {
+public interface GradingReportService {
 
     SupervisorGradingReport getSupervisorGradingReport(Project project, User student);
 
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 b57d2bee0c..6f9b1a9824 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
@@ -2,54 +2,60 @@ package se.su.dsv.scipro.report;
 
 import com.google.common.eventbus.EventBus;
 import com.google.inject.persist.Transactional;
-import jakarta.persistence.EntityManager;
 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.AbstractServiceImpl;
 import se.su.dsv.scipro.system.Language;
+import se.su.dsv.scipro.system.ProjectModule;
+import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.ProjectTypeService;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.Either;
 
 import jakarta.inject.Inject;
-import jakarta.inject.Named;
-import jakarta.inject.Provider;
 import java.time.Clock;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.util.*;
 
-@Named
-public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport, Long> implements GradingReportService {
+public class GradingReportServiceImpl implements GradingReportTemplateService, GradingReportService {
 
-    public static final String OPPOSITION_SWEDISH = "Ö1 Oppositionsrapport";
-    public static final String OPPOSITION_ENGLISH = "Ö1 Opposition report";
     private final EventBus eventBus;
     private final ThesisSubmissionHistoryService thesisSubmissionHistoryService;
     private final Clock clock;
+    private final SupervisorGradingReportRepository supervisorGradingReportRepository;
+    private final GradingReportTemplateRepo gradingReportTemplateRepo;
+    private final ProjectTypeService projectTypeService;
 
     @Inject
     public GradingReportServiceImpl(
-            Provider<EntityManager> em,
             EventBus eventBus,
             ThesisSubmissionHistoryService thesisSubmissionHistoryService,
-            Clock clock)
+            Clock clock,
+            SupervisorGradingReportRepository supervisorGradingReportRepository,
+            GradingReportTemplateRepo gradingReportTemplateRepo,
+            ProjectTypeService projectTypeService)
     {
-        super(em, GradingReport.class, QGradingReport.gradingReport);
         this.eventBus = eventBus;
         this.thesisSubmissionHistoryService = thesisSubmissionHistoryService;
         this.clock = clock;
+        this.supervisorGradingReportRepository = supervisorGradingReportRepository;
+        this.gradingReportTemplateRepo = gradingReportTemplateRepo;
+        this.projectTypeService = projectTypeService;
     }
 
     @Override
     public boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
         for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) {
-            boolean isOppositionCriterion = gradingCriterion.getTitle().equals(OPPOSITION_SWEDISH) || gradingCriterion.getTitle().equals(OPPOSITION_ENGLISH);
+            boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION;
             boolean betterGrade = gradingCriterion.getPoints() == null || opposition.getPoints() > gradingCriterion.getPoints();
             if (isOppositionCriterion && betterGrade) {
                 gradingCriterion.setFeedback(opposition.getFeedback());
                 gradingCriterion.setPoints(opposition.getPoints());
-                save(report);
+                supervisorGradingReportRepository.save(report);
                 return true;
             }
         }
@@ -88,7 +94,7 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
             }
             supervisorGradingReport.setMotivation(gradingBasis.getOverallMotivation());
             supervisorGradingReport.setRejectionCommentFeedback(gradingBasis.getRejectionCommentFeedback());
-            save(supervisorGradingReport);
+            supervisorGradingReportRepository.save(supervisorGradingReport);
         }
         return getGradingBasis(project);
     }
@@ -138,22 +144,18 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
     @Override
     @Transactional
     public SupervisorGradingReport getSupervisorGradingReport(Project project, User user) {
-        SupervisorGradingReport supervisorGradingReport = from(QSupervisorGradingReport.supervisorGradingReport)
-                .where(QSupervisorGradingReport.supervisorGradingReport.user.eq(user).and(
-                        QSupervisorGradingReport.supervisorGradingReport.project.eq(project)))
-                .fetchOne();
+        SupervisorGradingReport supervisorGradingReport = supervisorGradingReportRepository.getReport(project, user);
         if (supervisorGradingReport == null) {
-            supervisorGradingReport = save(getTemplate(project).createSupervisorReport(project, user));
+            GradingReportTemplate template = getTemplate(project);
+            SupervisorGradingReport supervisorReport = template.createSupervisorReport(project, user);
+            supervisorGradingReport = supervisorGradingReportRepository.save(supervisorReport);
         }
         return supervisorGradingReport;
     }
 
     @Override
     public GradingReportTemplate getTemplate(Project project) {
-        QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
-        return from(template)
-                .where(template.projectType.eq(project.getProjectType()).and(template.credits.eq(project.getCredits())))
-                .fetchOne();
+        return gradingReportTemplateRepo.getTemplate(project);
     }
 
     @Override
@@ -192,11 +194,100 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
                     rejectionCommentFeedback);
         }
 
-        save(supervisorGradingReport);
+        supervisorGradingReportRepository.save(supervisorGradingReport);
         return Either.right(supervisorGradingReport);
     }
 
     private static boolean isBlank(final String str) {
         return str == null || str.isBlank();
     }
+
+    @Override
+    public List<ProjectType> getProjectTypes() {
+        return projectTypeService.findWithModule(ProjectModule.GRADING);
+    }
+
+    @Override
+    public GradingReportTemplate getCurrentTemplate(ProjectType projectType) {
+        return gradingReportTemplateRepo.getCurrentTemplate(projectType, LocalDate.now(clock));
+    }
+
+    @Override
+    public GradingReportTemplate getTemplate(long templateId) {
+        return gradingReportTemplateRepo.findOne(templateId);
+    }
+
+    @Override
+    public LocalDate getEndDate(GradingReportTemplate gradingReportTemplate) {
+        GradingReportTemplate next = gradingReportTemplateRepo.getNextTemplate(gradingReportTemplate);
+        if (next == null) {
+            return null;
+        } else {
+            return next.getValidFrom().minusDays(1);
+        }
+    }
+
+    @Override
+    public List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType) {
+        GradingReportTemplate current = getCurrentTemplate(projectType);
+        if (current == null) {
+            return gradingReportTemplateRepo.getTemplatesValidAfter(projectType, LocalDate.now(clock));
+        } else {
+            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);
+    }
+
+    @Override
+    public GradingReportTemplate create(ProjectType projectType, GradingReportTemplateUpdate update)
+            throws ValidDateMustBeInTheFutureException, DuplicateDateException
+    {
+        LocalDate today = LocalDate.now(clock);
+        if (!update.validFrom().isAfter(today)) {
+            throw new ValidDateMustBeInTheFutureException(update.validFrom(), today.plusDays(1));
+        }
+
+        GradingReportTemplate currentTemplate = gradingReportTemplateRepo.getCurrentTemplate(
+                projectType,
+                update.validFrom());
+        if (currentTemplate != null &&
+            Objects.equals(currentTemplate.getValidFrom(), update.validFrom()))
+        {
+            throw new DuplicateDateException(update.validFrom(), projectType);
+        }
+
+        return gradingReportTemplateRepo.createTemplate(projectType, 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 0598728f2c..cc106b6c95 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
@@ -7,6 +7,9 @@ import se.su.dsv.scipro.system.ProjectType;
 import se.su.dsv.scipro.system.User;
 
 import jakarta.persistence.*;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -23,22 +26,35 @@ public class GradingReportTemplate extends DomainObject {
     @OneToOne(optional = false)
     private ProjectType projectType;
 
-    @OneToMany(mappedBy = "gradingReportTemplate", cascade = {CascadeType.ALL})
+    @OneToMany(mappedBy = "gradingReportTemplate", cascade = {CascadeType.ALL}, orphanRemoval = true)
     private Collection<GradingCriterionTemplate> criteria = new HashSet<>();
 
+    @Temporal(TemporalType.DATE)
+    @Column(name = "valid_from")
+    private LocalDate validFrom;
+
     @Basic
-    private int credits;
+    @Column(name = "note")
+    private String note;
+
+    @Basic
+    @Column(name = "failing_grade")
+    private String failingGrade;
+
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
+    @JoinColumn(name = "grading_report_template_id")
+    private Collection<GradeLimit> gradeLimits = new ArrayList<>();
 
     protected GradingReportTemplate() {
 
     }
 
-    public GradingReportTemplate(ProjectType projectType, int credits) {
+    public GradingReportTemplate(ProjectType projectType, LocalDate validFrom) {
         if (projectType == null) {
             throw new IllegalArgumentException("ProjectType may not be null");
         }
         this.projectType = projectType;
-        this.credits = credits;
+        this.validFrom = validFrom;
     }
 
     public SupervisorGradingReport createSupervisorReport(Project project, User student) {
@@ -53,18 +69,28 @@ public class GradingReportTemplate extends DomainObject {
     }
 
     public GradingCriterionTemplate addProjectCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates) {
+        return addProjectCriterion(title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates, null);
+    }
+
+    public GradingCriterionTemplate addProjectCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates, AbstractGradingCriterion.Flag flag) {
         GradingCriterionTemplate gradingCriterionTemplate = new ProjectGradingCriterionTemplate(this, criteria.size(), title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates);
+        gradingCriterionTemplate.setFlag(flag);
         criteria.add(gradingCriterionTemplate);
         return gradingCriterionTemplate;
     }
 
     public GradingCriterionTemplate addIndividualCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates) {
+        return addIndividualCriterion(title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates, null);
+    }
+
+    public GradingCriterionTemplate addIndividualCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates, AbstractGradingCriterion.Flag flag) {
         GradingCriterionTemplate gradingCriterionTemplate = new IndividualGradingCriterionTemplate(this, criteria.size(), title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates);
+        gradingCriterionTemplate.setFlag(flag);
         criteria.add(gradingCriterionTemplate);
         return gradingCriterionTemplate;
     }
 
-    public Iterable<GradingCriterionTemplate> getCriteria() {
+    public Collection<GradingCriterionTemplate> getCriteria() {
         return criteria;
     }
 
@@ -73,6 +99,46 @@ public class GradingReportTemplate extends DomainObject {
         return this.id;
     }
 
+    public LocalDate getValidFrom() {
+        return validFrom;
+    }
+
+    public void setValidFrom(LocalDate validFrom) {
+        this.validFrom = validFrom;
+    }
+
+    public ProjectType getProjectType() {
+        return projectType;
+    }
+
+    public void setProjectType(ProjectType projectType) {
+        this.projectType = projectType;
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+
+    public String getFailingGrade() {
+        return failingGrade;
+    }
+
+    public void setFailingGrade(String failingGrade) {
+        this.failingGrade = failingGrade;
+    }
+
+    public Collection<GradeLimit> getGradeLimits() {
+        return gradeLimits;
+    }
+
+    public void setGradeLimits(Collection<GradeLimit> gradeLimits) {
+        this.gradeLimits = gradeLimits;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (o == this) return true;
@@ -93,6 +159,6 @@ public class GradingReportTemplate extends DomainObject {
 
     @Override
     public String toString() {
-        return "GradingReportTemplate(id=" + this.id + ", projectType=" + this.projectType + ", credits=" + this.credits + ")";
+        return "GradingReportTemplate(id=" + this.id + ", projectType=" + this.projectType + ", validFrom=" + this.validFrom + ")";
     }
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateGradeCalculator.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateGradeCalculator.java
new file mode 100644
index 0000000000..777770eb3b
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportTemplateGradeCalculator.java
@@ -0,0 +1,39 @@
+package se.su.dsv.scipro.report;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+class GradingReportTemplateGradeCalculator implements GradeCalculator {
+    private final GradingReportTemplate template;
+
+    GradingReportTemplateGradeCalculator(GradingReportTemplate template) {
+        this.template = template;
+    }
+
+    @Override
+    public GradingReport.Grade getGrade(GradingReport gradingReport) {
+        for (GradingCriterion gradingCriterion : gradingReport.getGradingCriteria()) {
+            if (!gradingCriterion.meetsMinimumPointRequirement()) {
+                return new GradingReport.Grade(template.getFailingGrade());
+            }
+        }
+        long points = getPoints(gradingReport);
+        String textualGrade = template.getGradeLimits()
+                .stream()
+                .filter(gradeLimit -> points >= gradeLimit.getLowerLimit())
+                .max(Comparator.comparing(GradeLimit::getLowerLimit))
+                .map(GradeLimit::getGrade)
+                .orElseGet(template::getFailingGrade);
+        return new GradingReport.Grade(textualGrade);
+    }
+
+    @Override
+    public long getPoints(GradingReport gradingReport) {
+        return gradingReport.getGradingCriteria()
+                .stream()
+                .map(GradingCriterion::getPoints)
+                .filter(Objects::nonNull)
+                .mapToInt(Integer::intValue)
+                .sum();
+    }
+}
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 9f6f8d1238..9ae1d6560f 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,8 +1,25 @@
 package se.su.dsv.scipro.report;
 
 import se.su.dsv.scipro.system.JpaRepository;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
+import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.ProjectType;
 
+import java.time.LocalDate;
+import java.util.List;
+
 public interface GradingReportTemplateRepo extends JpaRepository<GradingReportTemplate, Long> {
-    GradingReportTemplate findByProjectTypeAndCredits(ProjectType projectType, int credits);
+    GradingReportTemplate getTemplate(Project project);
+
+    GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now);
+
+    GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate);
+
+    List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date);
+
+    GradingReportTemplate getTemplateById(long templateId);
+
+    GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update);
+
+    GradingReportTemplate createTemplate(ProjectType projectType, 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 170ef05806..8cf96941a1 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,12 +1,19 @@
 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;
-import se.su.dsv.scipro.system.ProjectType;
 
 import jakarta.inject.Inject;
 import jakarta.inject.Provider;
 import jakarta.persistence.EntityManager;
-import jakarta.persistence.TypedQuery;
+import se.su.dsv.scipro.system.ProjectType;
+
+import java.time.LocalDate;
+import java.util.List;
 
 public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemplate, Long> implements GradingReportTemplateRepo {
     @Inject
@@ -15,10 +22,114 @@ public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemp
     }
 
     @Override
-    public GradingReportTemplate findByProjectTypeAndCredits(ProjectType projectType, int credits) {
-        TypedQuery<GradingReportTemplate> query = em().createQuery("select distinct template from GradingReportTemplate template where template.projectType = :projectType and template.credits = :credits", GradingReportTemplate.class);
-        query.setParameter("projectType", projectType);
-        query.setParameter("credits", credits);
-        return query.getSingleResult();
+    public GradingReportTemplate getTemplate(Project project) {
+        return getCurrentTemplate(project.getProjectType(), project.getStartDate());
+    }
+
+    @Override
+    public GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now) {
+        QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
+        // find the latest template that is valid for the project
+        JPQLQuery<LocalDate> validFrom = JPAExpressions
+                .select(template.validFrom.max())
+                .from(template)
+                .where(template.projectType.eq(projectType)
+                        .and(template.validFrom.loe(now)));
+        return findOne(template.projectType.eq(projectType).and(template.validFrom.eq(validFrom)));
+    }
+
+    @Override
+    public GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate) {
+        QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
+        // find the latest template that is valid for the project
+        JPQLQuery<LocalDate> validFrom = JPAExpressions
+                .select(template.validFrom.min())
+                .from(template)
+                .where(template.projectType.eq(gradingReportTemplate.getProjectType())
+                        .and(template.validFrom.gt(gradingReportTemplate.getValidFrom())));
+        return findOne(template.projectType.eq(gradingReportTemplate.getProjectType()).and(template.validFrom.eq(validFrom)));
+    }
+
+    @Override
+    public List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date) {
+        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 updateTemplate(gradingReportTemplate, update);
+    }
+
+    private GradingReportTemplate updateTemplate(
+            GradingReportTemplate gradingReportTemplate,
+            GradingReportTemplateUpdate update)
+    {
+        gradingReportTemplate.setValidFrom(update.validFrom());
+        gradingReportTemplate.setNote(update.note());
+        gradingReportTemplate.setFailingGrade(update.failingGrade());
+
+        gradingReportTemplate.getCriteria().clear();
+        for (var criteria : update.criteria()) {
+            final List<GradingCriterionPointTemplate> pointTemplates = criteria.requirements()
+                    .stream()
+                    .map(this::toPointTemplate)
+                    .toList();
+            AbstractGradingCriterion.Flag flag = criteria.flag() == null ? null : switch (criteria.flag()) {
+                case OPPOSITION -> AbstractGradingCriterion.Flag.OPPOSITION;
+                case REFLECTION -> AbstractGradingCriterion.Flag.REFLECTION;
+                //case null -> null; sigh java 17
+            };
+            switch (criteria.type()) {
+                case THESIS -> {
+                    gradingReportTemplate.addProjectCriterion(
+                            criteria.title().swedish(),
+                            criteria.title().english(),
+                            criteria.minimumPointsRequiredToPass(),
+                            pointTemplates,
+                            flag);
+                }
+                case INDIVIDUAL -> {
+                    gradingReportTemplate.addIndividualCriterion(
+                            criteria.title().swedish(),
+                            criteria.title().english(),
+                            criteria.minimumPointsRequiredToPass(),
+                            pointTemplates,
+                            flag);
+                }
+            }
+        }
+
+        gradingReportTemplate.getGradeLimits().clear();
+        for (var grade : update.gradeLimits()) {
+            GradeLimit gradeLimit = new GradeLimit();
+            gradeLimit.setGrade(grade.grade());
+            gradeLimit.setLowerLimit(grade.minimumPoints());
+            gradingReportTemplate.getGradeLimits().add(gradeLimit);
+        }
+
+        return save(gradingReportTemplate);
+    }
+
+    @Override
+    @Transactional
+    public GradingReportTemplate createTemplate(ProjectType projectType, GradingReportTemplateUpdate update) {
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, update.validFrom());
+        return updateTemplate(gradingReportTemplate, update);
+    }
+
+    private GradingCriterionPointTemplate toPointTemplate(GradingReportTemplateUpdate.Criteria.Requirement requirement) {
+        return new GradingCriterionPointTemplate.Builder()
+                .point(requirement.points())
+                .description(requirement.description().swedish())
+                .descriptionEn(requirement.description().english())
+                .build();
     }
 }
\ 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/OppositionReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/OppositionReportServiceImpl.java
index bf1321d744..417e7a15bf 100644
--- a/core/src/main/java/se/su/dsv/scipro/report/OppositionReportServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/report/OppositionReportServiceImpl.java
@@ -33,7 +33,7 @@ public class OppositionReportServiceImpl implements OppositionReportService {
         if (oppositionReport != null) {
             return oppositionReport;
         } else {
-            OppositionReport newReport = gradingReportTemplateRepo.findByProjectTypeAndCredits(finalSeminarOpposition.getProjectType(), finalSeminarOpposition.getProject().getCredits())
+            OppositionReport newReport = gradingReportTemplateRepo.getTemplate(finalSeminarOpposition.getProject())
                     .createOppositionReport(finalSeminarOpposition);
             return oppositionReportRepo.save(newReport);
         }
diff --git a/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepository.java b/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepository.java
new file mode 100644
index 0000000000..2415d52712
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepository.java
@@ -0,0 +1,10 @@
+package se.su.dsv.scipro.report;
+
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.system.User;
+
+public interface SupervisorGradingReportRepository {
+    SupervisorGradingReport save(SupervisorGradingReport report);
+
+    SupervisorGradingReport getReport(Project project, User author);
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepositoryImpl.java b/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepositoryImpl.java
new file mode 100644
index 0000000000..be7c252236
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/report/SupervisorGradingReportRepositoryImpl.java
@@ -0,0 +1,37 @@
+package se.su.dsv.scipro.report;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+import jakarta.persistence.EntityManager;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.system.AbstractRepository;
+import se.su.dsv.scipro.system.User;
+
+public class SupervisorGradingReportRepositoryImpl extends AbstractRepository
+        implements SupervisorGradingReportRepository
+{
+    @Inject
+    public SupervisorGradingReportRepositoryImpl(Provider<EntityManager> em) {
+        super(em);
+    }
+
+    @Override
+    public SupervisorGradingReport save(SupervisorGradingReport report) {
+        EntityManager entityManager = em();
+        if (entityManager.contains(report)) {
+            return entityManager.merge(report);
+        }
+        else {
+            entityManager.persist(report);
+            return report;
+        }
+    }
+
+    @Override
+    public SupervisorGradingReport getReport(Project project, User author) {
+        return from(QSupervisorGradingReport.supervisorGradingReport)
+                .where(QSupervisorGradingReport.supervisorGradingReport.user.eq(author).and(
+                        QSupervisorGradingReport.supervisorGradingReport.project.eq(project)))
+                .fetchOne();
+    }
+}
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/core/src/main/resources/db/migration/V389_1__grading_report_template_valid_timespans.sql b/core/src/main/resources/db/migration/V389_1__grading_report_template_valid_timespans.sql
new file mode 100644
index 0000000000..04e804ea63
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_1__grading_report_template_valid_timespans.sql
@@ -0,0 +1,46 @@
+# For any given date there is only ever one valid template for a project type
+# That template is valid until the date a new template is valid from
+ALTER TABLE `grading_report_template`
+    ADD COLUMN `valid_from` DATE NOT NULL DEFAULT '2001-01-01';
+
+CREATE TEMPORARY TABLE pt_points
+(
+    pt_id   INT,
+    credits INT,
+    count   INT
+);
+
+# Count how many projects/credits each project type has
+INSERT INTO pt_points
+SELECT pt.id, credits, COUNT(*)
+FROM ProjectType pt
+         INNER JOIN project p ON pt.id = p.projectType_id
+WHERE pt.id IN (SELECT projectType_id FROM grading_report_template)
+GROUP BY pt.id, credits;
+
+CREATE TEMPORARY TABLE grading_templates_to_keep
+(
+    id INT
+);
+
+# Keep the most used grading template for each project type (based on projects/credits)
+INSERT INTO grading_templates_to_keep (id)
+SELECT t.id
+FROM grading_report_template t
+WHERE credits = (SELECT credits
+                 FROM pt_points m
+                 WHERE m.pt_id = t.projectType_id
+                   AND m.`count` = (SELECT MAX(count)
+                                    FROM pt_points n
+                                    WHERE n.pt_id = t.projectType_id));
+
+DELETE FROM grading_criterion_point_template WHERE gradingCriterionTemplate_id IN (SELECT id FROM grading_criterion_template WHERE gradingReportTemplate_id NOT IN (SELECT id FROM grading_templates_to_keep));
+DELETE FROM grading_criterion_template WHERE gradingReportTemplate_id NOT IN (SELECT id FROM grading_templates_to_keep);
+DELETE FROM grading_report_template WHERE id NOT IN (SELECT id FROM grading_templates_to_keep);
+
+DROP TABLE grading_templates_to_keep;
+DROP TABLE pt_points;
+
+ALTER TABLE `grading_report_template`
+    DROP COLUMN `credits`,
+    ADD UNIQUE `UK_only_one_template_per_date_and_type` (`projectType_id`, `valid_from`);
diff --git a/core/src/main/resources/db/migration/V389_2__failing_grade_and_note_on_templates.sql b/core/src/main/resources/db/migration/V389_2__failing_grade_and_note_on_templates.sql
new file mode 100644
index 0000000000..31d85e8edf
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_2__failing_grade_and_note_on_templates.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `grading_report_template`
+    ADD COLUMN `note` TEXT,
+    ADD COLUMN `failing_grade` VARCHAR(32);
diff --git a/core/src/main/resources/db/migration/V389_3__grade_limits_on_grading_templates.sql b/core/src/main/resources/db/migration/V389_3__grade_limits_on_grading_templates.sql
new file mode 100644
index 0000000000..80a2cc6439
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_3__grade_limits_on_grading_templates.sql
@@ -0,0 +1,10 @@
+CREATE TABLE `grading_report_template_grade_limits` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT,
+    `grading_report_template_id` BIGINT, -- can't be NOT NULL because of Hibernate using an INSERT followed by an UPDATE
+    `grade` VARCHAR(32) NOT NULL,
+    `lower_limit` INT NOT NULL,
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `UK_one_grade_per_template` (`grading_report_template_id`, `grade`),
+    FOREIGN KEY `FK_grade_limit_grading_report_template `(`grading_report_template_id`)
+        REFERENCES `grading_report_template` (`id`) ON DELETE CASCADE
+);
diff --git a/core/src/main/resources/db/migration/V389_4__grading_criterion_flags.sql b/core/src/main/resources/db/migration/V389_4__grading_criterion_flags.sql
new file mode 100644
index 0000000000..9faf8c121e
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_4__grading_criterion_flags.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `grading_criterion_template`
+    ADD COLUMN `flag` VARCHAR(64);
+ALTER TABLE `GradingCriterion`
+    ADD COLUMN `flag` VARCHAR(64);
diff --git a/core/src/main/resources/db/migration/V389_5__migrate_opposition_criteria_to_flag.sql b/core/src/main/resources/db/migration/V389_5__migrate_opposition_criteria_to_flag.sql
new file mode 100644
index 0000000000..d4ffffc008
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_5__migrate_opposition_criteria_to_flag.sql
@@ -0,0 +1,2 @@
+UPDATE `GradingCriterion` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';
+UPDATE `grading_criterion_template` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';
diff --git a/core/src/main/resources/db/migration/V389_6__migrate_reflection_criteria_to_flag.sql b/core/src/main/resources/db/migration/V389_6__migrate_reflection_criteria_to_flag.sql
new file mode 100644
index 0000000000..0ad60ce957
--- /dev/null
+++ b/core/src/main/resources/db/migration/V389_6__migrate_reflection_criteria_to_flag.sql
@@ -0,0 +1,2 @@
+UPDATE `GradingCriterion` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';
+UPDATE `grading_criterion_template` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';
diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java
index 07fdc37c8c..97bdd1b8e2 100644
--- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java
@@ -13,6 +13,7 @@ import se.su.dsv.scipro.test.IntegrationTest;
 
 import jakarta.inject.Inject;
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.Date;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -52,7 +53,8 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
     }
 
     private GradingReportTemplate createGradingReportTemplate() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType,
+                LocalDate.of(2024, Month.JANUARY, 1));
         return save(gradingReportTemplate);
     }
 
diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplIntegrationTest.java
index 31b189eaaf..a3fc78016c 100644
--- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplIntegrationTest.java
@@ -15,6 +15,7 @@ import se.su.dsv.scipro.test.IntegrationTest;
 
 import jakarta.inject.Inject;
 import java.time.LocalDate;
+import java.time.Month;
 import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.List;
@@ -316,7 +317,8 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
     }
 
     private GradingReportTemplate createGradingReportTemplate(ProjectType projectType) {
-        GradingReportTemplate template = new GradingReportTemplate(projectType, 30);
+        GradingReportTemplate template = new GradingReportTemplate(projectType,
+                LocalDate.of(2024, Month.JANUARY, 1));
         return save(template);
     }
 
diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplTest.java
index 89d7aefc1a..a0f5b8c592 100755
--- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarServiceImplTest.java
@@ -29,6 +29,8 @@ import se.su.dsv.scipro.test.InstanceProvider;
 import jakarta.persistence.EntityManager;
 import java.lang.reflect.Field;
 import java.time.Clock;
+import java.time.LocalDate;
+import java.time.Month;
 import java.time.ZonedDateTime;
 import java.util.*;
 
@@ -137,7 +139,8 @@ public class FinalSeminarServiceImplTest {
 
         FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
         DomainObjects.injectId(finalSeminarOpposition, 1L);
-        finalSeminarOpposition.setOppositionReport(new OppositionReport(new GradingReportTemplate(new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor"), 1), finalSeminarOpposition));
+        finalSeminarOpposition.setOppositionReport(new OppositionReport(new GradingReportTemplate(new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor"),
+                LocalDate.of(2024, Month.JANUARY, 1)), finalSeminarOpposition));
         finalSeminar.setOppositions(Collections.singletonList(finalSeminarOpposition));
 
         seminarService.delete(finalSeminar);
diff --git a/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java
index 6bd8a3f8a8..765513cc4e 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java
@@ -15,14 +15,13 @@ import se.su.dsv.scipro.util.Either;
 
 import jakarta.inject.Inject;
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.*;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static se.su.dsv.scipro.report.GradingReportServiceImpl.OPPOSITION_ENGLISH;
-import static se.su.dsv.scipro.report.GradingReportServiceImpl.OPPOSITION_SWEDISH;
 
 public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
 
@@ -82,7 +81,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         addOppositionCriterion();
         boolean updated = updateOppositionCriterion();
 
-        GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
+        GradingCriterion oppositionCriterion = findOppositionCriterion();
         assert oppositionCriterion != null;
         assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
         assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
@@ -94,7 +93,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         addOppositionCriterion();
         boolean updated = updateOppositionCriterion();
 
-        GradingCriterion oppositionCriterion = findEnglishOppositionCriterion(OPPOSITION_ENGLISH);
+        GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report");
         assert oppositionCriterion != null;
         assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
         assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
@@ -107,7 +106,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         assessAllCriteria(gradingReport);
         boolean updated = updateOppositionCriterion();
 
-        GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
+        GradingCriterion oppositionCriterion = findOppositionCriterion();
         assert oppositionCriterion != null;
         assertEquals(FEEDBACK, oppositionCriterion.getFeedback());
         assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints());
@@ -121,7 +120,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         setPointsOnAllCriteria(gradingReport, 1);
         updateOppositionCriterion();
 
-        GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
+        GradingCriterion oppositionCriterion = findOppositionCriterion();
         assert oppositionCriterion != null;
         assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
         assertEquals(OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
@@ -134,7 +133,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         setPointsOnAllCriteria(gradingReport, points);
         updateOppositionCriterion();
 
-        GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
+        GradingCriterion oppositionCriterion = findOppositionCriterion();
         assert oppositionCriterion != null;
         assertEquals((Integer) points, oppositionCriterion.getPoints());
         assertNull(oppositionCriterion.getFeedback());
@@ -150,9 +149,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
         return gradingReportService.updateOppositionCriteria(gradingReport, opposition);
     }
 
-    private GradingCriterion findOppositionCriterion(String title) {
+    private GradingCriterion findOppositionCriterion() {
         for (GradingCriterion gradingCriterion : gradingReport.getIndividualCriteria()) {
-            if (gradingCriterion.getTitle().equals(title)) {
+            if (gradingCriterion.getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) {
                 return gradingCriterion;
             }
         }
@@ -246,12 +245,13 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
 
     private GradingReportTemplate createOppositionCriteria(GradingReportTemplate gradingReportTemplate, int maxPoints) {
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = getPointTemplates(maxPoints, "Opposition");
-        gradingReportTemplate.addIndividualCriterion(OPPOSITION_SWEDISH, OPPOSITION_ENGLISH, 0, gradingCriterionPointTemplates);
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 0, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.OPPOSITION);
         return save(gradingReportTemplate);
     }
 
     private GradingReportTemplate createGradingReportTemplate(ProjectType projectType) {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType,
+                LocalDate.of(2024, Month.JANUARY, 1));
         return save(gradingReportTemplate);
     }
 
diff --git a/core/src/test/java/se/su/dsv/scipro/report/GradingReportTemplateTest.java b/core/src/test/java/se/su/dsv/scipro/report/GradingReportTemplateTest.java
index 3a5f0fde9b..4f6b4adc27 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/GradingReportTemplateTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/GradingReportTemplateTest.java
@@ -11,6 +11,7 @@ import se.su.dsv.scipro.system.ProjectType;
 import se.su.dsv.scipro.system.User;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -34,7 +35,7 @@ public class GradingReportTemplateTest {
     @Test
     public void creating_a_grading_report_template_with_null_project_type_should_fail() {
         assertThrows(IllegalArgumentException.class, () ->
-                new GradingReportTemplate(null, 30));
+                new GradingReportTemplate(null, LocalDate.of(2024, Month.JANUARY, 1)));
     }
 
     @Test
@@ -100,6 +101,6 @@ public class GradingReportTemplateTest {
     }
 
     private GradingReportTemplate createBachelorTemplate() {
-        return new GradingReportTemplate(bachelor, 30);
+        return new GradingReportTemplate(bachelor, LocalDate.of(2024, Month.JANUARY, 1));
     }
 }
diff --git a/core/src/test/java/se/su/dsv/scipro/report/OppositionReportServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/report/OppositionReportServiceImplTest.java
index 2e74c37739..ebf8e6d827 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/OppositionReportServiceImplTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/OppositionReportServiceImplTest.java
@@ -13,6 +13,7 @@ import se.su.dsv.scipro.file.FileService;
 import se.su.dsv.scipro.finalseminar.FinalSeminar;
 import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
 import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
+import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
 import se.su.dsv.scipro.system.User;
@@ -20,12 +21,13 @@ import se.su.dsv.scipro.test.DomainObjects;
 import se.su.dsv.scipro.test.ObjectMother;
 import se.su.dsv.scipro.test.UserBuilder;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.time.ZonedDateTime;
 import java.util.*;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 
 @ExtendWith(MockitoExtension.class)
 public class OppositionReportServiceImplTest {
@@ -49,7 +51,7 @@ public class OppositionReportServiceImplTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        GradingReportTemplate template = new GradingReportTemplate(BACHELOR, 30);
+        GradingReportTemplate template = new GradingReportTemplate(BACHELOR, LocalDate.of(2024, Month.JANUARY, 1));
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
                 .point(1)
@@ -73,7 +75,7 @@ public class OppositionReportServiceImplTest {
     public void create_report_if_not_existing() {
         GradingReportTemplate template = Mockito.mock(GradingReportTemplate.class);
         Mockito.when(template.createOppositionReport(any(FinalSeminarOpposition.class))).thenReturn(oppositionReport);
-        Mockito.when(gradingReportTemplateRepo.findByProjectTypeAndCredits(any(ProjectType.class), anyInt())).thenReturn(template);
+        Mockito.when(gradingReportTemplateRepo.getTemplate(any(Project.class))).thenReturn(template);
         Mockito.when(oppositionReportRepo.save(oppositionReport)).thenReturn(oppositionReport);
         assertEquals(oppositionReport, oppositionReportService.findOrCreateReport(createFinalSeminarOpposition()));
     }
diff --git a/core/src/test/java/se/su/dsv/scipro/report/OppositionReportTest.java b/core/src/test/java/se/su/dsv/scipro/report/OppositionReportTest.java
index 344f6cf502..6cdae1e826 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/OppositionReportTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/OppositionReportTest.java
@@ -6,6 +6,8 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
 import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -20,7 +22,8 @@ public class OppositionReportTest {
     public void prepareTemplate() {
         ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor");
 
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelor, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelor,
+                LocalDate.of(2024, Month.JANUARY, 1));
         addCriteria(gradingReportTemplate);
 
         oppositionReport = gradingReportTemplate.createOppositionReport(new FinalSeminarOpposition());
diff --git a/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportFactoryTest.java b/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportFactoryTest.java
index 383e39a53d..46f39e4a7d 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportFactoryTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportFactoryTest.java
@@ -3,6 +3,8 @@ package se.su.dsv.scipro.report;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -16,7 +18,7 @@ public class SupervisorGradingReportFactoryTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
+        gradingReportTemplate = new GradingReportTemplate(BACHELOR, LocalDate.of(2024, Month.JANUARY, 1));
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
                 .point(1)
diff --git a/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportTest.java b/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportTest.java
index 4e618dc351..2cf70d275e 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/SupervisorGradingReportTest.java
@@ -8,6 +8,7 @@ import se.su.dsv.scipro.system.ProjectType;
 import se.su.dsv.scipro.system.User;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -25,7 +26,7 @@ public class SupervisorGradingReportTest {
         ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor");
         project = Project.builder().title("Foo").projectType(bachelor).startDate(LocalDate.now()).build();
 
-        gradingReportTemplate = new GradingReportTemplate(bachelor, 30);
+        gradingReportTemplate = new GradingReportTemplate(bachelor, LocalDate.of(2024, Month.JANUARY, 1));
         addCriteria(gradingReportTemplate);
 
         gradingReport = gradingReportTemplate.createSupervisorReport(project, new User());
@@ -73,12 +74,12 @@ public class SupervisorGradingReportTest {
         gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1)).setFx(false);
         gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 0, getPointTemplates(3)).setFx(false);
 
-        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
         gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
         gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
-        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
+        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
     }
 
     private List<GradingCriterionPointTemplate> getPointTemplates(int maxPoint) {
diff --git a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorBachelorGradeCalculatorTest.java b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorBachelorGradeCalculatorTest.java
index b3a06857d3..f0c024be16 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorBachelorGradeCalculatorTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorBachelorGradeCalculatorTest.java
@@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.ObjectMother;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -85,7 +87,8 @@ public class SupervisorBachelorGradeCalculatorTest extends GradeCalculatorTest {
 
     @Override
     protected GradingReportTemplate prepareTemplate() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.BACHELOR, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.BACHELOR,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
@@ -101,12 +104,12 @@ public class SupervisorBachelorGradeCalculatorTest extends GradeCalculatorTest {
         gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 0, getPointTemplates(3)).setFx(false);
 
-        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
         gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
         gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
-        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
+        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplate;
     }
diff --git a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster15GradeCalculatorTest.java b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster15GradeCalculatorTest.java
index 18981669cb..da7d4ea369 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster15GradeCalculatorTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster15GradeCalculatorTest.java
@@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.ObjectMother;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -72,7 +74,8 @@ public class SupervisorMaster15GradeCalculatorTest extends GradeCalculatorTest {
 
     @Override
     protected GradingReportTemplate prepareTemplate() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER, 15);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
@@ -88,12 +91,12 @@ public class SupervisorMaster15GradeCalculatorTest extends GradeCalculatorTest {
         gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 1, getPointTemplates(3));
 
-        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
         gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
         gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
-        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
+        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplate;
     }
diff --git a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster30GradeCalculatorTest.java b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster30GradeCalculatorTest.java
index 70bf7076cc..1f21c29600 100644
--- a/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster30GradeCalculatorTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/report/calculators/original/SupervisorMaster30GradeCalculatorTest.java
@@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.ObjectMother;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -72,7 +74,8 @@ public class SupervisorMaster30GradeCalculatorTest extends GradeCalculatorTest {
 
     @Override
     protected GradingReportTemplate prepareTemplate() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER,
+                LocalDate.of(2024, Month.JANUARY, 1));
 
         gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
@@ -88,12 +91,12 @@ public class SupervisorMaster30GradeCalculatorTest extends GradeCalculatorTest {
         gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
         gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 2, getPointTemplates(4));
 
-        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
+        gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
         gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1));
         gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1));
-        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
+        gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
 
         return gradingReportTemplate;
     }
diff --git a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
index e02ddcfc74..ebbc21afa8 100755
--- a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
+++ b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
@@ -14,6 +14,10 @@ import org.apache.wicket.util.convert.converter.LocalDateConverter;
 import org.apache.wicket.util.convert.converter.LocalDateTimeConverter;
 import se.su.dsv.scipro.activityplan.*;
 import se.su.dsv.scipro.admin.pages.*;
+import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplateCreationPage;
+import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplateEditPage;
+import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatePage;
+import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
 import se.su.dsv.scipro.admin.pages.settings.*;
 import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodExemptionsPage;
 import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodPage;
@@ -282,6 +286,10 @@ public class SciProApplication extends LifecycleManagedWebApplication {
         mountPage("admin/project/survey", AdminSurveyPage.class);
         mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
         mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
+        mountPage("admin/project/grading/template", AdminGradingTemplatesOverviewPage.class);
+        mountPage("admin/project/grading/template/view", AdminGradingTemplatePage.class);
+        mountPage("admin/project/grading/template/edit", AdminGradingTemplateEditPage.class);
+        mountPage("admin/project/grading/template/create", AdminGradingTemplateCreationPage.class);
         mountPage("admin/edit", AdminEditProjectPage.class);
         mountPage("admin/finalseminars", AdminFinalSeminarPage.class);
         mountPage("admin/finalseminars/exemptions", AdminFinalSeminarExemptionPage.class);
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AbstractAdminProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AbstractAdminProjectPage.java
index cb15d8e777..8fa344c822 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AbstractAdminProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AbstractAdminProjectPage.java
@@ -1,6 +1,8 @@
 package se.su.dsv.scipro.admin.pages;
 
 import se.su.dsv.scipro.activityplan.AdminActivityPlanTemplatesPage;
+import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
+import se.su.dsv.scipro.admin.pages.grading.MenuHighlightGradingTemplates;
 import se.su.dsv.scipro.checklists.AdminChecklistPage;
 import se.su.dsv.scipro.components.AbstractMenuPanel;
 import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminActivityPlanTemplates;
@@ -28,7 +30,8 @@ public class AbstractAdminProjectPage extends AbstractAdminPage {
                         new MenuItem("Checklist templates", AdminChecklistPage.class, MenuHighlightAdminChecklist.class),
                         new MenuItem("Non work days", NonWorkDaysPage.class),
                         new MenuItem("Survey", AdminSurveyPage.class),
-                        new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class)
+                        new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class),
+                        new MenuItem("Grading templates", AdminGradingTemplatesOverviewPage.class, MenuHighlightGradingTemplates.class)
                 );
             }
 
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.html
new file mode 100644
index 0000000000..8fd44e66ab
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.html
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<body>
+<wicket:extend>
+    <h1>Create a new grading report template</h1>
+
+    <div wicket:id="feedback"></div>
+
+    <form wicket:id="form" class="line-length-limit">
+        <div class="mb-3">
+            <label class="form-label" wicket:for="project_type">Project type</label>
+            <select wicket:id="project_type" class="form-select">
+                <option value="bachelor">Bachelor</option>
+                <option value="master">Master</option>
+            </select>
+        </div>
+
+        <div wicket:id="grading_template_component_panel"></div>
+
+        <div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
+            <button type="submit" class="btn btn-primary">Create</button>
+        </div>
+    </form>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.java
new file mode 100644
index 0000000000..c14d4925db
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateCreationPage.java
@@ -0,0 +1,80 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import jakarta.inject.Inject;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+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.components.AjaxDropDownChoice;
+import se.su.dsv.scipro.data.DetachableServiceModel;
+import se.su.dsv.scipro.grading.GradingReportTemplateService;
+import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
+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.system.ProjectType;
+import se.su.dsv.scipro.system.ProjectTypeService;
+
+public class AdminGradingTemplateCreationPage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
+
+    @Inject
+    GradingReportTemplateService gradingReportTemplateService;
+    @Inject
+    ProjectTypeService projectTypeService;
+
+    private final IModel<ProjectType> projectTypeModel;
+    private EditingGradingTemplate editingGradingTemplateModel;
+
+    public AdminGradingTemplateCreationPage() {
+        projectTypeModel = new DetachableServiceModel<>(projectTypeService);
+
+        add(new FeedbackPanel("feedback"));
+
+        Form<?> form = new Form<>("form") {
+            @Override
+            protected void onSubmit() {
+                super.onSubmit();
+                GradingReportTemplateUpdate update = AdminGradingTemplateEditPage.toUpdate(editingGradingTemplateModel);
+                try {
+                    GradingReportTemplate gradingReportTemplate = gradingReportTemplateService.create(
+                            projectTypeModel.getObject(),
+                            update);
+                    getSession().success(getString("template_created", projectTypeModel));
+                    PageParameters pageParameters = AdminGradingTemplateEditPage.getPageParameters(gradingReportTemplate);
+                    throw new RestartResponseException(AdminGradingTemplateEditPage.class, pageParameters);
+                } catch (ValidDateMustBeInTheFutureException e) {
+                    error(getString("valid_from_must_be_in_the_future", () -> e));
+                } catch (DuplicateDateException e) {
+                    error(getString("another_template_exists_for_the_given_date_date", () -> e));
+                }
+            }
+        };
+        form.setOutputMarkupId(true);
+        add(form);
+
+        form.add(new AjaxDropDownChoice<>(
+                "project_type",
+                projectTypeModel,
+                gradingReportTemplateService.getProjectTypes(),
+                new LambdaChoiceRenderer<>(ProjectType::getName, ProjectType::getId)) {
+            @Override
+            public void onNewSelection(AjaxRequestTarget target, ProjectType objectSelected) {
+                target.add(form);
+            }
+        });
+
+        editingGradingTemplateModel = new EditingGradingTemplate();
+        form.add(new EditingGradingTemplateComponentPanel("grading_template_component_panel", Model.of(editingGradingTemplateModel)) {
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setVisible(projectTypeModel.getObject() != null);
+            }
+        });
+    }
+}
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
new file mode 100644
index 0000000000..9e28766880
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.html
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+            <button type="submit" class="btn btn-primary">Save</button>
+        </div>
+    </form>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
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
new file mode 100644
index 0000000000..da4a0b42a7
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplateEditPage.java
@@ -0,0 +1,130 @@
+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;
+
+    private EditingGradingTemplate editingGradingTemplate;
+
+    public AdminGradingTemplateEditPage(PageParameters pageParameters) {
+        long id = pageParameters.get(GRADING_REPORT_TEMPLATE_ID_PARAMETER).toLong();
+
+        GradingReportTemplate template = gradingReportTemplateService.getTemplate(id);
+
+        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));
+                }
+            }
+        };
+
+        form.add(new EditingGradingTemplateComponentPanel("editing", Model.of(editingGradingTemplate)));
+        add(form);
+    }
+
+    static GradingReportTemplateUpdate toUpdate(EditingGradingTemplate editingGradingTemplate) {
+        List<GradingReportTemplateUpdate.GradeLimit> gradeLimits = editingGradingTemplate
+                .getGradeLimits()
+                .getGradeLimits()
+                .stream()
+                .map(AdminGradingTemplateEditPage::toGrade)
+                .toList();
+        List<GradingReportTemplateUpdate.Criteria> criteria = editingGradingTemplate
+                .getCriteria()
+                .stream()
+                .map(AdminGradingTemplateEditPage::toCriteria)
+                .toList();
+        return new GradingReportTemplateUpdate(
+                editingGradingTemplate.getValidFrom(),
+                editingGradingTemplate.getNote(),
+                editingGradingTemplate.getGradeLimits().getFailingGrade(),
+                gradeLimits,
+                criteria);
+    }
+
+    private static 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()),
+                switch (criteria.getType()) {
+                    case PROJECT -> GradingReportTemplateUpdate.Criteria.Type.THESIS;
+                    case INDIVIDUAL -> GradingReportTemplateUpdate.Criteria.Type.INDIVIDUAL;
+                },
+                criteria.getPointsRequiredToPass(),
+                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 static GradingReportTemplateUpdate.GradeLimit toGrade(GradeLimits.GradeLimit gradeLimit) {
+        return new GradingReportTemplateUpdate.GradeLimit(
+                gradeLimit.getGrade(),
+                gradeLimit.getLowerLimit());
+    }
+
+    public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
+        PageParameters pageParameters = new PageParameters();
+        pageParameters.add(GRADING_REPORT_TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
+        return pageParameters;
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.html
new file mode 100644
index 0000000000..970e9229d9
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.html
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<head>
+    <title>View a specific grading report template</title>
+    <wicket:remove>
+        <link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
+        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+    </wicket:remove>
+</head>
+<body>
+<wicket:extend>
+    <div class="line-length-limit" wicket:id="details">
+        <h1>
+            Grading report template for
+            <wicket:container wicket:id="project_type"/>
+            <small>
+                (<span wicket:id="valid_period"></span>)
+            </small>
+        </h1>
+        <h2>Criteria</h2>
+        <ol class="list-unstyled">
+            <li wicket:id="criteria" class="mb-3">
+                <h3 wicket:id="title"></h3>
+                <ol class="list-unstyled">
+                    <li wicket:id="points">
+                        <span wicket:id="point"></span> - <span wicket:id="description"></span>
+                    </li>
+                </ol>
+            </li>
+        </ol>
+    </div>
+</wicket:extend>
+</body>
+</html>
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.java
new file mode 100644
index 0000000000..f445f860ee
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatePage.java
@@ -0,0 +1,95 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import jakarta.inject.Inject;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.markup.html.GenericWebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+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.report.GradingCriterionPointTemplate;
+import se.su.dsv.scipro.report.GradingCriterionTemplate;
+import se.su.dsv.scipro.report.GradingReportTemplate;
+import se.su.dsv.scipro.system.ProjectType;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class AdminGradingTemplatePage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
+    private static final String TEMPLATE_ID_PARAMETER = "id";
+
+    @Inject
+    private GradingReportTemplateService gradingReportTemplateService;
+
+    public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
+        PageParameters pageParameters = new PageParameters();
+        pageParameters.add(TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
+        return pageParameters;
+    }
+
+    public AdminGradingTemplatePage(PageParameters parameters) {
+        long templateId = parameters.get(TEMPLATE_ID_PARAMETER).toLong();
+
+        IModel<GradingReportTemplate> template = LoadableDetachableModel.of(() ->
+                gradingReportTemplateService.getTemplate(templateId));
+
+        if (template.getObject() == null) {
+            throw new RestartResponseException(AdminGradingTemplatesOverviewPage.class);
+        }
+
+        add(new TemplateDetailsPanel("details", template));
+    }
+
+    private class TemplateDetailsPanel extends GenericWebMarkupContainer<GradingReportTemplate> {
+        public TemplateDetailsPanel(String id, IModel<GradingReportTemplate> model) {
+            super(id, model);
+
+            add(new Label("project_type", model.map(GradingReportTemplate::getProjectType).map(ProjectType::getName)));
+
+            IModel<String> validPeriod = () -> {
+                GradingReportTemplate template = model.getObject();
+                LocalDate validFrom = template.getValidFrom();
+                LocalDate endDate = gradingReportTemplateService.getEndDate(template);
+                if (endDate == null) {
+                    return "from " + validFrom + " and onwards";
+                } else {
+                    return "from " + validFrom + " to " + endDate;
+                }
+            };
+
+            add(new Label("valid_period", validPeriod));
+
+            add(new ListView<>("criteria", model.map(this::getCriteria)) {
+                @Override
+                protected void populateItem(ListItem<GradingCriterionTemplate> item) {
+                    item.add(new Label("title", item.getModel().map(GradingCriterionTemplate::getTitle)));
+                    item.add(new ListView<>(
+                            "points",
+                            item.getModel().map(GradingCriterionTemplate::getGradingCriterionPointTemplates))
+                    {
+                        @Override
+                        protected void populateItem(ListItem<GradingCriterionPointTemplate> item) {
+                            item.add(new Label("point", item.getModel().map(GradingCriterionPointTemplate::getPoint)));
+                            item.add(new Label(
+                                    "description",
+                                    item.getModel().map(GradingCriterionPointTemplate::getDescription)));
+                        }
+                    });
+                }
+            });
+        }
+
+        private List<GradingCriterionTemplate> getCriteria(GradingReportTemplate gradingReportTemplate) {
+            ArrayList<GradingCriterionTemplate> criteria = new ArrayList<>(gradingReportTemplate.getCriteria());
+            criteria.sort(Comparator.comparing(GradingCriterionTemplate::getSortOrder));
+            return Collections.unmodifiableList(criteria);
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.html
new file mode 100644
index 0000000000..91b7896e26
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.html
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<head>
+    <wicket:remove>
+    <link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+    </wicket:remove>
+</head>
+<body>
+<div class="container-fluid">
+<wicket:extend>
+<div class="line-length-limit">
+    <h1>Grading report template management</h1>
+    <p>
+        Here you can manage the grading templates that are used to grade projects.
+    </p>
+    <p>
+        A template consists of multiple criteria, each with a description and the maximum points that can be scored on it.
+
+        The point sum total conversion to grade letters is also defined in the template.
+    </p>
+    <p>
+        Each template has a period during which it is in use. The period is defined by a start date,
+        and it remains current until a new template takes over. There will only ever be one template
+        current for any given date.
+
+        The template that is selected to be used for grading a project is the one that was current the date
+        the project started.
+    </p>
+    <p>
+        Once a template has become current it is <strong><em>no longer possible</em></strong> to edit it.
+    </p>
+
+    <div class="btn-group mb-3" role="group">
+        <wicket:link>
+            <a href="AdminGradingTemplateCreationPage.html" class="btn btn-outline-primary">Create new template</a>
+        </wicket:link>
+    </div>
+
+    <div class="mb-3" wicket:id="project_types">
+        <h2 wicket:id="name">Bachelor</h2>
+
+        <div class="hstack gap-3 flex-wrap align-items-start" wicket:id="templates">
+            <div class="card" wicket:id="upcoming_templates">
+                <div class="card-header bg-info-subtle">
+                    Upcoming template
+                </div>
+                <div class="card-body">
+                    <dl>
+                        <dt>Valid from</dt>
+                        <dd wicket:id="valid_from">2024-10-31</dd>
+
+                        <wicket:enclosure>
+                            <dt>Note</dt>
+                            <dd wicket:id="note">Added a new criteria U14</dd>
+                        </wicket:enclosure>
+                    </dl>
+                    <span class="card-text">
+                        <a href="#" wicket:id="edit_template">View / Edit</a>
+                    </span>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header bg-success-subtle">
+                    Current template
+                </div>
+                <div class="card-body">
+                    <dl>
+                        <dt>Valid from</dt>
+                        <dd wicket:id="valid_from">2024-01-01</dd>
+
+                        <wicket:enclosure>
+                            <dt>Note</dt>
+                            <dd wicket:id="note">Change ethics criteria to include AI</dd>
+                        </wicket:enclosure>
+                    </dl>
+                    <span class="card-text">
+                        <a href="#" wicket:id="view_template">View</a>
+                    </span>
+                </div>
+            </div>
+
+            <wicket:remove>
+            <div class="card">
+                <div class="card-header">
+                    Older templates
+                </div>
+                <div class="card-body hstack gap-2">
+                    <div class="mw-100">
+                        <dl>
+                            <dt>Valid</dt>
+                            <dd>2023-01-01 to 2023-12-41</dd>
+
+                            <dt>Note</dt>
+                            <dd>Change ethics criteria to include AI</dd>
+                        </dl>
+                        <span class="card-text">
+                            <a href="#">View</a>
+                        </span>
+                    </div>
+                    <div class="vr"></div>
+                    <div>
+                        <dl>
+                            <dt>Valid</dt>
+                            <dd>2001-01-01 to 2022-12-31</dd>
+
+                            <dt>Note</dt>
+                            <dd>Change ethics criteria to include AI</dd>
+                        </dl>
+                        <span class="card-text">
+                            <a href="#">View</a>
+                        </span>
+                    </div>
+                </div>
+            </div>
+            </wicket:remove>
+        </div>
+    </div>
+</div>
+</wicket:extend>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.java
new file mode 100644
index 0000000000..7d994d340f
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/AdminGradingTemplatesOverviewPage.java
@@ -0,0 +1,57 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import jakarta.inject.Inject;
+import org.apache.wicket.markup.html.GenericWebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
+import se.su.dsv.scipro.components.NonEmptyLabel;
+import se.su.dsv.scipro.grading.GradingReportTemplateService;
+import se.su.dsv.scipro.report.GradingReportTemplate;
+import se.su.dsv.scipro.system.ProjectType;
+
+import java.util.List;
+
+public class AdminGradingTemplatesOverviewPage extends AbstractAdminProjectPage {
+    @Inject
+    private GradingReportTemplateService gradingReportTemplateService;
+
+    public AdminGradingTemplatesOverviewPage() {
+        LoadableDetachableModel<List<ProjectType>> projectTypes =
+                LoadableDetachableModel.of(gradingReportTemplateService::getProjectTypes);
+
+        add(new ListView<>("project_types", projectTypes) {
+            @Override
+            protected void populateItem(ListItem<ProjectType> item) {
+                item.add(new Label("name", item.getModel().map(ProjectType::getName)));
+                item.add(new TemplatesPanel("templates", item.getModel()));
+            }
+        });
+    }
+
+    private class TemplatesPanel extends GenericWebMarkupContainer<ProjectType> {
+        public TemplatesPanel(String id, IModel<ProjectType> model) {
+            super(id, model);
+
+            IModel<GradingReportTemplate> currentTemplate = model.map(gradingReportTemplateService::getCurrentTemplate);
+
+            add(new Label("valid_from", currentTemplate.map(GradingReportTemplate::getValidFrom)));
+            add(new NonEmptyLabel("note", currentTemplate.map(GradingReportTemplate::getNote)));
+            add(new BookmarkablePageLink<>("view_template", AdminGradingTemplatePage.class, AdminGradingTemplatePage.getPageParameters(currentTemplate.getObject())));
+
+            IModel<List<GradingReportTemplate>> upcomingTemplates = model.map(gradingReportTemplateService::getUpcomingTemplates);
+            add(new ListView<>("upcoming_templates", upcomingTemplates) {
+                @Override
+                protected void populateItem(ListItem<GradingReportTemplate> item) {
+                    item.add(new Label("valid_from", item.getModel().map(GradingReportTemplate::getValidFrom)));
+                    item.add(new NonEmptyLabel("note", item.getModel().map(GradingReportTemplate::getNote)));
+                    item.add(new BookmarkablePageLink<>("edit_template", AdminGradingTemplateEditPage.class, AdminGradingTemplateEditPage.getPageParameters(item.getModelObject())));
+                }
+            });
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplate.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplate.java
new file mode 100644
index 0000000000..7e4e935ad5
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplate.java
@@ -0,0 +1,184 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
+import se.su.dsv.scipro.report.GradingCriterionTemplate;
+import se.su.dsv.scipro.report.GradingReportTemplate;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+class EditingGradingTemplate implements Serializable {
+    private String note;
+    private LocalDate validFrom;
+    private List<Criteria> criteria;
+    private GradeLimits gradeLimits;
+
+    public EditingGradingTemplate() {
+        this.gradeLimits = new GradeLimits();
+        this.criteria = new ArrayList<>();
+    }
+
+    EditingGradingTemplate(GradingReportTemplate template) {
+        this.note = template.getNote();
+        this.validFrom = template.getValidFrom();
+        this.gradeLimits = new GradeLimits(template);
+        this.criteria = new ArrayList<>();
+        for (var criteria : template.getCriteria()) {
+            Criteria editingCriteria = new Criteria(criteria);
+            this.criteria.add(editingCriteria);
+        }
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+
+    public LocalDate getValidFrom() {
+        return validFrom;
+    }
+
+    public void setValidFrom(LocalDate validFrom) {
+        this.validFrom = validFrom;
+    }
+
+    public GradeLimits getGradeLimits() {
+        return gradeLimits;
+    }
+
+    public List<Criteria> getCriteria() {
+        return criteria;
+    }
+
+    public int getMaxPointsAvailable() {
+        return criteria.stream()
+                .mapToInt(Criteria::getMaxPoints)
+                .sum();
+    }
+
+    public void addCriteria() {
+        this.criteria.add(new Criteria());
+    }
+
+    class Criteria implements Serializable {
+        enum Flag {
+            OPPOSITION, REFLECTION
+        }
+
+        enum Type {
+            PROJECT, INDIVIDUAL
+        }
+
+        private String titleSv;
+        private String titleEn;
+        private List<Point> points = new ArrayList<>();
+        private Flag flag;
+        private Type type = Type.PROJECT;
+        private int pointsRequiredToPass;
+
+        Criteria(GradingCriterionTemplate criteria) {
+            this.titleSv = criteria.getTitle();
+            this.titleEn = criteria.getTitleEn();
+            this.pointsRequiredToPass = criteria.getPointsRequiredToPass();
+            for (var point : criteria.getGradingCriterionPointTemplates()) {
+                if (point.getPoint() == 0) continue;
+                Point editingPoint = new Point(point);
+                this.points.add(editingPoint);
+            }
+            this.flag = criteria.getFlag() == null ? null : switch (criteria.getFlag()) {
+                case OPPOSITION -> Flag.OPPOSITION;
+                case REFLECTION -> Flag.REFLECTION;
+            };
+        }
+
+        private Criteria() {}
+
+        public Type getType() {
+            return type;
+        }
+
+        public void setType(Type type) {
+            this.type = type;
+        }
+
+        public String getTitleSv() {
+            return titleSv;
+        }
+
+        public void setTitleSv(String titleSv) {
+            this.titleSv = titleSv;
+        }
+
+        public String getTitleEn() {
+            return titleEn;
+        }
+
+        public void setTitleEn(String titleEn) {
+            this.titleEn = titleEn;
+        }
+
+        public Flag getFlag() {
+            return flag;
+        }
+
+        public void setFlag(Flag flag) {
+            this.flag = flag;
+        }
+
+        public int getMaxPoints() {
+            return points.size();
+        }
+
+        public List<Point> getPoints() {
+            return points;
+        }
+
+        public void setPoints(List<Point> points) {
+            this.points = points;
+        }
+
+        public int getPointsRequiredToPass() {
+            return pointsRequiredToPass;
+        }
+
+        public void setPointsRequiredToPass(int pointsRequiredToPass) {
+            this.pointsRequiredToPass = pointsRequiredToPass;
+        }
+
+        class Point implements Serializable {
+            private String requirementEn;
+            private String requirementSv;
+
+            Point() {
+                this.requirementEn = "";
+                this.requirementSv = "";
+            }
+
+            Point(GradingCriterionPointTemplate point) {
+                this.requirementEn = point.getDescriptionEn();
+                this.requirementSv = point.getDescription();
+            }
+
+            public String getRequirementEn() {
+                return requirementEn;
+            }
+
+            public void setRequirementEn(String requirement) {
+                this.requirementEn = requirement;
+            }
+
+            public String getRequirementSv() {
+                return requirementSv;
+            }
+
+            public void setRequirementSv(String requirementSv) {
+                this.requirementSv = requirementSv;
+            }
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.html
new file mode 100644
index 0000000000..80952a8f02
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.html
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<head>
+    <wicket:remove>
+        <link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
+        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
+        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
+    </wicket:remove>
+</head>
+<body>
+<wicket:panel>
+    <div class="mb-3 line-length-limit">
+        <label class="form-label" wicket:for="valid_from">
+            Valid from
+        </label>
+        <input required type="text" class="form-control" wicket:id="valid_from">
+        <small class="text-muted">
+            When does this grading template take effect.
+        </small>
+    </div>
+
+    <div class="mb-3 line-length-limit">
+        <label class="form-label" wicket:for="note">
+            Note
+        </label>
+        <textarea class="form-control" wicket:id="note" rows="3"></textarea>
+        <small class="text-muted">
+            This note can be used to describe the template, for example, what changes have been made from the previous version.
+            It is purely an aid for administrators if necessary.
+        </small>
+    </div>
+
+    <fieldset wicket:id="grade_limits" class="line-length-limit">
+        <legend>Grade limits</legend>
+        <p>
+            A grade limit is a point requirement to get another grade, e.g. at least 30 points to receive an A.
+            The order in which you specify the grade limits does not matter.
+            The authors will receive the grade associated with the highest minimum point requirement they met.
+            If they do not meet any of the requirements, they will receive the failing grade.
+        </p>
+        <div class="mb-3">
+            <label class="form-label" wicket:for="failing_grade">Failing grade</label>
+            <input required type="text" class="form-control" wicket:id="failing_grade">
+            <small class="text-muted">The grade they receive if they do not meet the minimum points for any other grade.</small>
+        </div>
+
+        <wicket:container wicket:id="grade_limits">
+            <div class="row align-items-center mb-3" wicket:id="grade_limit">
+                <div class="col"><label class="form-label" wicket:for="minimum">Minimum points</label></div>
+                <div class="col"><input type="number" required class="form-control" min="1" wicket:id="minimum"></div>
+                <div class="col"><label class="form-label">Grade</label></div>
+                <div class="col"><input type="text" required class="form-control" wicket:id="grade"></div>
+                <div class="col-auto"><button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button></div>
+            </div>
+        </wicket:container>
+        <div class="mb-3">
+            <button class="btn btn-sm btn-outline-success" wicket:id="add">Add new grade limit</button>
+        </div>
+    </fieldset>
+
+    <p class="mb-3">
+        <wicket:message key="max_points_available">
+            The maximum number of points available is <strong wicket:id="max_points_available">8</strong> with the below criteria.
+        </wicket:message>
+    </p>
+
+    <div class="mb-3 line-length-limit card" wicket:id="criteria">
+        <div class="card-header text-bg-info text-white hstack justify-content-between">
+            <h3 class="text-white mb-0">Criterion</h3>
+            <button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button>
+        </div>
+        <div wicket:id="criteria" class="card-body">
+            <div class="row mb-3">
+                <div class="col">
+                    <label class="form-label" wicket:for="title_en">
+                        Title (english)
+                    </label>
+                    <input required class="form-control" wicket:id="title_en" type="text">
+                </div>
+                <div class="col">
+                    <label class="form-label" wicket:for="title_sv">
+                        Title (swedish)
+                    </label>
+                    <input required class="form-control" wicket:id="title_sv" type="text">
+                </div>
+            </div>
+
+            <div class="mb-3">
+                <label class="form-label" wicket:for="type">Type</label>
+                <select wicket:id="type" class="form-control"></select>
+                <small class="text-muted">Whether this criterion is assessed once for the thesis or per author.</small>
+            </div>
+
+            <div class="mb-3">
+                <label class="form-label" wicket:for="flag">Special flag</label>
+                <select class="form-control" wicket:id="flag"></select>
+                <small class="text-muted">You can flag a criteria if it is connected to some other part of the system</small>
+            </div>
+
+            <div class="mb-3">
+                <label class="form-label" wicket:for="points_required_to_pass">
+                    Points required to pass
+                </label>
+                <input required type="number" class="form-control" wicket:id="points_required_to_pass">
+                <small class="text-muted">
+                    If the author does not get at least this many points on this criterion,
+                    they get the default grade.
+                </small>
+            </div>
+
+            <fieldset>
+                <legend>Points</legend>
+
+                <ol class="list-unstyled">
+                    <li wicket:id="points">
+                        <fieldset wicket:id="point" class="card mb-3">
+                            <legend class="card-header hstack justify-content-between">
+                                <span>Requirement for <span wicket:id="point"></span> point(s)</span>
+                                <button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button>
+                            </legend>
+                            <div class="card-body">
+                                <div class="mb-3">
+                                    <label wicket:for="requirement_en">
+                                        Requirement (english)
+                                    </label>
+                                    <textarea required rows="4" class="form-control" wicket:id="requirement_en"></textarea>
+                                </div>
+                                <div>
+                                    <label wicket:for="requirement_sv">
+                                        Requirement (swedish)
+                                    </label>
+                                    <textarea required rows="4" class="form-control" wicket:id="requirement_sv"></textarea>
+                                </div>
+                            </div>
+                        </fieldset>
+                    </li>
+                    <li>
+                        <div class="row align-items-center">
+                            <label class="col-auto">
+                                <wicket:container wicket:id="new_point"/>
+                            </label>
+                            <div class="col-auto">
+                                <div class="btn-group">
+                                    <button class="btn btn-sm btn-outline-success" wicket:id="add_new_point">Add</button>
+                                </div>
+                            </div>
+                        </div>
+                    </li>
+                </ol>
+            </fieldset>
+        </div>
+    </div>
+
+    <div class="mb-3">
+        <button class="btn btn-sm btn-outline-success" wicket:id="add_criteria">Add new criteria</button>
+    </div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.java
new file mode 100644
index 0000000000..fb6b0af006
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.java
@@ -0,0 +1,297 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.html.GenericWebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.html.form.NumberTextField;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.GenericPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LambdaModel;
+import se.su.dsv.scipro.components.AjaxDropDownChoice;
+import se.su.dsv.scipro.components.BootstrapDatePicker;
+
+import java.time.LocalDate;
+import java.util.List;
+
+class EditingGradingTemplateComponentPanel extends GenericPanel<EditingGradingTemplate> {
+    EditingGradingTemplateComponentPanel(
+            String id,
+            IModel<EditingGradingTemplate> editingGradingTemplateModel)
+    {
+        super(id, editingGradingTemplateModel);
+
+        setOutputMarkupId(true);
+
+        TextField<LocalDate> validFromField = new RequiredTextField<>("valid_from",
+                LambdaModel.of(editingGradingTemplateModel,
+                        EditingGradingTemplate::getValidFrom,
+                        EditingGradingTemplate::setValidFrom),
+                LocalDate.class);
+        validFromField.add(new BootstrapDatePicker());
+        validFromField.add(new AutoSave());
+        add(validFromField);
+
+        add(new TextArea<>("note", LambdaModel.of(
+                editingGradingTemplateModel,
+                EditingGradingTemplate::getNote,
+                EditingGradingTemplate::setNote)) {
+            {
+                add(new AutoSave());
+            }
+        });
+
+        add(new GradeLimitsPanel("grade_limits", editingGradingTemplateModel.map(EditingGradingTemplate::getGradeLimits)));
+
+        add(new Label("max_points_available", editingGradingTemplateModel.map(EditingGradingTemplate::getMaxPointsAvailable)));
+
+        add(new ListView<>("criteria", editingGradingTemplateModel.map(EditingGradingTemplate::getCriteria)) {
+            {
+                setReuseItems(true);
+            }
+
+            @Override
+            protected void populateItem(ListItem<EditingGradingTemplate.Criteria> item) {
+                item.add(new AjaxLink<>("remove") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        editingGradingTemplateModel.getObject().getCriteria().remove(item.getModelObject());
+                        target.add(EditingGradingTemplateComponentPanel.this);
+                    }
+                });
+                item.add(new CriteriaEditingPanel("criteria", item.getModel()));
+            }
+        });
+
+        add(new AjaxLink<>("add_criteria") {
+            @Override
+            public void onClick(AjaxRequestTarget target) {
+                editingGradingTemplateModel.getObject().addCriteria();
+                target.add(EditingGradingTemplateComponentPanel.this);
+            }
+        });
+    }
+
+    class CriteriaEditingPanel extends GenericWebMarkupContainer<EditingGradingTemplate.Criteria> {
+        CriteriaEditingPanel(String id, IModel<EditingGradingTemplate.Criteria> model) {
+            super(id, model);
+
+            setOutputMarkupId(true);
+
+            add(new RequiredTextField<>("title_sv", LambdaModel.of(
+                    model,
+                    EditingGradingTemplate.Criteria::getTitleSv,
+                    EditingGradingTemplate.Criteria::setTitleSv)) {
+                {
+                    add(new AutoSave());
+                }
+            });
+            add(new RequiredTextField<>("title_en", LambdaModel.of(
+                    model,
+                    EditingGradingTemplate.Criteria::getTitleEn,
+                    EditingGradingTemplate.Criteria::setTitleEn)) {
+                {
+                    add(new AutoSave());
+                }
+            });
+
+            AjaxDropDownChoice<EditingGradingTemplate.Criteria.Type> typeChoice = new AjaxDropDownChoice<>(
+                    "type",
+                    LambdaModel.of(
+                            model,
+                            EditingGradingTemplate.Criteria::getType,
+                            EditingGradingTemplate.Criteria::setType),
+                    List.of(EditingGradingTemplate.Criteria.Type.values()),
+                    new EnumChoiceRenderer<>(this))
+            {
+                @Override
+                public void onNewSelection(
+                        AjaxRequestTarget target,
+                        EditingGradingTemplate.Criteria.Type objectSelected)
+                {
+                    // auto save
+                }
+            };
+            typeChoice.setRequired(true);
+            add(typeChoice);
+
+            NumberTextField<Integer> pointsRequiredToPass = new NumberTextField<>(
+                    "points_required_to_pass",
+                    LambdaModel.of(
+                            model,
+                            EditingGradingTemplate.Criteria::getPointsRequiredToPass,
+                            EditingGradingTemplate.Criteria::setPointsRequiredToPass),
+                    Integer.class);
+            pointsRequiredToPass.setMinimum(0);
+            pointsRequiredToPass.setRequired(true);
+            pointsRequiredToPass.add(new AutoSave());
+            add(pointsRequiredToPass);
+
+            AjaxDropDownChoice<EditingGradingTemplate.Criteria.Flag> flagChoice = new AjaxDropDownChoice<>(
+                    "flag",
+                    LambdaModel.of(
+                            model,
+                            EditingGradingTemplate.Criteria::getFlag,
+                            EditingGradingTemplate.Criteria::setFlag),
+                    List.of(EditingGradingTemplate.Criteria.Flag.values()),
+                    new EnumChoiceRenderer<>(this))
+            {
+                @Override
+                public void onNewSelection(
+                        AjaxRequestTarget target,
+                        EditingGradingTemplate.Criteria.Flag objectSelected)
+                {
+                    // auto save
+                }
+            };
+            flagChoice.setNullValid(true);
+            add(flagChoice);
+
+            add(new ListView<>("points", model.map(EditingGradingTemplate.Criteria::getPoints)) {
+                {
+                    setReuseItems(true);
+                }
+
+                @Override
+                protected void populateItem(ListItem<EditingGradingTemplate.Criteria.Point> item) {
+                    item.add(new PointEditingPanel("point", item));
+                }
+            });
+
+            add(new Label("new_point", model
+                    .map(criteria -> criteria.getPoints().size())
+                    .map(size -> "Requirement for " + (size + 1) + " points")));
+
+            add(new AjaxLink<>("add_new_point") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    EditingGradingTemplate.Criteria criteria = model.getObject();
+                    EditingGradingTemplate.Criteria.Point newPoint = criteria.new Point();
+                    criteria.getPoints().add(newPoint);
+                    target.add(CriteriaEditingPanel.this);
+                }
+            });
+        }
+
+        private class PointEditingPanel extends GenericWebMarkupContainer<EditingGradingTemplate.Criteria.Point> {
+            public PointEditingPanel(String id, ListItem<EditingGradingTemplate.Criteria.Point> item) {
+                super(id, item.getModel());
+
+                IModel<EditingGradingTemplate.Criteria.Point> model = item.getModel();
+
+                add(new Label("point", () -> item.getIndex() + 1));
+
+                TextArea<String> englishRequirement = new TextArea<>("requirement_en", LambdaModel.of(
+                        model,
+                        EditingGradingTemplate.Criteria.Point::getRequirementEn,
+                        EditingGradingTemplate.Criteria.Point::setRequirementEn));
+                englishRequirement.setRequired(true);
+                englishRequirement.add(new AutoSave());
+                add(englishRequirement);
+
+                TextArea<String> swedishRequirement = new TextArea<>("requirement_sv", LambdaModel.of(
+                        model,
+                        EditingGradingTemplate.Criteria.Point::getRequirementSv,
+                        EditingGradingTemplate.Criteria.Point::setRequirementSv));
+                swedishRequirement.setRequired(true);
+                swedishRequirement.add(new AutoSave());
+                add(swedishRequirement);
+
+                add(new AjaxLink<>("remove") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        EditingGradingTemplate.Criteria criteria = CriteriaEditingPanel.this.getModelObject();
+                        criteria.getPoints().remove(model.getObject());
+                        target.add(CriteriaEditingPanel.this);
+                    }
+                });
+            }
+        }
+    }
+
+    private static class GradeLimitsPanel extends GenericWebMarkupContainer<GradeLimits> {
+        public GradeLimitsPanel(String id, IModel<GradeLimits> model) {
+            super(id, model);
+
+            setOutputMarkupId(true);
+
+            add(new RequiredTextField<>("failing_grade", LambdaModel.of(
+                    model,
+                    GradeLimits::getFailingGrade,
+                    GradeLimits::setFailingGrade)) {
+                {
+                    add(new AutoSave());
+                }
+            });
+
+            add(new ListView<>("grade_limits", model.map(GradeLimits::getGradeLimits)) {
+                {
+                    setReuseItems(true);
+                }
+
+                @Override
+                protected void populateItem(ListItem<GradeLimits.GradeLimit> item) {
+                    item.add(new GradeLimitEditingPanel("grade_limit", item.getModel()));
+                }
+            });
+
+            add(new AjaxLink<>("add") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    GradeLimits gradeLimits = GradeLimitsPanel.this.getModelObject();
+                    gradeLimits.addNewLimit();
+                    target.add(GradeLimitsPanel.this);
+                }
+            });
+        }
+
+        private class GradeLimitEditingPanel extends GenericWebMarkupContainer<GradeLimits.GradeLimit> {
+            public GradeLimitEditingPanel(String id, IModel<GradeLimits.GradeLimit> model) {
+                super(id, model);
+
+                NumberTextField<Integer> minimum = new NumberTextField<>("minimum", LambdaModel.of(
+                        model,
+                        GradeLimits.GradeLimit::getLowerLimit,
+                        GradeLimits.GradeLimit::setLowerLimit), Integer.class);
+                minimum.setRequired(true);
+                minimum.add(new AutoSave());
+                add(minimum);
+
+                TextField<String> grade = new TextField<>("grade", LambdaModel.of(
+                        model,
+                        GradeLimits.GradeLimit::getGrade,
+                        GradeLimits.GradeLimit::setGrade));
+                grade.setRequired(true);
+                grade.add(new AutoSave());
+                add(grade);
+
+                add(new AjaxLink<>("remove") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        GradeLimits gradeLimits = GradeLimitsPanel.this.getModelObject();
+                        gradeLimits.getGradeLimits().remove(model.getObject());
+                        target.add(GradeLimitsPanel.this);
+                    }
+                });
+            }
+        }
+    }
+
+    private static class AutoSave extends AjaxFormComponentUpdatingBehavior {
+        public AutoSave() {
+            super("input");
+        }
+
+        @Override
+        protected void onUpdate(AjaxRequestTarget target) {
+            // just trigger the ajax call is enough to update the model object
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.utf8.properties
new file mode 100644
index 0000000000..6315499c53
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/EditingGradingTemplateComponentPanel.utf8.properties
@@ -0,0 +1,13 @@
+Flag.OPPOSITION=Final seminar opposition
+Flag.REFLECTION=Reflection
+max_points_available=The maximum number of points available is ${max_points_available} with the below criteria.
+failing_grade.Required=You must set the failing grade.
+title_sv.Required=You must set the swedish title for all criteria
+title_en.Required=You must set the english title for all criteria
+requirement_sv.Required=You must set the swedish requirement for every criteria point
+requirement_en.Required=You must set the english requirement for every criteria point
+Type.PROJECT=Once per thesis
+Type.INDIVIDUAL=Individually for each author
+minimum.Required=You must set the minimum number of points for each grade
+grade.Required=You must set the letter for each grade
+flag.nullValid=None
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/GradeLimits.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/GradeLimits.java
new file mode 100644
index 0000000000..29bd5877d2
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/GradeLimits.java
@@ -0,0 +1,64 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import se.su.dsv.scipro.report.GradingReportTemplate;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+class GradeLimits implements Serializable {
+    private List<GradeLimit> gradeLimits;
+    private String failingGrade;
+
+    public GradeLimits() {
+        this.gradeLimits = new ArrayList<>();
+    }
+
+    GradeLimits(GradingReportTemplate template) {
+        this.gradeLimits = new ArrayList<>();
+        for (var gradeLimit : template.getGradeLimits()) {
+            GradeLimit editableGradeLimit = new GradeLimit();
+            editableGradeLimit.setGrade(gradeLimit.getGrade());
+            editableGradeLimit.setLowerLimit(gradeLimit.getLowerLimit());
+            gradeLimits.add(editableGradeLimit);
+        }
+        this.failingGrade = template.getFailingGrade();
+    }
+
+    void addNewLimit() {
+        getGradeLimits().add(new GradeLimit());
+    }
+
+    public String getFailingGrade() {
+        return failingGrade;
+    }
+
+    public void setFailingGrade(String failingGrade) {
+        this.failingGrade = failingGrade;
+    }
+
+    public List<GradeLimit> getGradeLimits() {
+        return gradeLimits;
+    }
+
+    class GradeLimit implements Serializable {
+        private String grade;
+        private int lowerLimit;
+
+        public String getGrade() {
+            return grade;
+        }
+
+        public void setGrade(String grade) {
+            this.grade = grade;
+        }
+
+        public int getLowerLimit() {
+            return lowerLimit;
+        }
+
+        public void setLowerLimit(int lowerLimit) {
+            this.lowerLimit = lowerLimit;
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/MenuHighlightGradingTemplates.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/MenuHighlightGradingTemplates.java
new file mode 100644
index 0000000000..0ff8cfc851
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/MenuHighlightGradingTemplates.java
@@ -0,0 +1,6 @@
+package se.su.dsv.scipro.admin.pages.grading;
+
+import se.su.dsv.scipro.components.menuhighlighting.MenuHighlight;
+
+public interface MenuHighlightGradingTemplates extends MenuHighlight {
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/wicket-package.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/wicket-package.utf8.properties
new file mode 100644
index 0000000000..f761f20e4e
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/grading/wicket-package.utf8.properties
@@ -0,0 +1,5 @@
+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}.
+template_created=New template for ${name} created.
diff --git a/view/src/main/java/se/su/dsv/scipro/components/NonEmptyLabel.java b/view/src/main/java/se/su/dsv/scipro/components/NonEmptyLabel.java
new file mode 100644
index 0000000000..27218979e7
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/components/NonEmptyLabel.java
@@ -0,0 +1,19 @@
+package se.su.dsv.scipro.components;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.IModel;
+
+/**
+ * A basic {@link Label} that hides itself if the rendered model object {@link String#isBlank() is blank}
+ */
+public class NonEmptyLabel extends Label {
+    public NonEmptyLabel(String id, IModel<?> model) {
+        super(id, model);
+    }
+
+    @Override
+    protected void onConfigure() {
+        super.onConfigure();
+        setVisible(!getDefaultModelObjectAsString().isBlank());
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/grading/CriteriaPanel.java b/view/src/main/java/se/su/dsv/scipro/grading/CriteriaPanel.java
index 2dcfa3420a..e5475c42ed 100644
--- a/view/src/main/java/se/su/dsv/scipro/grading/CriteriaPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/grading/CriteriaPanel.java
@@ -121,7 +121,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
                 grading.add(pf);
                 grading.add(new Label("pfCriteria", item.getModel().combineWith(language, (gc, lang) -> gc.getGradingCriterionPoints().get(1).getDescription(lang))));
 
-                if (item.getModelObject().getTitle().startsWith("Ö1 ")) {
+                if (item.getModelObject().getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) {
                     grading.setEnabled(false);
                 }
                 item.add(new AjaxConfirmationLink<>("enable", "Are you sure you want to grade this criteria? It is usually filled in by the seminar supervisor") {
@@ -194,7 +194,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
         @Override
         protected void onConfigure() {
             super.onConfigure();
-            final boolean isOppositionCriteria = gradingCriterion.getObject().getTitle().startsWith("Ö1 ");
+            final boolean isOppositionCriteria = gradingCriterion.getObject().getFlag() == AbstractGradingCriterion.Flag.OPPOSITION;
             setVisibilityAllowed(isOppositionCriteria && isEnabledInHierarchy());
         }
 
@@ -296,7 +296,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
         @Override
         protected void onConfigure() {
             super.onConfigure();
-            boolean isReflectionCriteria = gradingCriterion.getObject().getTitle().startsWith("Ö6 ");
+            boolean isReflectionCriteria = gradingCriterion.getObject().getFlag() == AbstractGradingCriterion.Flag.REFLECTION;
             setVisibilityAllowed(isReflectionCriteria);
         }
     }
diff --git a/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java b/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java
index 6a0c6c6e71..ad4c49050d 100644
--- a/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java
@@ -24,7 +24,7 @@ public class GradingReportPointsPanel extends Panel {
                 return gradingReportIModel.getObject().getGrade(gradeCalculator);
             }
         };
-        final Label grade = new Label(GRADE, gradeModel) {
+        final Label grade = new Label(GRADE, gradeModel.map(GradingReport.Grade::name)) {
             @Override
             protected void onConfigure() {
                 super.onConfigure();
diff --git a/view/src/main/webapp/css/scipro_m.css b/view/src/main/webapp/css/scipro_m.css
index 46944aaf54..5c4184a9c0 100755
--- a/view/src/main/webapp/css/scipro_m.css
+++ b/view/src/main/webapp/css/scipro_m.css
@@ -597,4 +597,7 @@ th.wicket_orderUp, th.sorting_asc {
 
 .bg-su-primary {
     background-color: #002f5f;
-}
\ No newline at end of file
+}
+.line-length-limit {
+    max-width: 80em;
+}
diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/DownloadPdfReportPanelTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/DownloadPdfReportPanelTest.java
index 5d8ecbdfe5..70c92fb5cb 100644
--- a/view/src/test/java/se/su/dsv/scipro/finalseminar/DownloadPdfReportPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/DownloadPdfReportPanelTest.java
@@ -18,6 +18,8 @@ import se.su.dsv.scipro.test.ObjectMother;
 import se.su.dsv.scipro.test.SeminarBuilder;
 import se.su.dsv.scipro.test.UserBuilder;
 
+import java.time.LocalDate;
+import java.time.Month;
 import java.time.ZonedDateTime;
 import java.util.*;
 
@@ -76,7 +78,8 @@ public class DownloadPdfReportPanelTest extends SciProTest {
     }
 
     private void prepareReports() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
+                LocalDate.of(2024, Month.JANUARY, 1));
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
                 .point(0)
diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java
index c446887a68..cafe8f9928 100644
--- a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java
@@ -29,6 +29,7 @@ import se.su.dsv.scipro.test.UserBuilder;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.time.ZonedDateTime;
 import java.util.*;
 
@@ -152,7 +153,8 @@ public class OppositionReportPageTest extends SciProTest {
     }
 
     private GradingReportTemplate createTemplate(ProjectType bachelor) {
-        GradingReportTemplate reportTemplate = new GradingReportTemplate(bachelor, 30);
+        GradingReportTemplate reportTemplate = new GradingReportTemplate(bachelor,
+                LocalDate.of(2024, Month.JANUARY, 1));
         List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
         gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
                 .point(1)
diff --git a/view/src/test/java/se/su/dsv/scipro/grading/CriteriaPanelTest.java b/view/src/test/java/se/su/dsv/scipro/grading/CriteriaPanelTest.java
index 9577a21d84..608659db2f 100644
--- a/view/src/test/java/se/su/dsv/scipro/grading/CriteriaPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/grading/CriteriaPanelTest.java
@@ -17,6 +17,7 @@ import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.UserBuilder;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.*;
 
 import static se.su.dsv.scipro.grading.CriteriaPanel.*;
@@ -110,7 +111,8 @@ public class CriteriaPanelTest extends SciProTest {
     }
 
     private void prepareReports() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
+                LocalDate.of(2024, Month.JANUARY, 1));
         gradingReportTemplate.addIndividualCriterion("Projektkriterium", "Project criterion", 1, getPointTemplates(2));
         gradingReportTemplate.addIndividualCriterion("Individuellt kriterium", "Individual criterion", 0, getPointTemplates(2));
         gradingReport = gradingReportTemplate.createSupervisorReport(SOME_PROJECT, SOME_USER);
diff --git a/view/src/test/java/se/su/dsv/scipro/grading/FillOutReportPanelTest.java b/view/src/test/java/se/su/dsv/scipro/grading/FillOutReportPanelTest.java
index 2ccff71344..209a783033 100644
--- a/view/src/test/java/se/su/dsv/scipro/grading/FillOutReportPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/grading/FillOutReportPanelTest.java
@@ -20,6 +20,7 @@ import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.UserBuilder;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -44,7 +45,8 @@ public class FillOutReportPanelTest extends SciProTest {
         User headSupervisorUser = new UserBuilder().create();
         Project project = Project.builder().title("title").projectType(projectType).startDate(LocalDate.now()).headSupervisor(headSupervisorUser).build();
         finalSeminar.setProject(project);
-        GradingReportTemplate template = new GradingReportTemplate(projectType, 30);
+        GradingReportTemplate template = new GradingReportTemplate(projectType,
+                LocalDate.of(2024, Month.JANUARY, 1));
         template.addProjectCriterion("U1", "U1", 1, getPointTemplates(1));
         FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
         finalSeminarOpposition.setFinalSeminar(finalSeminar);
diff --git a/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java b/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java
index a7edf1d14b..cc0c6799df 100644
--- a/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java
@@ -13,6 +13,7 @@ import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.UserBuilder;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -44,7 +45,7 @@ public class GradingReportPointsPanelTest extends SciProTest {
         when(gradeCalculator.getGrade(any(GradingReport.class))).thenReturn(grade);
         startPanel();
 
-        tester.assertLabel(path(panel, GRADE), grade.toString());
+        tester.assertLabel(path(panel, GRADE), grade.name());
     }
 
     @Test
@@ -61,7 +62,8 @@ public class GradingReportPointsPanelTest extends SciProTest {
     }
 
     private void prepareReports() {
-        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
+        GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
+                LocalDate.of(2024, Month.JANUARY, 1));
         gradingReportTemplate.addProjectCriterion("title", "titleEn", 0, getPointTemplates(2));
         gradingReport = gradingReportTemplate.createSupervisorReport(SOME_PROJECT, SOME_USER);
         for (GradingCriterion gradingCriterion : gradingReport.getGradingCriteria()) {
diff --git a/view/src/test/java/se/su/dsv/scipro/grading/ThesisApprovedPanelTest.java b/view/src/test/java/se/su/dsv/scipro/grading/ThesisApprovedPanelTest.java
index 46d7ef2a76..5a13e8e940 100644
--- a/view/src/test/java/se/su/dsv/scipro/grading/ThesisApprovedPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/grading/ThesisApprovedPanelTest.java
@@ -11,6 +11,7 @@ import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
 
 import java.time.LocalDate;
+import java.time.Month;
 import java.util.Date;
 
 public class ThesisApprovedPanelTest extends SciProTest {
@@ -31,7 +32,8 @@ public class ThesisApprovedPanelTest extends SciProTest {
         ProjectType projectType = new ProjectType(ProjectType.MASTER, "Master", "Master");
         projectType.setId(1L);
 
-        Mockito.when(gradingReportService.getTemplate(SOME_PROJECT)).thenReturn(new GradingReportTemplate(projectType, 30));
+        Mockito.when(gradingReportService.getTemplate(SOME_PROJECT)).thenReturn(new GradingReportTemplate(projectType,
+                LocalDate.of(2024, Month.JANUARY, 1)));
         panel = tester.startComponentInPage(new ThesisApprovedPanel("panel", Model.of(SOME_PROJECT)));
     }