diff --git a/src/videoag_common/objects/__init__.py b/src/videoag_common/objects/__init__.py
index 6c95ffb30610b55254dafd836676c5b8b5eb10cd..11b0571ebd36767a02a9ac69e1075920e13f068e 100644
--- a/src/videoag_common/objects/__init__.py
+++ b/src/videoag_common/objects/__init__.py
@@ -1,7 +1,15 @@
 from .course import Course, Lecture, Chapter
 from .site import Announcement, AnnouncementType, AnnouncementPageVisibility, Featured
 from .user import User
-from .medium import PublishMedium, TargetMedium, TargetMediumType, PlainVideoTargetMedium, PlainAudioTargetMedium, SourceMedium
+from .medium import (
+    PublishMedium,
+    TargetMedium,
+    TargetMediumType,
+    PlainVideoTargetMedium,
+    PlainAudioTargetMedium,
+    ThumbnailTargetMedium,
+    SourceMedium
+)
 from .view_permissions import ViewPermissions, ViewPermissionsType, EffectiveViewPermissions
 from .changelog import (
     ChangelogEntry,
diff --git a/src/videoag_common/objects/course.py b/src/videoag_common/objects/course.py
index 9a32b14281e82249abaeb3938edf06239da5fccc..d31b9945ecf934e88134f006c27cf21fd154b650 100644
--- a/src/videoag_common/objects/course.py
+++ b/src/videoag_common/objects/course.py
@@ -254,15 +254,6 @@ class Lecture(DeletableApiObject, VisibilityApiObject, ApiViewPermissionsObject,
     def authentication_methods(self):
         return self.effective_view_permissions.get_authentication_methods()
     
-    @api_include_in_data(
-        type_id="string",
-        data_notes="URL to the lecture's thumbnail file (Maybe with a redirect)"
-    )
-    def thumbnail_url(self) -> str:
-        # This is not too nice and only works from the API, but we need to access the API config
-        import api
-        return f"{api.config['API_BASE_URL']}/resources/lecture_thumbnail/{self.id}"
-    
     @property
     def effective_media_process_obj(self) -> MediaProcess:
         if self.media_process is not None:
diff --git a/src/videoag_common/objects/medium.py b/src/videoag_common/objects/medium.py
index 75e8b4a7b6e9c87737f0cfbdfbf415e3ce386857..0ab4ba12e1234a5c57d363015a884efc5dc4e97a 100644
--- a/src/videoag_common/objects/medium.py
+++ b/src/videoag_common/objects/medium.py
@@ -73,6 +73,7 @@ class SourceMedium(DeletableApiObject, Base):
 class TargetMediumType(Enum):
     PLAIN_VIDEO = "plain_video"
     PLAIN_AUDIO = "plain_audio"
+    THUMBNAIL = "thumbnail"
 
 
 _TARGET_MEDIUM_TYPE_ENUM = create_enum_type(TargetMediumType)
@@ -114,6 +115,11 @@ class TargetMedium(DeletableApiObject, Base):
         primaryjoin=lambda: Job.id == TargetMedium.producer_job_id,
         lazy="raise_on_sql"
     )
+    publish_medium: Mapped["PublishMedium"] = relationship(
+        primaryjoin=lambda: PublishMedium.target_medium_id == TargetMedium.id,
+        back_populates="target_medium",
+        lazy="raise_on_sql"
+    )
     
     @api_include_in_data(
         type_id="string",
@@ -169,6 +175,13 @@ class PlainAudioTargetMedium(TargetMedium):
     }
 
 
+class ThumbnailTargetMedium(TargetMedium):
+    __tablename__ = None  # Prevent our own base from adding a table name. This should be a single-table inheritance
+    __mapper_args__ = {
+        "polymorphic_identity": TargetMediumType.THUMBNAIL
+    }
+
+
 class PublishMedium(VisibilityApiObject, DeletableApiObject, Base):
     __api_data__ = ApiObjectClass(
         parent_relationship_config_ids=["lecture"]
@@ -193,12 +206,21 @@ class PublishMedium(VisibilityApiObject, DeletableApiObject, Base):
     target_medium: Mapped["TargetMedium"] = api_mapped(
         relationship(
             primaryjoin=lambda: TargetMedium.id == PublishMedium.target_medium_id,
+            back_populates="publish_medium",
             lazy="raise_on_sql"
         ),
         ApiMany2OneRelationshipField(
             include_in_data=True
         )
     )
+    __table_args__ = (
+        sql.Index(
+            "check_target_medium_unique",
+            target_medium_id,
+            unique=True,
+            postgresql_where="NOT deleted"
+        ),
+    )
 
 
 class MediaProcessTemplate(ApiObject, Base):