diff --git a/ExpressAPI/.idea/material_theme_project_new.xml b/ExpressAPI/.idea/material_theme_project_new.xml
index 08d93208ec31f4c00760f273384c3ace69519b66..16e830f2d917c0815e814dcbf57e219c3b85c2e2 100644
--- a/ExpressAPI/.idea/material_theme_project_new.xml
+++ b/ExpressAPI/.idea/material_theme_project_new.xml
@@ -3,7 +3,9 @@
   <component name="MaterialThemeProjectNewConfig">
     <option name="metadata">
       <MTProjectMetadataState>
-        <option name="userId" value="104e8585:19002424fea:-7fcc" />
+        <option name="migrated" value="true" />
+        <option name="pristineConfig" value="false" />
+        <option name="userId" value="104e8585:19002424fea:-7ffe" />
       </MTProjectMetadataState>
     </option>
   </component>
diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
index 8ebb21662faad48639bce22286e04c0d2f7c4450..23d099e0a7b64cb6a9bff5c5fe886336cbe3d1d8 100644
--- a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
+++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
@@ -1,7 +1,7 @@
 openapi: 3.1.0
 info:
     title: Dojo API
-    version: 4.1.0
+    version: 4.2.0
     description: |
         **Backend API of the Dojo project.**
         
diff --git a/ExpressAPI/prisma/migrations/20240212153007_add_tags/migration.sql b/ExpressAPI/prisma/migrations/20240212153007_add_tags/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..4760713466e16ff80d81a734fe2f3c31837eadcc
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20240212153007_add_tags/migration.sql
@@ -0,0 +1,46 @@
+-- CreateTable
+CREATE TABLE `Tag` (
+    `name` CHAR(36) NOT NULL,
+    `type` ENUM('LANGUAGE', 'FRAMEWORK', 'THEME', 'USERDEFINED') NOT NULL,
+
+    PRIMARY KEY (`name`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `_AssignmentToTag` (
+    `A` VARCHAR(191) NOT NULL,
+    `B` CHAR(36) NOT NULL,
+
+    UNIQUE INDEX `_AssignmentToTag_AB_unique`(`A`, `B`),
+    INDEX `_AssignmentToTag_B_index`(`B`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `_ExerciseToTag` (
+    `A` CHAR(36) NOT NULL,
+    `B` CHAR(36) NOT NULL,
+
+    UNIQUE INDEX `_ExerciseToTag_AB_unique`(`A`, `B`),
+    INDEX `_ExerciseToTag_B_index`(`B`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `_AssignmentToTag` ADD CONSTRAINT `_AssignmentToTag_A_fkey` FOREIGN KEY (`A`) REFERENCES `Assignment`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `_AssignmentToTag` ADD CONSTRAINT `_AssignmentToTag_B_fkey` FOREIGN KEY (`B`) REFERENCES `Tag`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `_ExerciseToTag` ADD CONSTRAINT `_ExerciseToTag_A_fkey` FOREIGN KEY (`A`) REFERENCES `Exercise`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `_ExerciseToTag` ADD CONSTRAINT `_ExerciseToTag_B_fkey` FOREIGN KEY (`B`) REFERENCES `Tag`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- CreateTable
+CREATE TABLE `SubmissionTag` (
+    `name` CHAR(36) NOT NULL,
+    `type` ENUM('LANGUAGE', 'FRAMEWORK', 'THEME', 'USERDEFINED') NOT NULL,
+    `state` VARCHAR(191) NOT NULL,
+
+    PRIMARY KEY (`name`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/ExpressAPI/prisma/migrations/20240619160717_rename_tag_proposal_table/migration.sql b/ExpressAPI/prisma/migrations/20240619160717_rename_tag_proposal_table/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5484fa4855165f86579d571d91ec421ee13ab5c9
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20240619160717_rename_tag_proposal_table/migration.sql
@@ -0,0 +1,17 @@
+/*
+  Warnings:
+
+  - You are about to drop the `SubmissionTag` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropTable
+DROP TABLE `SubmissionTag`;
+
+-- CreateTable
+CREATE TABLE `TagProposal` (
+    `name` CHAR(36) NOT NULL,
+    `type` ENUM('LANGUAGE', 'FRAMEWORK', 'THEME', 'USERDEFINED') NOT NULL,
+    `state` VARCHAR(191) NOT NULL,
+
+    PRIMARY KEY (`name`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/ExpressAPI/prisma/migrations/20240619232301_set_tag_proposal_state_default/migration.sql b/ExpressAPI/prisma/migrations/20240619232301_set_tag_proposal_state_default/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f0feb8cccc7b2e7466528d133231457418edd075
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20240619232301_set_tag_proposal_state_default/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE `TagProposal` MODIFY `state` VARCHAR(191) NOT NULL DEFAULT 'PendingApproval';
diff --git a/ExpressAPI/prisma/migrations/20240619232804_add_tag_proposal_details/migration.sql b/ExpressAPI/prisma/migrations/20240619232804_add_tag_proposal_details/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..28577819d43b5e17369af23ae3e9ee4efb43edfc
--- /dev/null
+++ b/ExpressAPI/prisma/migrations/20240619232804_add_tag_proposal_details/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE `TagProposal` ADD COLUMN `details` VARCHAR(191) NULL;
diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma
index 8d7143ac874fd4324ce2d1c043f65c2f6e27ba68..69e3b2678b3e6e6548bc86c6f713b5560ccc31f0 100644
--- a/ExpressAPI/prisma/schema.prisma
+++ b/ExpressAPI/prisma/schema.prisma
@@ -13,6 +13,13 @@ enum UserRole {
     ADMIN
 }
 
+enum TagType {
+    LANGUAGE
+    FRAMEWORK
+    THEME
+    USERDEFINED
+}
+
 model User {
     id             Int       @id /// The user's id is the same as their gitlab id
     name           String?
@@ -37,6 +44,7 @@ model Assignment {
 
     exercises Exercise[]
     staff     User[]
+    tags      Tag[]
 }
 
 model Exercise {
@@ -57,6 +65,7 @@ model Exercise {
 
     members User[]
     results Result[]
+    tags    Tag[]
 }
 
 model Result {
@@ -72,3 +81,18 @@ model Result {
 
     @@id([exerciseId, dateTime])
 }
+
+model Tag {
+    name String  @id @db.Char(36)
+    type TagType
+
+    assignments Assignment[]
+    exercises   Exercise[]
+}
+
+model TagProposal {
+    name    String  @id @db.Char(36)
+    type    TagType
+    state   String  @default("PendingApproval")
+    details String?
+}
diff --git a/ExpressAPI/prisma/seed.ts b/ExpressAPI/prisma/seed.ts
index 93680f6e393563ebb3adee65bdfe48e5918797a0..6ac1a5d060fb2009293ea630763ec747a2790beb 100644
--- a/ExpressAPI/prisma/seed.ts
+++ b/ExpressAPI/prisma/seed.ts
@@ -6,13 +6,15 @@ import SharedConfig from '../src/shared/config/SharedConfig.js';
 import { UserRole } from '@prisma/client';
 import logger       from '../src/shared/logging/WinstonLogger.js';
 import db           from '../src/helpers/DatabaseHelper.js';
-
+import TagManager  from '../src/managers/TagManager';
+import { TagType }  from '@prisma/client';
 
 async function main() {
     await users();
     await assignments();
     await exercises();
     await results();
+    await tag();
 }
 
 main().then(async () => {
@@ -1580,4 +1582,383 @@ async function results() {
                                    }
                                });
     }
-}
\ No newline at end of file
+}
+
+async function tag() {
+    await db.tag.upsert({
+        where : { name: 'C' },
+        update: {},
+        create: {
+            name          : 'C',
+            type          : TagType.LANGUAGE
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Java' },
+        update: {},
+        create: {
+            name          : 'Java',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Scala' },
+        update: {},
+        create: {
+            name            : 'Scala',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Kotlin' },
+        update: {},
+        create: {
+            name            : 'Kotlin',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Rust' },
+        update: {},
+        create: {
+            name            : 'Rust',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'JavaScript' },
+        update: {},
+        create: {
+            name            : 'JavaScript',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'TypeScript' },
+        update: {},
+        create: {
+            name            : 'TypeScript',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Python' },
+        update: {},
+        create: {
+            name            : 'Python',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'HTML' },
+        update: {},
+        create: {
+            name            : 'HTML',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'CSS' },
+        update: {},
+        create: {
+            name            : 'CSS',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'C++' },
+        update: {},
+        create: {
+            name            : 'C++',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Go' },
+        update: {},
+        create: {
+            name            : 'Go',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'PHP' },
+        update: {},
+        create: {
+            name            : 'PHP',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'C#' },
+        update: {},
+        create: {
+            name            : 'C#',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Swift' },
+        update: {},
+        create: {
+            name            : 'Swift',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Matlab' },
+        update: {},
+        create: {
+            name            : 'Matlab',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'SQL' },
+        update: {},
+        create: {
+            name            : 'SQL',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Assembly' },
+        update: {},
+        create: {
+            name            : 'Assembly',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Ruby' },
+        update: {},
+        create: {
+            name            : 'Ruby',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Fortran' },
+        update: {},
+        create: {
+            name            : 'Fortran',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Pascal' },
+        update: {},
+        create: {
+            name            : 'Pascal',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Visual Basic' },
+        update: {},
+        create: {
+            name            : 'Visual Basic',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'R' },
+        update: {},
+        create: {
+            name            : 'R',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Objective-C' },
+        update: {},
+        create: {
+            name            : 'Objective-C',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Lua' },
+        update: {},
+        create: {
+            name            : 'Lua',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Ada' },
+        update: {},
+        create: {
+            name            : 'Ada',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Haskell' },
+        update: {},
+        create: {
+            name            : 'Haskell',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Shell/PowerShell' },
+        update: {},
+        create: {
+            name            : 'Shell/PowerShell',
+            type          : TagType.LANGUAGE,
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Express' },
+        update: {},
+        create: {
+            name            : 'Express',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Django' },
+        update: {},
+        create: {
+            name            : 'Django',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Ruby on Rails' },
+        update: {},
+        create: {
+            name            : 'Ruby on Rails',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Angular' },
+        update: {},
+        create: {
+            name            : 'Angular',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'React' },
+        update: {},
+        create: {
+            name            : 'React',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Flutter' },
+        update: {},
+        create: {
+            name            : 'Flutter',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Ionic' },
+        update: {},
+        create: {
+            name            : 'Ionic',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Flask' },
+        update: {},
+        create: {
+            name            : 'Flask',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'React Native' },
+        update: {},
+        create: {
+            name            : 'React Native',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Xamarin' },
+        update: {},
+        create: {
+            name            : 'Xamarin',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Laravel' },
+        update: {},
+        create: {
+            name            : 'Laravel',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Spring' },
+        update: {},
+        create: {
+            name            : 'Spring',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Play' },
+        update: {},
+        create: {
+            name            : 'Play',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Symfony' },
+        update: {},
+        create: {
+            name            : 'Symfony',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'ASP.NET' },
+        update: {},
+        create: {
+            name            : 'ASP.NET',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Meteor' },
+        update: {},
+        create: {
+            name            : 'Meteor',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Vue.js' },
+        update: {},
+        create: {
+            name            : 'Vue.js',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Svelte' },
+        update: {},
+        create: {
+            name            : 'Svelte',
+            type          : TagType.FRAMEWORK
+        }
+    });
+    await db.tag.upsert({
+        where : { name: 'Express.js' },
+        update: {},
+        create: {
+            name            : 'Express.js',
+            type          : TagType.FRAMEWORK
+        }
+    });
+}
diff --git a/ExpressAPI/src/managers/TagManager.ts b/ExpressAPI/src/managers/TagManager.ts
new file mode 100644
index 0000000000000000000000000000000000000000..936b3bcb04d3aea3c205215d6d283337ae62f0b9
--- /dev/null
+++ b/ExpressAPI/src/managers/TagManager.ts
@@ -0,0 +1,16 @@
+import { Prisma, Tag }           from '@prisma/client';
+import db                   from '../helpers/DatabaseHelper';
+
+class TagManager {
+	async get(name: string, include: Prisma.TagInclude | undefined = undefined): Promise<Tag | undefined> {
+        return await db.tag.findUnique({
+                                                where  : {
+                                                    name: name
+                                                },
+                                                include: include
+                                            }) as unknown as Tag ?? undefined;
+    }
+}
+
+export default new TagManager();
+
diff --git a/ExpressAPI/src/managers/TagProposalManager.ts b/ExpressAPI/src/managers/TagProposalManager.ts
new file mode 100644
index 0000000000000000000000000000000000000000..89607e24c566a377b3abc0b1e5ac66c5b1129874
--- /dev/null
+++ b/ExpressAPI/src/managers/TagProposalManager.ts
@@ -0,0 +1,17 @@
+import { TagProposal } from '@prisma/client';
+import db              from '../helpers/DatabaseHelper';
+
+
+class TagProposalManager {
+    async get(name: string | undefined = undefined): Promise<TagProposal | undefined> {
+        return await db.tagProposal.findUnique({
+                                                   where: {
+                                                       name: name
+                                                   }
+                                               }) as unknown as TagProposal ?? undefined;
+    }
+}
+
+
+export default new TagProposalManager();
+
diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
index a1836d10d7349c935920061299226a7eb825568c..1dffaef8a782bb06c01e485705afc830e563f441 100644
--- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
+++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts
@@ -1,8 +1,10 @@
-import { Express }       from 'express-serve-static-core';
-import express           from 'express';
-import { StatusCodes }   from 'http-status-codes';
-import ExerciseManager   from '../managers/ExerciseManager.js';
-import AssignmentManager from '../managers/AssignmentManager.js';
+import { Express }        from 'express-serve-static-core';
+import express            from 'express';
+import { StatusCodes }    from 'http-status-codes';
+import ExerciseManager    from '../managers/ExerciseManager';
+import AssignmentManager  from '../managers/AssignmentManager';
+import TagManager         from '../managers/TagManager';
+import TagProposalManager from '../managers/TagProposalManager';
 
 
 type GetFunction = (id: string | number, ...args: Array<unknown>) => Promise<unknown>
@@ -27,8 +29,10 @@ class ParamsCallbackManager {
     initBoundParams(req: express.Request) {
         if ( !req.boundParams ) {
             req.boundParams = {
-                assignment: undefined,
-                exercise  : undefined
+                assignment : undefined,
+                exercise   : undefined,
+                tag        : undefined,
+                tagProposal: undefined
             };
         }
     }
@@ -44,6 +48,12 @@ class ParamsCallbackManager {
             members   : true,
             results   : true
         } ], 'exercise');
+
+        this.listenParam('tagName', backend, (TagManager.get as GetFunction).bind(TagManager), [ {
+            assignments: true
+        } ], 'tag');
+
+        this.listenParam('tagProposalName', backend, (TagProposalManager.get as GetFunction).bind(TagProposalManager), [ {} ], 'tagProposal');
     }
 }
 
diff --git a/ExpressAPI/src/middlewares/SecurityMiddleware.ts b/ExpressAPI/src/middlewares/SecurityMiddleware.ts
index 02c54c16b151e259a45b8ff47fda8f9f50b17bf6..6edc8bee8f308084bdf03e542f0f91949578788e 100644
--- a/ExpressAPI/src/middlewares/SecurityMiddleware.ts
+++ b/ExpressAPI/src/middlewares/SecurityMiddleware.ts
@@ -13,6 +13,8 @@ class SecurityMiddleware {
     private async checkType(checkType: SecurityCheckType, req: express.Request): Promise<boolean> {
         try {
             switch ( String(checkType) ) {
+                case SecurityCheckType.ADMIN.valueOf():
+                    return req.session.profile.isAdmin;
                 case SecurityCheckType.TEACHING_STAFF.valueOf():
                     return req.session.profile.isTeachingStaff;
                 case SecurityCheckType.ASSIGNMENT_STAFF.valueOf():
diff --git a/ExpressAPI/src/routes/ApiRoutesManager.ts b/ExpressAPI/src/routes/ApiRoutesManager.ts
index 57a418842eb2003e5cab56d24344877837f90691..1508de4124baa104b7aac892da229685ecb9b998 100644
--- a/ExpressAPI/src/routes/ApiRoutesManager.ts
+++ b/ExpressAPI/src/routes/ApiRoutesManager.ts
@@ -5,6 +5,7 @@ import SessionRoutes    from './SessionRoutes.js';
 import AssignmentRoutes from './AssignmentRoutes.js';
 import GitlabRoutes     from './GitlabRoutes.js';
 import ExerciseRoutes   from './ExerciseRoutes.js';
+import TagsRoutes       from './TagRoutes';
 
 
 class AdminRoutesManager implements RoutesManager {
@@ -14,6 +15,7 @@ class AdminRoutesManager implements RoutesManager {
         GitlabRoutes.registerOnBackend(backend);
         AssignmentRoutes.registerOnBackend(backend);
         ExerciseRoutes.registerOnBackend(backend);
+        TagsRoutes.registerOnBackend(backend);
     }
 }
 
diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
index 5f46129df7cf916d4b7a94f17bb108f32289ee4c..0f06d74728a684153763d8b7d710809ff6f6e9f4 100644
--- a/ExpressAPI/src/routes/AssignmentRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -112,7 +112,8 @@ class AssignmentRoutes implements RoutesManager {
                                                            include: {
                                                                assignment: false,
                                                                members   : true,
-                                                               results   : true
+                                                               results   : true,
+                                                               tags      : true
                                                            }
                                                        });
             }
diff --git a/ExpressAPI/src/routes/TagRoutes.ts b/ExpressAPI/src/routes/TagRoutes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7e673403fe94dc209d53a91616c7140bc0ea1ec
--- /dev/null
+++ b/ExpressAPI/src/routes/TagRoutes.ts
@@ -0,0 +1,132 @@
+import express, { RequestHandler } from 'express';
+import { TagType }                 from '@prisma/client';
+import * as ExpressValidator       from 'express-validator';
+import { StatusCodes }             from 'http-status-codes';
+import RoutesManager               from '../express/RoutesManager';
+import { Express }                 from 'express-serve-static-core';
+import db                          from '../helpers/DatabaseHelper';
+import SecurityCheckType           from '../types/SecurityCheckType';
+import SecurityMiddleware          from '../middlewares/SecurityMiddleware';
+import ParamsValidatorMiddleware   from '../middlewares/ParamsValidatorMiddleware';
+import DojoStatusCode              from '../shared/types/Dojo/DojoStatusCode';
+
+
+class TagRoutes implements RoutesManager {
+    private readonly tagValidator: ExpressValidator.Schema = {
+        name: {
+            trim    : true,
+            notEmpty: true
+        },
+        type: {
+            trim    : true,
+            notEmpty: true
+        }
+    };
+
+    private readonly tagProposalAnswerValidator: ExpressValidator.Schema = {
+        state  : {
+            trim    : true,
+            notEmpty: true
+        },
+        details: {
+            trim    : true,
+            optional: true
+        }
+    };
+
+    registerOnBackend(backend: Express) {
+        backend.post('/tags', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.tagValidator), this.createTag.bind(this) as RequestHandler);
+        backend.delete('/tags/:tagName', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.deleteTag.bind(this) as RequestHandler);
+        backend.get('/tags/proposals', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.getTagProposals.bind(this) as RequestHandler);
+        backend.post('/tags/proposals', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.tagValidator), this.createTagProposal.bind(this) as RequestHandler);
+        backend.patch('/tags/proposals/:tagProposalName', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), ParamsValidatorMiddleware.validate(this.tagProposalAnswerValidator), this.validateTag.bind(this) as RequestHandler);
+    }
+
+    private async createTag(req: express.Request, res: express.Response) {
+        const tagName = req.body.name;
+        const tagType = (req.body.type as string).toUpperCase() as TagType;
+
+        if ( tagType !== TagType.USERDEFINED && !req.session.profile.isAdmin ) {
+            return req.session.sendResponse(res, StatusCodes.FORBIDDEN, {}, 'Only admins can create non userDefined tags', DojoStatusCode.TAG_ONLY_ADMIN_CREATION);
+        }
+
+        await db.tag.create({
+                                data: {
+                                    name: tagName,
+                                    type: tagType
+                                }
+                            });
+
+        return req.session.sendResponse(res, StatusCodes.OK);
+    }
+
+    private async deleteTag(req: express.Request, res: express.Response) {
+        if ( req.boundParams.tag!.assignments.length > 0 ) {
+            return req.session.sendResponse(res, StatusCodes.LOCKED, {}, 'Tag is used in assignments', DojoStatusCode.TAG_WITH_ACTIVE_LINK_DELETION);
+        }
+
+        await db.tag.delete({
+                                where: { name: req.boundParams.tag!.name }
+                            });
+
+        return req.session.sendResponse(res, StatusCodes.OK);
+    }
+
+    private async getTagProposals(req: express.Request, res: express.Response) {
+        const state = req.query.stateFilter as string;
+
+        const tagProposals = await db.tagProposal.findMany(state ? {
+            where: { state: state }
+        } : {});
+
+        return req.session.sendResponse(res, StatusCodes.OK, tagProposals);
+    }
+
+    private async createTagProposal(req: express.Request, res: express.Response) {
+        const tagName = req.body.name;
+        const tagType = (req.body.type as string).toUpperCase() as TagType;
+
+        await db.tagProposal.create({
+                                        data: {
+                                            name: tagName,
+                                            type: tagType
+                                        }
+                                    });
+
+        return req.session.sendResponse(res, StatusCodes.OK);
+    }
+
+    private async validateTag(req: express.Request, res: express.Response) {
+        if ( req.boundParams.tagProposal!.state === 'PendingApproval' ) {
+            const state: string = req.body.state;
+
+            if ( state === 'Approved' ) {
+                try {
+                    await db.tag.create({
+                                            data: {
+                                                name: req.boundParams.tagProposal!.name,
+                                                type: req.boundParams.tagProposal!.type
+                                            }
+                                        });
+                } catch ( error ) {
+                    // empty
+                }
+            }
+
+            await db.tagProposal.update({
+                                            where: { name: req.boundParams.tagProposal?.name },
+                                            data : {
+                                                state  : state,
+                                                details: req.body.details ?? ''
+                                            }
+                                        });
+
+            return req.session.sendResponse(res, StatusCodes.OK);
+        } else {
+            return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, {}, 'Tag proposal is not pending', DojoStatusCode.TAG_PROPOSAL_ANSWER_NOT_PENDING);
+        }
+    }
+}
+
+
+export default new TagRoutes();
diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared
index c2afa861bf6306ddec79ffd465a4c7b0edcd3453..708a3c0805fb2b2d853a781bde86a10d5282545a 160000
--- a/ExpressAPI/src/shared
+++ b/ExpressAPI/src/shared
@@ -1 +1 @@
-Subproject commit c2afa861bf6306ddec79ffd465a4c7b0edcd3453
+Subproject commit 708a3c0805fb2b2d853a781bde86a10d5282545a
diff --git a/ExpressAPI/src/types/DatabaseTypes.ts b/ExpressAPI/src/types/DatabaseTypes.ts
index 011d2efc5643f66cefb4da435b26e4579accfc32..2a7109c532b14733f5f32328757d922a404821b7 100644
--- a/ExpressAPI/src/types/DatabaseTypes.ts
+++ b/ExpressAPI/src/types/DatabaseTypes.ts
@@ -9,14 +9,16 @@ const userBase = Prisma.validator<Prisma.UserDefaultArgs>()({
 const assignmentBase = Prisma.validator<Prisma.AssignmentDefaultArgs>()({
                                                                             include: {
                                                                                 exercises: true,
-                                                                                staff    : true
+                                                                                staff    : true,
+                                                                                tags     : true
                                                                             }
                                                                         });
 const exerciseBase = Prisma.validator<Prisma.ExerciseDefaultArgs>()({
                                                                         include: {
                                                                             assignment: true,
                                                                             members   : true,
-                                                                            results   : true
+                                                                            results   : true,
+                                                                            tags      : true
                                                                         }
                                                                     });
 const resultBase = Prisma.validator<Prisma.ResultDefaultArgs>()({
@@ -24,6 +26,12 @@ const resultBase = Prisma.validator<Prisma.ResultDefaultArgs>()({
                                                                         exercise: true
                                                                     }
                                                                 });
+const tagBase = Prisma.validator<Prisma.TagDefaultArgs>()({
+                                                              include: {
+                                                                  exercises  : true,
+                                                                  assignments: true
+                                                              }
+                                                          });
 
 
 export type User = Prisma.UserGetPayload<typeof userBase> & {
@@ -37,4 +45,5 @@ export type Exercise = Prisma.ExerciseGetPayload<typeof exerciseBase> & {
 export type Assignment = Prisma.AssignmentGetPayload<typeof assignmentBase> & {
     corrections: LazyVal<Array<Exercise>>
 }
-export type Result = Prisma.ResultGetPayload<typeof resultBase>
\ No newline at end of file
+export type Result = Prisma.ResultGetPayload<typeof resultBase>
+export type Tag = Prisma.TagGetPayload<typeof tagBase>
\ No newline at end of file
diff --git a/ExpressAPI/src/types/SecurityCheckType.ts b/ExpressAPI/src/types/SecurityCheckType.ts
index 3a0b733103af4604e10f917ec3edc4c7f56b3b66..17d026a253327907355176e847a6ac4a7047922f 100644
--- a/ExpressAPI/src/types/SecurityCheckType.ts
+++ b/ExpressAPI/src/types/SecurityCheckType.ts
@@ -1,5 +1,6 @@
 enum SecurityCheckType {
     TEACHING_STAFF          = 'teachingStaff',
+    ADMIN                   = 'admin',
     ASSIGNMENT_STAFF        = 'assignmentStaff',
     ASSIGNMENT_IS_PUBLISHED = 'assignmentIsPublished',
     EXERCISE_SECRET         = 'exerciseSecret',
diff --git a/ExpressAPI/src/types/express/index.d.ts b/ExpressAPI/src/types/express/index.d.ts
index 744609f9823e62e1348d63df9252a48ccfa3b64b..59a5e0056b7d85185e11d49a66d82153cab01192 100644
--- a/ExpressAPI/src/types/express/index.d.ts
+++ b/ExpressAPI/src/types/express/index.d.ts
@@ -1,5 +1,6 @@
-import Session                  from '../../controllers/Session.js';
-import { Assignment, Exercise } from '../DatabaseTypes.js';
+import Session                       from '../../controllers/Session.js';
+import { Assignment, Exercise, Tag } from '../DatabaseTypes';
+import { TagProposal }               from '@prisma/client';
 
 // to make the file a module and avoid the TypeScript error
 export {};
@@ -9,7 +10,7 @@ declare global {
         export interface Request {
             session: Session,
             boundParams: {
-                assignment: Assignment | undefined, exercise: Exercise | undefined
+                assignment: Assignment | undefined, exercise: Exercise | undefined, tag: Tag | undefined, tagProposal: TagProposal | undefined
             }
         }
     }