diff --git a/src/api/serializers.py b/src/api/serializers.py
index bbad60a25ca8a057c2da409cb7360f97f328f9af..e8ff4d0190d7198b13583b122b5844f822897954 100644
--- a/src/api/serializers.py
+++ b/src/api/serializers.py
@@ -228,7 +228,7 @@ class BadgeSerializer(HubModelSerializer):
     )
 
     def create(self, validated_data):
-        issuing_assembly = Assembly.objects.filter(slug=self.context['assembly'])
+        issuing_assembly = Assembly.objects.filter(slug__iexact=self.context['assembly'])
         conference = Conference.objects.filter(slug=self.context['conference'])
         validated_data.update({'conference': conference, 'issuing_assembly': issuing_assembly})
         return super().create(validated_data)
diff --git a/src/api/views/badges.py b/src/api/views/badges.py
index 5d6a7d49183cb8797778c28900083e1274011fe3..a9d9789ac1f67e03a80319b836dcc9016061c83a 100644
--- a/src/api/views/badges.py
+++ b/src/api/views/badges.py
@@ -26,7 +26,7 @@ class BadgeListCreate(ListCreateAPIView):
     def get_queryset(self):
         queryset = self.model.objects.all()
         assembly = self.kwargs.get('assembly')
-        return queryset.filter(issuing_assembly__slug=assembly)
+        return queryset.filter(issuing_assembly__slug__iexact=assembly)
 
     def get_serializer_context(self):
         context = super().get_serializer_context()
@@ -43,7 +43,7 @@ class BadgeTokenListCreate(ConferenceSlugAssemblyMixin, ListCreateAPIView):
         queryset = BadgeToken.objects.all()
         assembly = self.kwargs.get('assembly')
         badge = self.kwargs.get('pk')
-        return queryset.filter(badge=badge, badge__issuing_assembly__slug=assembly)
+        return queryset.filter(badge=badge, badge__issuing_assembly__slug__iexact=assembly)
 
     def get_serializer_context(self):
         if self.kwargs.get('pk'):
diff --git a/src/api/views/mixins.py b/src/api/views/mixins.py
index 46c9302450d1ce62942b9217e02672d560521884..8ba7e8deee90aa90a71ef6cd99a63ae642101452 100644
--- a/src/api/views/mixins.py
+++ b/src/api/views/mixins.py
@@ -54,7 +54,7 @@ class ConferenceSlugAssemblyMixin(ConferenceSlugMixin):
         if self._assembly is None:
             assembly_slug = self.request.resolver_match.kwargs['assembly']
             try:
-                self._assembly = Assembly.objects.associated_with_user(self.conference, user=self.request.user).get(slug=assembly_slug)
+                self._assembly = Assembly.objects.associated_with_user(self.conference, user=self.request.user).get(slug__iexact=assembly_slug)
             except Assembly.DoesNotExist:
                 if issuing_token := self.kwargs.get('issuing_token', None):
                     try:
diff --git a/src/backoffice/forms/__init__.py b/src/backoffice/forms/__init__.py
index 9753d9d085e692bc68d58e09628f97a659d1d52c..23cf9d09ce338f9e7590698dd4bc2d808366bdae 100644
--- a/src/backoffice/forms/__init__.py
+++ b/src/backoffice/forms/__init__.py
@@ -86,13 +86,13 @@ class ScheduleSourceCreateForm(forms.ModelForm):
         assembly_slug = self.cleaned_data['assembly_slug']
         if assembly_slug == '*':
             return None
-        if not Assembly.objects.filter(conference=self._conference, slug=assembly_slug).exists():
+        if not Assembly.objects.filter(conference=self._conference, slug__iexact=assembly_slug).exists():
             raise ValidationError('An assembly with this slug does not exist.')
         return assembly_slug
 
     def clean(self):
         assembly_slug = self.cleaned_data.get('assembly_slug')
-        self.instance.assembly = Assembly.objects.get(conference=self._conference, slug=assembly_slug) if assembly_slug else None
+        self.instance.assembly = Assembly.objects.get(conference=self._conference, slug__iexact=assembly_slug) if assembly_slug else None
         self.instance.conference = self._conference
         try:
             self.instance.import_frequency = str2timedelta(self.cleaned_data.get('import_frequency'))
