diff --git a/src/core/migrations/0165_alter_eventparticipant_options_and_more.py b/src/core/migrations/0165_alter_eventparticipant_options_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..39e98e039cd7c719039c01f09bbab21689605bfd
--- /dev/null
+++ b/src/core/migrations/0165_alter_eventparticipant_options_and_more.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.1.3 on 2024-12-15 17:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("core", "0164_alter_translation_keys"),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name="eventparticipant",
+            options={"ordering": ["order"]},
+        ),
+        migrations.AddField(
+            model_name="eventparticipant",
+            name="order",
+            field=models.PositiveIntegerField(default=0),
+        ),
+    ]
diff --git a/src/core/models/events.py b/src/core/models/events.py
index d2dfb2f09fddac209126ded5a44ca1b94f9e15f8..c05859c5818348c64210734f4037a2a62c5651b5 100644
--- a/src/core/models/events.py
+++ b/src/core/models/events.py
@@ -425,10 +425,10 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model):
         if 'speakers' in data:
             if speaker_lookup is not None:
                 obj.save()  # TODO: check if we can do this better, but we need the model saved for adding speakers
-                for item in data['speakers']:
+                for order, item in enumerate(data['speakers']):
                     speaker = speaker_lookup(item)
                     if speaker is not None:
-                        obj.ensure_speaker(speaker)
+                        obj.ensure_speaker(speaker, order)
             else:
                 raise RuntimeWarning('Event.from_dict() got data with "speakers" but no speaker_lookup was provided.')
             if pop_used_keys:
@@ -632,7 +632,7 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model):
         # we require a configured BigBlueButton integration
         return settings.INTEGRATIONS_BBB
 
-    def ensure_speaker(self, user: PlatformUser):
+    def ensure_speaker(self, user: PlatformUser, order: int):
         entry, created = self.participants.get_or_create(participant=user)
         if created:
             logger.info(
@@ -646,6 +646,7 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model):
         entry.role = EventParticipant.Role.SPEAKER
         entry.is_public = True
         entry.is_accepted = True
+        entry.order = order
         entry.save()
         logger.info(
             'Forced participant #%s (%s) of event %s to accepted+public+speaker.',
@@ -695,6 +696,9 @@ class EventAttachment(models.Model):
 
 
 class EventParticipant(models.Model):
+    class Meta:
+        ordering = ['order']
+
     class Role(models.TextChoices):
         SPEAKER = 'speaker', _('EventParticipant__type-speaker')
         ANGEL = 'angel', _('EventParticipant__type-angel')
@@ -710,6 +714,9 @@ class EventParticipant(models.Model):
         max_length=20, choices=Role.choices, default=Role.REGULAR, help_text=_('EventParticipant__role__help'), verbose_name=_('EventParticipant__role')
     )
 
+    # Future TODO: evaluate https://pypi.org/project/django-sortedm2m/ but for now we just use a simple order field
+    order = models.PositiveIntegerField(default=0)
+
     is_accepted = models.BooleanField(default=False, help_text=_('EventParticipant__is_accepted__help'), verbose_name=_('EventParticipant__is_accepted'))
     is_public = models.BooleanField(default=False, help_text=_('EventParticipant__is_public__help'), verbose_name=_('EventParticipant__is_public'))
 
@@ -727,6 +734,10 @@ class EventParticipant(models.Model):
         except ConferenceMember.DoesNotExist:
             raise ValidationError(_('EventParticipant__must_be_conference_member'))
 
+        if not self.order:  # if 'order' is not explicitly set
+            last_order = EventParticipant.objects.filter(event=self.event).aggregate(models.Max('order')).get('order__max')
+            self.order = (last_order or 0) + 1
+
     def __str__(self):
         result = self.participant.username
         if self.role in [self.Role.SPEAKER, self.Role.ANGEL]: