diff --git a/apps/api/urls.py b/apps/api/urls.py
index b74efc887ac5e11a7c57cad915df28e1c62bf963..e3eb2a8349c82876b345f392d1bf6c3df6269a1a 100644
--- a/apps/api/urls.py
+++ b/apps/api/urls.py
@@ -4,14 +4,15 @@
 from django.conf.urls import url, include
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
 from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework import routers, serializers
-from rest_framework.filters import SearchFilter
 from rest_framework.viewsets import ReadOnlyModelViewSet
 from activity.api.urls import register_activity_urls
 from api.viewsets import ReadProtectedModelViewSet
 from member.api.urls import register_members_urls
 from note.api.urls import register_note_urls
+from note.models import Alias
 from treasury.api.urls import register_treasury_urls
 from logs.api.urls import register_logs_urls
 from permission.api.urls import register_permission_urls
@@ -52,9 +53,47 @@ class UserViewSet(ReadProtectedModelViewSet):
     """
     queryset = User.objects.all()
     serializer_class = UserSerializer
-    filter_backends = [DjangoFilterBackend, SearchFilter]
+    filter_backends = [DjangoFilterBackend]
     filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
-    search_fields = ['$username', '$first_name', '$last_name', '$note__alias__name', '$note__alias__normalized_name', ]
+
+    def get_queryset(self):
+        queryset = super().get_queryset().order_by("username")
+
+        if "search" in self.request.GET:
+            pattern = self.request.GET["search"]
+
+            # We match first a user by its username, then if an alias is matched without normalization
+            # And finally if the normalized pattern matches a normalized alias.
+            queryset = queryset.filter(
+                username__iregex="^" + pattern
+            ).union(
+                queryset.filter(
+                    Q(note__alias__name__iregex="^" + pattern)
+                    & ~Q(username__iregex="^" + pattern)
+                ), all=True).union(
+                queryset.filter(
+                    Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
+                    & ~Q(note__alias__name__iregex="^" + pattern)
+                    & ~Q(username__iregex="^" + pattern)
+                ),
+                all=True).union(
+                queryset.filter(
+                    Q(note__alias__normalized_name__iregex="^" + pattern.lower())
+                    & ~Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
+                    & ~Q(note__alias__name__iregex="^" + pattern)
+                    & ~Q(username__iregex="^" + pattern)
+                ),
+                all=True).union(
+                queryset.filter(
+                    (Q(last_name__iregex="^" + pattern) | Q(first_name__iregex="^" + pattern))
+                    & ~Q(note__alias__normalized_name__iregex="^" + pattern.lower())
+                    & ~Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
+                    & ~Q(note__alias__name__iregex="^" + pattern)
+                    & ~Q(username__iregex="^" + pattern)
+                ),
+                all=True)
+
+        return queryset
 
 
 # This ViewSet is the only one that is accessible from all authenticated users!
diff --git a/apps/member/views.py b/apps/member/views.py
index 2a16168e97b554a60c459fc695b6751096a09db7..c97d15a395ac108e438f198fab0affdb7265c41f 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -180,9 +180,9 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
         """
         Filter the user list with the given pattern.
         """
-        qs = super().get_queryset().distinct("pk").annotate(alias=F("note__alias__name"))\
+        qs = super().get_queryset().distinct("username").annotate(alias=F("note__alias__name"))\
             .annotate(normalized_alias=F("note__alias__normalized_name"))\
-            .filter(profile__registration_valid=True)
+            .filter(profile__registration_valid=True).order_by("username")
         if "search" in self.request.GET:
             pattern = self.request.GET["search"]
 
@@ -190,13 +190,16 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
                 return qs.none()
 
             qs = qs.filter(
-                Q(first_name__iregex=pattern)
-                | Q(last_name__iregex=pattern)
-                | Q(profile__section__iregex=pattern)
-                | Q(username__iregex=pattern)
-                | Q(alias__iregex=pattern)
-                | Q(normalized_alias__iregex=Alias.normalize(pattern))
-            )
+                username__iregex="^" + pattern
+            ).union(
+                qs.filter(
+                    (Q(alias__iregex="^" + pattern)
+                     | Q(normalized_alias__iregex="^" + Alias.normalize(pattern))
+                     | Q(last_name__iregex="^" + pattern)
+                     | Q(first_name__iregex="^" + pattern)
+                     | Q(email__istartswith=pattern))
+                    & ~Q(username__iregex="^" + pattern)
+                ), all=True)
         else:
             qs = qs.none()
 
diff --git a/apps/note/api/views.py b/apps/note/api/views.py
index 0821ab488962fafed3ba0d831cfd32c809a44984..20818e1bf6bc7e871f9b46abf656fc4909614ebb 100644
--- a/apps/note/api/views.py
+++ b/apps/note/api/views.py
@@ -40,9 +40,19 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
 
         alias = self.request.query_params.get("alias", ".*")
         queryset = queryset.filter(
-            Q(alias__name__regex="^" + alias)
-            | Q(alias__normalized_name__regex="^" + Alias.normalize(alias))
-            | Q(alias__normalized_name__regex="^" + alias.lower()))
+            name__iregex="^" + alias
+        ).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + alias.lower())
+                & ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True)
 
         return queryset.distinct()
 
@@ -85,9 +95,19 @@ class AliasViewSet(ReadProtectedModelViewSet):
 
         alias = self.request.query_params.get("alias", ".*")
         queryset = queryset.filter(
-            Q(name__regex="^" + alias)
-            | Q(normalized_name__regex="^" + Alias.normalize(alias))
-            | Q(normalized_name__regex="^" + alias.lower()))
+            name__iregex="^" + alias
+        ).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + alias.lower())
+                & ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True)
 
         return queryset
 
@@ -108,13 +128,25 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
         queryset = super().get_queryset()
 
         alias = self.request.query_params.get("alias", ".*")
+        queryset = queryset.order_by('name').prefetch_related('note')
+        # We match first an alias if it is matched without normalization,
+        # then if the normalized pattern matches a normalized alias.
         queryset = queryset.filter(
-            Q(name__regex="^" + alias)
-            | Q(normalized_name__regex="^" + Alias.normalize(alias))
-            | Q(normalized_name__regex="^" + alias.lower()))\
-            .order_by('name').prefetch_related('note')
+            name__iregex="^" + alias
+        ).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True).union(
+            queryset.filter(
+                Q(normalized_name__iregex="^" + alias.lower())
+                & ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
+                & ~Q(name__iregex="^" + alias)
+            ),
+            all=True)
 
-        return queryset
+        return queryset.distinct()
 
 
 class TemplateCategoryViewSet(ReadProtectedModelViewSet):
diff --git a/note_kfet/static/js/base.js b/note_kfet/static/js/base.js
index c12e3f0a50e796002892756fc3fca5ef9cd0d92d..97ef7005db7c68e54b68cb8ac7a0343ff9cac6c8 100644
--- a/note_kfet/static/js/base.js
+++ b/note_kfet/static/js/base.js
@@ -79,7 +79,7 @@ function refreshBalance () {
  * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
  */
 function getMatchedNotes (pattern, fun) {
-    $.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club&ordering=normalized_name", fun);
+    $.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club", fun);
 }
 
 /**
@@ -261,9 +261,7 @@ function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_p
             return;
         }
 
-        $.getJSON("/api/note/consumer/?format=json&alias="
-            + pattern
-            + "&search=user|club&ordering=normalized_name",
+        $.getJSON("/api/note/consumer/?format=json&alias=" + pattern + "&search=user|club",
             function (consumers) {
                 // The response arrived too late, we stop the request
                 if (pattern !== $("#" + field_id).val())