diff --git a/backend/app/crud/__init__.py b/backend/app/crud/__init__.py
index 69ccf364cfcad47854f4841912cc47f8d4c85a19..b8a8c2a8b60dc83c89d65e489b6133a8930a4b86 100644
--- a/backend/app/crud/__init__.py
+++ b/backend/app/crud/__init__.py
@@ -9,6 +9,7 @@ from hashing import Hasher
 
 from crud.tests import *
 from crud.studies import *
+from crud.tasks import *
 
 
 def get_user(db: Session, user_id: int):
diff --git a/backend/app/crud/tasks.py b/backend/app/crud/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..7877449104999f6f7071cfe7018a9218fa974960
--- /dev/null
+++ b/backend/app/crud/tasks.py
@@ -0,0 +1,32 @@
+from sqlalchemy.orm import Session
+
+import models
+import schemas
+
+
+def get_tasks(db: Session, skip: int = 0):
+    return db.query(models.Task).offset(skip).all()
+
+
+def get_task(db: Session, task_id: int):
+    return db.query(models.Task).filter(models.Task.id == task_id).first()
+
+
+def create_task(db: Session, task: schemas.TaskCreate) -> models.Task:
+    db_task = models.Task(**task.model_dump())
+    db.add(db_task)
+    db.commit()
+    db.refresh(db_task)
+    return db_task
+
+
+def update_task(db: Session, task: schemas.TaskCreate, task_id: int) -> None:
+    db.query(models.Task).filter(models.Task.id == task_id).update(
+        {**task.model_dump(exclude_unset=True)}
+    )
+    db.commit()
+
+
+def delete_task(db: Session, task_id: int) -> None:
+    db.query(models.Task).filter(models.Task.id == task_id).delete()
+    db.commit()
diff --git a/backend/app/main.py b/backend/app/main.py
index 8aee55ca3f218e73b1ba5419e992dc095099a514..fec8ab0a205a0481b0613d7b703e0ff1aee429ae 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -28,6 +28,7 @@ import config
 from security import jwt_cookie, get_jwt_user
 from routes.tests import testRouter
 from routes.studies import studiesRouter
+from routes.tasks import taskRouter
 
 websocket_users = defaultdict(lambda: defaultdict(set))
 websocket_users_global = defaultdict(set)
@@ -1064,5 +1065,6 @@ v1Router.include_router(studyRouter)
 v1Router.include_router(websocketRouter)
 v1Router.include_router(testRouter)
 v1Router.include_router(studiesRouter)
+v1Router.include_router(taskRouter)
 apiRouter.include_router(v1Router)
 app.include_router(apiRouter)
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
index 131af45538f6e3c6e8ea62551bd1fee65989250a..206737aa04b7e86d0731a9197ee30c6be555b80e 100644
--- a/backend/app/models/__init__.py
+++ b/backend/app/models/__init__.py
@@ -18,6 +18,7 @@ from utils import datetime_aware
 
 from models.studies import *
 from models.tests import *
+from models.tasks import *
 
 
 class UserType(Enum):
