diff --git a/reader/forms.py b/reader/forms.py
index b20e692afe84da92032b55a04ddff1be3d91c10f..b2f833ae53e36437324b7098fba173db9c4b7f1b 100644
--- a/reader/forms.py
+++ b/reader/forms.py
@@ -10,7 +10,7 @@ from django.core.exceptions import ValidationError
 from django.db.models.fields.files import FieldFile
 
 from api_koreader.xpointer_cfi_utils import validate_epub
-from reader.models import Book
+from reader.models import Book, UserBook
 
 
 class FileTypeValidator:
@@ -68,3 +68,9 @@ class BookForm(forms.ModelForm):
         if not self.request.user.has_perm("reader.book_create_loanable"):
             self.fields["loanable"].disabled = True
             self.fields["loanable_copies"].disabled = True
+
+
+class UserBookProgressForm(forms.ModelForm):
+    class Meta:
+        model = UserBook
+        fields = ["percentage_read", "last_progress_device", "last_progress_cfi"]
diff --git a/reader/urls.py b/reader/urls.py
index fe79163a827b3d2c599e3c509d740d5140b4090f..3994eec433dddebfb645c1e0856507b07a1261a1 100644
--- a/reader/urls.py
+++ b/reader/urls.py
@@ -9,4 +9,5 @@ urlpatterns = [
     path("books/<int:book_id>", views.view_book, name='view_book'),
     path("books/<int:book_id>/to_library", views.add_to_library, name='add_to_library'),
     path("books/<int:book_id>/read", views.read_book, name='read_book'),
+    path("books/<int:book_id>/progress", views.update_progress, name='update_progress'),
 ]
\ No newline at end of file
diff --git a/reader/views.py b/reader/views.py
index 6a1139b2b7b07db69439d8983899be71ae543656..0307ac852a729753247759d4d7cc3829396ebbb7 100644
--- a/reader/views.py
+++ b/reader/views.py
@@ -1,9 +1,12 @@
+import re
+
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import PermissionDenied
+from django.http import JsonResponse
 from django.shortcuts import render, redirect, get_object_or_404
 from django.views.decorators.http import require_POST
 
-from reader.forms import BookForm
+from reader.forms import BookForm, UserBookProgressForm
 from reader.models import Book, UserBook
 
 
@@ -139,15 +142,66 @@ def read_book(request, book_id):
     if not user_can_access_book(request, book):
         raise PermissionDenied
     userbook = get_object_or_404(UserBook, book=book, user=request.user)
+    pagenumber = None
+    cfi_ref = None
     # Check file type
     match book.file_type:
         case "pdf":
             reader_template = "reader/book_read/pdf_viewer.html"
+            try:
+                # Try to parse page number
+                pagenumber = int(userbook.last_progress_cfi)
+            except ValueError:
+                pass
+            except TypeError:
+                pass
         case "epub":
             reader_template = "reader/book_read/epub_viewer.html"
+            # Try to parse CFI
+            rgx = re.compile(r"epubcfi\((.*)\)")
+            m = rgx.match(userbook.last_progress_cfi)
+            if m:
+                cfi_ref = userbook.last_progress_cfi
         case _:
             reader_template = "reader/book_read/invalid_filetype.html"
     return render(request, reader_template, {
         "book": book,
-        "userbook": userbook
-    })
\ No newline at end of file
+        "userbook": userbook,
+        "pagenumber": pagenumber,
+        "cfi_ref": cfi_ref
+    })
+
+
+@require_POST
+@login_required(login_url='login')
+def update_progress(request, book_id):
+    book = get_object_or_404(Book, id=book_id)
+    if not user_can_access_book(request, book):
+        raise PermissionDenied
+    userbook = get_object_or_404(UserBook, book=book, user=request.user)
+    form = UserBookProgressForm(data=request.POST, instance=userbook)
+    if form.is_valid():
+        valid_for_filetype = False
+        if book.file_type == "pdf":
+            # Check page number is valid
+            try:
+                int(form.cleaned_data["last_progress_cfi"])
+                valid_for_filetype = True # If no exception, it's valid
+            except ValueError:
+                pass
+            except TypeError:
+                pass
+        elif book.file_type == "epub":
+            # Check CFI is valid
+            rgx = re.compile(r"epubcfi\((.*)\)")
+            m = rgx.match(form.cleaned_data["last_progress_cfi"])
+            if m:
+                valid_for_filetype = True
+
+        if valid_for_filetype:
+            form.save()
+            return JsonResponse({"success": True})
+        else:
+            return JsonResponse({"error": "Invalid progress for file type"}, status=400)
+    else:
+        return JsonResponse({"error": form.errors}, status=400)
\ No newline at end of file