diff --git a/src/backoffice/forms/assemblies.py b/src/backoffice/forms/assemblies.py
index cb3d92f4d1f84daee102ded38c97a174cef9806a..3cc02fda6f427db7820aae32ef93ba23aa6140ae 100644
--- a/src/backoffice/forms/assemblies.py
+++ b/src/backoffice/forms/assemblies.py
@@ -36,7 +36,7 @@ class AssemblyCreateForm(forms.ModelForm):
         if self._conference.has_disclaimer('assembly') and not self.cleaned_data['disclaimer']:
             self.add_error('disclaimer', _('Assembly__disclaimer-needed'))
 
-        if (slug := self.cleaned_data.get('slug')) and Assembly.objects.filter(conference=self._conference, slug=slug).exists():
+        if (slug := self.cleaned_data.get('slug')) and Assembly.objects.filter(conference=self._conference, slug__iexact=slug).exists():
             self.add_error('slug', _('Assembly__slug__already_exists'))
 
         # ensure Assembly's conference is set before saving
@@ -106,7 +106,7 @@ class AssemblyEditForm(TranslatedFieldsForm):
 
         # slug must not already exist in the conference
         slug = self.cleaned_data.get('slug')
-        if slug is not None and Assembly.objects.filter(conference=self.instance.conference, slug=slug).exclude(pk=self.instance.pk).exists():
+        if slug is not None and Assembly.objects.filter(conference=self.instance.conference, slug__iexact=slug).exclude(pk=self.instance.pk).exists():
             self.add_error('slug', _('Assembly__slug__already_exists'))
 
 
diff --git a/src/backoffice/views/moderation/mixins.py b/src/backoffice/views/moderation/mixins.py
index d912468bfd569cf430bf15ab8ecb978b7c6c1b48..b4f75e7ebdf8ab577d78a165483baa49473a38bb 100644
--- a/src/backoffice/views/moderation/mixins.py
+++ b/src/backoffice/views/moderation/mixins.py
@@ -39,7 +39,7 @@ def lookup_moderation_items(conference: Conference, query: str):
                 candidates.append(('assembly', p.kwargs['pk']))
             for kw in ['slug', 'assembly_slug']:
                 if kw in p.kwargs:
-                    for a in conference.assemblies.filter(slug=p.kwargs[kw]).values('pk'):
+                    for a in conference.assemblies.filter(slug__iexact=p.kwargs[kw]).values('pk'):
                         candidates.append(('assembly', a['pk']))
         if '/event' in p.route:
             if 'pk' in p.kwargs:
diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index de11241f08bc95add6e9475847bb76ff6d9c95cf..ff3f6a27a6010a4cc95a0d6b0f8b348b37ce88c8 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -2556,6 +2556,9 @@ msgstr "Das Bild muss die Abmessungen von minimal %(min_width)spx/%(min_height)s
 msgid "Validation__error_image_square"
 msgstr "Das Bild muss quadratisch sein."
 
+msgid "Validation__error_lowercase"
+msgstr "Das Feld darf nur Kleinbuchstaben enthalten."
+
 msgid "Registration"
 msgstr "Registrieren"
 
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index bf783a463c514c21bdf23d792eb2c9488b5359fb..14cfc2fcf2063fdbcd5cddd68ff2d4495486f321 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -2538,6 +2538,9 @@ msgstr "The image must have the dimensions of minimal %(min_width)spx/%(min_heig
 msgid "Validation__error_image_square"
 msgstr "The image must be square"
 
+msgid "Validation__error_lowercase"
+msgstr "The field may only contain lowercase letters"
+
 msgid "Registration"
 msgstr "Sign Up"
 
diff --git a/src/core/migrations/0170_alter_assembly_slug.py b/src/core/migrations/0170_alter_assembly_slug.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea803500a158bf4f1776b57a56fcde1d72f23522
--- /dev/null
+++ b/src/core/migrations/0170_alter_assembly_slug.py
@@ -0,0 +1,32 @@
+# Generated by Django 5.1.3 on 2024-12-23 01:30
+
+import core.validators
+import django.core.validators
+import re
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0169_team_teammember"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="assembly",
+            name="slug",
+            field=models.SlugField(
+                help_text="Assembly__slug__help",
+                validators=[
+                    django.core.validators.RegexValidator(
+                        re.compile("^[-a-zA-Z0-9_]+\\Z"),
+                        "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
+                        "invalid",
+                    ),
+                    core.validators.validate_lower_case,
+                ],
+                verbose_name="Assembly__slug",
+            ),
+        ),
+    ]
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index c3497ff6e3e1404f2523368483c376ee960a82c7..2f2a97badeb4ffd271ae4bed5040523d7f76dbe5 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -10,6 +10,7 @@ from django.conf import settings
 from django.contrib.contenttypes.fields import GenericRelation
 from django.contrib.gis.db import models as gis_models
 from django.core.exceptions import ValidationError