diff --git a/backend/app/models/tasks.py b/backend/app/models/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3f7a391aad9b8d66d408a73488459f0f10d1a1b
--- /dev/null
+++ b/backend/app/models/tasks.py
@@ -0,0 +1,25 @@
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
+from utils import datetime_aware
+from database import Base
+
+
+class Task(Base):
+    __tablename__ = "tasks"
+
+    id = Column(Integer, primary_key=True, index=True)
+    level = Column(String, nullable=False)
+    shortTitle = Column(String, nullable=False)
+    instructions = Column(String, nullable=True)
+    learnerInstructions = Column(String, nullable=True)
+    examples = Column(String, nullable=False)
+
+
+class TaskStatus(Base):
+    __tablename__ = "task_statuses"
+    id = Column(Integer, primary_key=True, index=True)
+    task_id = Column(Integer, ForeignKey("tasks.id"), nullable=False)
+    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+    status = Column(String, nullable=False)
+    tutor_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+    session_id = Column(Integer, ForeignKey("sessions.id"), nullable=False)
+    created_at = Column(DateTime, default=datetime_aware)
diff --git a/backend/app/routes/tasks.py b/backend/app/routes/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..136eb833257bdd55ccd03f8af07f781a1ed281dc
--- /dev/null
+++ b/backend/app/routes/tasks.py
@@ -0,0 +1,58 @@
+from fastapi import APIRouter, Depends, HTTPException, status
+from sqlalchemy.orm import Session
+
+import crud
+import schemas
+from database import get_db
+from routes.decorators import require_admin
+
+taskRouter = APIRouter(prefix="/tasks", tags=["Tasks"])
+
+
+@require_admin("You do not have permission to create a task.")
+@taskRouter.post("", status_code=status.HTTP_201_CREATED)
+def create_task(
+    task: schemas.TaskCreate,
+    db: Session = Depends(get_db),
+):
+    return crud.create_task(db, task).id
+
+
+@require_admin("You do not have permission to edit a task.")
+@taskRouter.put("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
+def update_task(
+    task_id: int,
+    task: schemas.TaskCreate,
+    db: Session = Depends(get_db),
+):
+    return crud.update_task(db, task, task_id)
+
+
+@require_admin("You do not have permission to delete a task.")
+@taskRouter.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
+def delete_task(
+    task_id: int,
+    db: Session = Depends(get_db),
+):
+    return crud.delete_task(db, task_id)
+
+
+@taskRouter.get("", response_model=list[schemas.Task])
+def get_tasks(
+    skip: int = 0,
+    db: Session = Depends(get_db),
+):
+    return crud.get_tasks(db, skip)
+
+
+@taskRouter.get("/{task_id}", response_model=schemas.Task)
+def get_task(
+    task_id: int,
+    db: Session = Depends(get_db),
+):
+    task = crud.get_task(db, task_id)
+    if task is None:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
+        )
+    return task
diff --git a/backend/app/routes/tests.py b/backend/app/routes/tests.py
index f310d546cfad88c57636aad32c2d14aaa6ac4c26..b19275d8bf53b4e849d942a7f720191bf3d896d5 100644
--- a/backend/app/routes/tests.py
+++ b/backend/app/routes/tests.py
@@ -1,6 +1,5 @@
 from fastapi import APIRouter, Depends, HTTPException, status
 from sqlalchemy.orm import Session
-from starlette.status import HTTP_200_OK
 
 import crud
 import schemas
diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py
index cfc6318044d0b78b7fe60a9ea8dc68cc7fdba32f..7affcd05d54f939700969e9fe9e00de67c05d520 100644
--- a/backend/app/schemas/__init__.py
+++ b/backend/app/schemas/__init__.py
@@ -3,6 +3,7 @@ from pydantic import BaseModel, NaiveDatetime
 from schemas.studies import *
 from schemas.tests import *
 from schemas.users import *
+from schemas.tasks import *
 
 
 class LoginData(BaseModel):
diff --git a/backend/app/schemas/tasks.py b/backend/app/schemas/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..524dcea453ee2a384ac5d6e04d471768d932785a
--- /dev/null
+++ b/backend/app/schemas/tasks.py
@@ -0,0 +1,13 @@
+from pydantic import BaseModel
+
+
+class TaskCreate(BaseModel):
+    level: str
+    shortTitle: str
+    instructions: str
+    learnerInstructions: str
+    examples: str
+
+
+class Task(TaskCreate):
+    id: int
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index 1cade0ccb2b5c789800e88ddf29d6b70a3c0ab38..cc5da60fdc709a805df8eb22b84f36e3ddc8934e 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -12,7 +12,8 @@
 		"admin": {
 			"users": "Utilisateurs",
 			"sessions": "Sessions",
-			"studies": "Études"
+			"studies": "Études",
+			"tasks": "Tâches"
 		}
 	},
 	"chatbox": {
@@ -399,6 +400,15 @@
 		"taskTests": "Tests de langue",
 		"typingTests": "Tests de frappe"
 	},