+from django.core.validators import validate_slug
 from django.db import models
 from django.db.models import Q, QuerySet
 from django.template.loader import render_to_string
@@ -33,7 +34,7 @@ from core.models.tags import TaggedItemMixin, TagItem
 from core.models.users import PlatformUser
 from core.predicates import has_perms
 from core.utils import render_markdown_as_text
-from core.validators import FileSizeValidator, ImageDimensionValidator
+from core.validators import FileSizeValidator, ImageDimensionValidator, validate_lower_case
 
 
 @rules.predicate
@@ -110,7 +111,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
 
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
     conference = ConferenceReference(related_name='assemblies')
-    slug = models.SlugField(help_text=_('Assembly__slug__help'), verbose_name=_('Assembly__slug'))
+    slug = models.SlugField(help_text=_('Assembly__slug__help'), verbose_name=_('Assembly__slug'), validators=[validate_slug, validate_lower_case])
     name = models.CharField(max_length=200, help_text=_('Assembly__name__help'), verbose_name=_('Assembly__name'))
 
     is_official = models.BooleanField(default=False, help_text=_('Assembly__is_official__help'), verbose_name=_('Assembly__is_official'))
diff --git a/src/core/validators.py b/src/core/validators.py
index d288ef9c886111569de90f3bb1a3970dfe1b33e8..cf62f4ea1b10c12d639a48e0103ab4a91a34f166 100644
--- a/src/core/validators.py
+++ b/src/core/validators.py
@@ -130,3 +130,17 @@ class ImageDimensionValidator:
             other.max_size,
             other.square,
         )
+
+
+def validate_lower_case(value: str) -> None:
+    """
+    A validator function that will check if a string is lowercase
+
+    Raises:
+        ValidationError: Raises a validation error if the string is not lowercase.
+
+    Args:
+        value (str): The string to check
+    """
+    if value != value.lower():
+        raise ValidationError(_('Validation__error_lowercase'), code='lowercase')
diff --git a/src/plainui/urls.py b/src/plainui/urls.py
index f50098409045120b3dff51b5f80c40c6bfadaad7..c6ac7b5d65f7c09d5ed3c91d3cc86511ca4c4d07 100644
--- a/src/plainui/urls.py
+++ b/src/plainui/urls.py
@@ -89,7 +89,6 @@ urlpatterns = [
     re_path(r'^assemblies/(?P<page>[0a-z])', views.AssembliesView.as_view(), name='assemblies_paginated'),
     re_path(r'^assemblies/all/(?P<page>[0a-z])', views.AssembliesAllView.as_view(), name='assemblies_all_paginated'),
     path('assembly/<slug:assembly_slug>/', views.AssemblyView.as_view(), name='assembly'),
-    path('assembly/<slug:slug>/', views.AssemblyView.as_view(), name='assembly'),
     path('sos/', views.SosList.as_view(), name='sos'),
     path('so/project/<slug:slug>/', views.ProjectView.as_view(self_organized=True), name='so_project'),
     path('report', views.ReportContentView.as_view(), name='report_content'),
diff --git a/src/plainui/views/assemblies.py b/src/plainui/views/assemblies.py
index c6a27a38df96cf4c3590b1087aa8a51cbbe94f7e..0197ef7e857078dd848e960d9d00fe82f75cd9a3 100644
--- a/src/plainui/views/assemblies.py
+++ b/src/plainui/views/assemblies.py
@@ -36,11 +36,8 @@ logger = logging.getLogger(__name__)
 class AssemblyView(ConferenceRequiredMixin, DetailView):
     model = Assembly
     template_name = 'plainui/assembly.html.j2'
-
-    def dispatch(self, request, *args, assembly_slug: str | None = None, **kwargs):
-        if assembly_slug:
-            self.kwargs.update({'slug': self.kwargs.pop('assembly_slug')})
-        return super().dispatch(request, *args, **kwargs)
+    slug_url_kwarg = 'assembly_slug'
+    slug_field = 'slug__iexact'
 
     def get_queryset(self) -> QuerySet[Any]:
         return Assembly.objects.conference_accessible(self.conf).prefetch_related(