+	"tasks": {
+		"createTitle": "Créer une nouvelle tâche",
+		"editTitle": "Modifier la tâche",
+		"level": "Niveau CEFR",
+		"shortTitle": "Nom de la tâche (max 5 mots)",
+		"instructions": "Instructions le tuteur (peut être sur plusieurs lignes)",
+		"learnerInstructions": "Instructions pour l'apprenant (peut être sur plusieurs lignes)",
+		"examples": "Exemples (peut être sur plusieurs lignes)"
+	},
 	"button": {
 		"create": "Créer",
 		"submit": "Envoyer",
diff --git a/frontend/src/lib/api/tasks.ts b/frontend/src/lib/api/tasks.ts
new file mode 100644
index 0000000000000000000000000000000000000000..614bd502edf0f45cca90e0899890f4b6e4abfbcd
--- /dev/null
+++ b/frontend/src/lib/api/tasks.ts
@@ -0,0 +1,68 @@
+import type { fetchType } from '$lib/utils/types';
+
+export async function createTaskAPI(
+	fetch: fetchType,
+	level: string,
+	shortTitle: string,
+	instructions: string,
+	learnerInstructions: string,
+	examples: string
+): Promise<number | undefined> {
+	const response = await fetch(`/api/tasks`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			level,
+			shortTitle,
+			instructions,
+			learnerInstructions,
+			examples
+		})
+	});
+
+	if (!response.ok) return;
+
+	return parseInt(await response.text());
+}
+
+export async function getTasksAPI(fetch: fetchType) {
+	const response = await fetch('/api/tasks');
+	if (!response.ok) return null;
+	return await response.json();
+}
+
+export async function getTaskAPI(fetch: fetchType, task_id: number) {
+	const response = await fetch(`/api/tasks/${task_id}`);
+	if (!response.ok) return null;
+	return await response.json();
+}
+
+export async function updateTaskAPI(
+	fetch: fetchType,
+	task_id: number,
+	level: string,
+	shortTitle: string,
+	instructions: string,
+	learnerInstructions: string,
+	examples: string
+): Promise<boolean> {
+	const response = await fetch(`/api/tasks/${task_id}`, {
+		method: 'PUT',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			level,
+			shortTitle,
+			instructions,
+			learnerInstructions,
+			examples
+		})
+	});
+	return response.ok;
+}
+
+export async function deleteTaskAPI(fetch: fetchType, task_id: number): Promise<boolean> {
+	const response = await fetch(`/api/tasks/${task_id}`, {
+		method: 'DELETE'
+	});
+	return response.ok;
+}
diff --git a/frontend/src/lib/components/studies/StudyForm.svelte b/frontend/src/lib/components/studies/StudyForm.svelte
index 9cd142beacab75f6f6a645bf5936573211a0f82c..4ecba1e3e2d1e417632e2ba64d27bb5655e7c9d2 100644
--- a/frontend/src/lib/components/studies/StudyForm.svelte
+++ b/frontend/src/lib/components/studies/StudyForm.svelte
@@ -16,7 +16,6 @@
 		study = $bindable(),
 		possibleTests,
 		mode,
-		data,
 		form
 	}: {
 		study: Study | null;
diff --git a/frontend/src/lib/types/tasks.ts b/frontend/src/lib/types/tasks.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce6129f86bb6f2eacae465f8d55a133476d12e2f
--- /dev/null
+++ b/frontend/src/lib/types/tasks.ts
@@ -0,0 +1,71 @@
+import { toastAlert } from '$lib/utils/toasts';
+
+export default class Task {
+	private _id: number;
+	private _level: string;
+	private _shortTitle: string;
+	private _instructions: string;
+	private _learnerInstructions: string;
+	private _examples: string;
+
+	constructor(
+		id: number,
+		level: string,
+		shortTitle: string,
+		instructions: string,
+		learnerInstructions: string,
+		examples: string
+	) {
+		this._id = id;
+		this._level = level;
+		this._shortTitle = shortTitle;
+		this._instructions = instructions;
+		this._learnerInstructions = learnerInstructions;
+		this._examples = examples;
+	}
+
+	get id(): number {
+		return this._id;
+	}
+	get level(): string {
+		return this._level;
+	}
+	get shortTitle(): string {
+		return this._shortTitle;
+	}
+	get instructions(): string {
+		return this._instructions;
+	}
+	get learnerInstructions(): string {
+		return this._learnerInstructions;
+	}
+	get examples(): string {
+		return this._examples;
+	}
+
+	static parse(data: any): Task | null {
+		if (data === null) {
+			toastAlert('Failed to parse tasks data');
+			return null;
+		}
+
+		return new Task(
+			data.id,
+			data.level,
+			data.shortTitle,
+			data.instructions,
+			data.learnerInstructions,
+			data.examples
+		);
+	}
+
+	static parseAll(data: any): Task[] {
+		if (data === null) {
+			toastAlert('Failed to parse tasks data');
+			return [];
+		}
+		return data
+			.map((task: any) => Task.parse(task))
+			.filter((task: Task | null): task is Task => task !== null);
+	}
+}
diff --git a/frontend/src/routes/Header.svelte b/frontend/src/routes/Header.svelte
index 876faf2f741150f97c9aaa4e5e0bb02f82717092..dbb172773b765caaeaec429c1998c2ce5b2e6bfa 100644
--- a/frontend/src/routes/Header.svelte
+++ b/frontend/src/routes/Header.svelte
@@ -93,6 +93,11 @@
 										{$t('header.admin.studies')}
 									</a>
 								</li>
+								<li>
+									<a data-sveltekit-reload href="/admin/tasks">
+										{$t('header.admin.tasks')}
+									</a>
+								</li>
 							</ul>
 						</details>
 					</li>
diff --git a/frontend/src/routes/admin/tasks/+page.svelte b/frontend/src/routes/admin/tasks/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d503e175ea5c28f0d8c8ee775eba2b906bfb1698
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/+page.svelte
@@ -0,0 +1,39 @@
+<script lang="ts">
+	import { t } from '$lib/services/i18n';
+	import type { PageData } from './$types';
+	import Task from '$lib/types/tasks';
+
+	const { data }: { data: PageData } = $props();
+
+	let tasks: Task[] = $state(data.tasks);
+</script>
+
+<h1 class="text-xl font-bold m-5 text-center">
+	{$t('header.admin.tasks')}
+</h1>
+
+<table class="table max-w-5xl mx-auto text-center">
+	<thead>
+		<tr>
+			<th>#</th>
+			<th>{$t('tasks.level')}</th>
+			<th>{$t('tasks.shortTitle')}</th>
+		</tr>
+	</thead>
+	<tbody>
+		{#each tasks as task (task.id)}
+			<tr
+				class="hover:bg-gray-100 hover:cursor-pointer"
+				onclick={() => (window.location.href = `/admin/tasks/${task.id}`)}
+			>
+				<td>{task.id}</td>
+				<td>{task.level}</td>
+				<td>{task.shortTitle}</td>
+			</tr>
+		{/each}
+	</tbody>
+</table>
+
+<div class="mt-8 w-[64rem] mx-auto">
+	<a class="button" href="/admin/tasks/new">{$t('tasks.create')}</a>
+</div>
diff --git a/frontend/src/routes/admin/tasks/+page.ts b/frontend/src/routes/admin/tasks/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..732612b1e902bf254b14aa93f792d86cfa0ace1e
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/+page.ts
@@ -0,0 +1,11 @@
+import { getTasksAPI } from '$lib/api/tasks';
+import Task from '$lib/types/tasks';
+import { type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ fetch }) => {
+	const tasks = Task.parseAll(await getTasksAPI(fetch));
+
+	return {
+		tasks
+	};
+};
diff --git a/frontend/src/routes/admin/tasks/TaskForm.svelte b/frontend/src/routes/admin/tasks/TaskForm.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..42c0fb295579e0109f86340d197ef76d8da4ad39
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/TaskForm.svelte
@@ -0,0 +1,101 @@
+<script lang="ts">
+	import { deleteTaskAPI } from '$lib/api/tasks';
+	import { t } from '$lib/services/i18n';
+	import type { Task } from '$lib/types/tasks';
+	import autosize from 'svelte-autosize';
+	import { ShoppingBag } from 'svelte-hero-icons';
+
+	let { form, task }: { form: FormData; task: Task | null } = $props();
+
+	async function deleteTask() {
+		if (!task) return;
+		await deleteTaskAPI(fetch, task.id);
+		document.location.href = '/admin/tasks';
+	}
+</script>
+
+<div class="mx-auto w-full max-w-5xl px-4">
+	<h2 class="text-xl font-bold m-5 text-center">
+		{$t(task === null ? 'tasks.createTitle' : 'tasks.editTitle')}
+	</h2>
+
+	{#if form?.message}
+		<div class="alert alert-error shadow-lg mb-4">
+			{form?.message}
+		</div>
+	{/if}
+
+	<form method="post">
+		<label class="label" for="level">{$t('tasks.level')} *</label>
+		<select
+			class="select select-bordered w-full"
+			id="level"
+			name="level"
+			required
+			value={task?.level}
+		>
+			<option value="A1">A1</option>
+			<option value="A2">A2</option>
+			<option value="B1">B1</option>
+			<option value="B2">B2</option>
+			<option value="C1">C1</option>
+		</select>
+
+		<label class="label" for="shortTitle">{$t('tasks.shortTitle')} *</label>
+		<input
+			class="input w-full"
+			type="text"
+			id="shortTitle"
+			name="shortTitle"
+			required
+			value={task?.shortTitle}
+		/>
+
+		<label class="label" for="instructions">{$t('tasks.instructions')}</label>
+		<textarea
+			use:autosize
+			class="input w-full"
+			id="instructions"
+			name="instructions"
+			value={task?.instructions}
+		></textarea>
+
+		<label class="label" for="learnerInstructions">{$t('tasks.learnerInstructions')}</label>
+		<textarea
+			use:autosize
+			class="input w-full"
+			id="learnerInstructions"
+			name="learnerInstructions"
+			value={task?.learnerInstructions}
+		></textarea>
+
+		<label class="label" for="examples">{$t('tasks.examples')} *</label>
+		<textarea
+			use:autosize
+			class="input w-full"
+			id="examples"
+			name="examples"
+			required
+			value={task?.examples}
+		></textarea>
+
+		<div class="mt-4 mb-6">
+			<input type="hidden" name="id" value={task ? task.id : ''} />
+			<button type="submit" class="button">
+				{$t(task === null ? 'button.create' : 'button.update')}
+			</button>
+			<a class="btn btn-outline float-end ml-2" href="/admin/tasks">
+				{$t('button.cancel')}
+			</a>
+			{#if task}
+				<button
+					type="button"
+					class="btn btn-error btn-outline float-end"
+					onclick={() => confirm($t('tasks.deleteConfirm')) && deleteTask()}
+				>
+					{$t('button.delete')}
+				</button>
+			{/if}
+		</div>
+	</form>
+</div>
diff --git a/frontend/src/routes/admin/tasks/[id]/+page.server.ts b/frontend/src/routes/admin/tasks/[id]/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ea11ed7010df5467a0ef6635c4220f319f1cb23
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/[id]/+page.server.ts
@@ -0,0 +1,49 @@
+import { updateTaskAPI } from '$lib/api/tasks';
+import { redirect, type Actions } from '@sveltejs/kit';
+
+export const actions: Actions = {
+	default: async ({ request, fetch }) => {
+		const formData = await request.formData();
+
+		const idStr = formData.get('id')?.toString();
+		const level = formData.get('level')?.toString();
+		const shortTitle = formData.get('shortTitle')?.toString();
+
+		const instructions = formData.get('instructions')?.toString() || '';
+
+		const learnerInstructions = formData.get('learnerInstructions')?.toString() || '';
+
+		const examples = formData.get('examples')?.toString();
+
+		if (!level || !shortTitle || !examples || !idStr) {
+			return {
+				message: 'Invalid request: Missing required fields'
+			};
+		}
+
+		const id = parseInt(idStr, 10);
+		if (isNaN(id)) {
+			return {
+				message: 'Invalid request: Invalid task ID'
+			};
+		}
+
+		const ok = await updateTaskAPI(
+			fetch,
+			id,
+			level,
+			shortTitle,
+			instructions,
+			learnerInstructions,
+			examples
+		);
+
+		if (!ok) {
+			return {
+				message: 'Invalid request: Failed to update task'
+			};
+		}
+
+		return redirect(303, `/admin/tasks`);
+	}
+};
diff --git a/frontend/src/routes/admin/tasks/[id]/+page.svelte b/frontend/src/routes/admin/tasks/[id]/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..08c2e686a3f917cf16f8339c4a21f0462cd461bd
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/[id]/+page.svelte
@@ -0,0 +1,8 @@
+<script lang="ts">
+	import type { PageData } from './$types';
+	import TaskForm from '../TaskForm.svelte';
+
+	let { form, data }: { form: FormData; data: PageData } = $props();
+</script>
+
+<TaskForm {form} task={data.task} />
diff --git a/frontend/src/routes/admin/tasks/[id]/+page.ts b/frontend/src/routes/admin/tasks/[id]/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d05028075f04922cf32e5f12ea4f8586062e936c
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/[id]/+page.ts
@@ -0,0 +1,20 @@
+import { getTaskAPI } from '$lib/api/tasks';
+import Task from '$lib/types/tasks';
+import { error, type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ fetch, params }) => {
+	const idStr = params.id;
+	if (!idStr) {
+		return error(400, 'Invalid request: Missing task ID');
+	}
+	const id = parseInt(idStr, 10);
+
+	const task = Task.parse(await getTaskAPI(fetch, id));
+	if (!task) {
+		return error(404, 'Task not found');
+	}
+
+	return {
+		task
+	};
+};
diff --git a/frontend/src/routes/admin/tasks/new/+page.server.ts b/frontend/src/routes/admin/tasks/new/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..263ac17c3fb9723fc38e621f4742dfbdfed30ad4
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/new/+page.server.ts
@@ -0,0 +1,40 @@
+import { createTaskAPI } from '$lib/api/tasks';
+import { redirect, type Actions } from '@sveltejs/kit';
+
+export const actions: Actions = {
+	default: async ({ request, fetch }) => {
+		const formData = await request.formData();
+
+		const level = formData.get('level')?.toString();
+		const shortTitle = formData.get('shortTitle')?.toString();
+
+		const instructions = formData.get('instructions')?.toString() || '';
+
+		const learnerInstructions = formData.get('learnerInstructions')?.toString() || '';
+
+		const examples = formData.get('examples')?.toString();
+
+		if (!level || !shortTitle || !examples) {
+			return {
+				message: 'Invalid request: Missing required fields'
+			};
+		}
+
+		const id = await createTaskAPI(
+			fetch,
+			level,
+			shortTitle,
+			instructions,
+			learnerInstructions,
+			examples
+		);
+
+		if (id === undefined) {
+			return {
+				message: 'Invalid request: Failed to create task'
+			};
+		}
+
+		return redirect(303, `/admin/tasks`);
+	}
+};
diff --git a/frontend/src/routes/admin/tasks/new/+page.svelte b/frontend/src/routes/admin/tasks/new/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2998be34552faff8ff983a2ee7f161c6df9684d1
--- /dev/null
+++ b/frontend/src/routes/admin/tasks/new/+page.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+	import TaskForm from '../TaskForm.svelte';
+
+	let { form }: { form: FormData } = $props();
+</script>
+
+<TaskForm {form} task={null} />