diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po
index 72e7f543670454915b60ba3f488cd973bac2e029..c675dec3ed8f6acdc3d1af3d9bd19008ff6294cf 100644
--- a/src/backoffice/locale/de/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/de/LC_MESSAGES/django.po
@@ -1481,11 +1481,11 @@ msgid "team__edit__metadata"
 msgstr "Team-Daten"
 
 # Use translation from core
-msgid "Team__require_staff"
+msgid "Team__visibility"
 msgstr ""
 
 # Use translation from core
-msgid "Team__require_staff__help"
+msgid "Team__visibility__help"
 msgstr ""
 
 msgid "Team__change_permission_admin"
@@ -1507,9 +1507,8 @@ msgstr "Das Team \"%(team)s\" wirklich löschen?"
 msgid "Team__delete__submit"
 msgstr "Team löschen"
 
-# Use translation from core
 msgid "Team__name"
-msgstr ""
+msgstr "Name"
 
 # Use translation from core
 msgid "Team__description"
diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po
index 2f93691957216fd26aff9990719a2b3945ecafe4..70d0d1cef0a8bd616b715d0657b803630cc3f1a6 100644
--- a/src/backoffice/locale/en/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/en/LC_MESSAGES/django.po
@@ -1486,11 +1486,11 @@ msgid "team__edit__metadata"
 msgstr "team data"
 
 # Use translation from core
-msgid "Team__require_staff"
+msgid "Team__visibility"
 msgstr ""
 
 # Use translation from core
-msgid "Team__require_staff__help"
+msgid "Team__visibility__help"
 msgstr ""
 
 msgid "Team__change_permission_admin"
@@ -1512,13 +1512,12 @@ msgstr "Are you sure you want to delete the team \"%(team)s\"?"
 msgid "Team__delete__submit"
 msgstr "Delete team"
 
-# Use translation from core
 msgid "Team__name"
-msgstr ""
+msgstr "name"
 
 # Use translation from core
 msgid "Team__description"
-msgstr "description"
+msgstr ""
 
 msgid "Team__no_description"
 msgstr "This team has no description at the moment."
diff --git a/src/backoffice/templates/backoffice/teams/create_edit.html b/src/backoffice/templates/backoffice/teams/create_edit.html
index 7d3696ca2bc1f28814a2878e630ebbf339270d60..b585a1327528f29db2d89657017c3c85604ebf7a 100644
--- a/src/backoffice/templates/backoffice/teams/create_edit.html
+++ b/src/backoffice/templates/backoffice/teams/create_edit.html
@@ -28,13 +28,13 @@
         <p class="fw-bold border-bottom mb-3">{% trans "team__edit__metadata" %}</p>
         <div class="row mb-3">
           <div class="col-md-9">{% bootstrap_field form.name %}</div>
-          {% if form.require_staff %}
-            <div class="col-md-3">{% bootstrap_field form.require_staff %}</div>
+          {% if form.visibility %}
+            <div class="col-md-3">{% bootstrap_field form.visibility %}</div>
           {% else %}
             <div class="col-md-3">
-              <label for="require_staff">{% trans "Team__require_staff" %}:</label>
-              <span id="require_staff">{{ form.require_staff|yesno }}</span>
-              <div class="form-text">{% trans "Team__require_staff__help" %}</div>
+              <label for="visibility">{% trans "Team__visibility" %}:</label>
+              <span id="visibility">{{ form.instance.get_visibility_display }}</span>
+              <div class="form-text">{% trans "Team__visibility__help" %}</div>
             </div>
           {% endif %}
         </div>
diff --git a/src/backoffice/templates/backoffice/teams/detail.html b/src/backoffice/templates/backoffice/teams/detail.html
index 5309f643f502047ac62a2692c4edcd4b802fa845..21e84133f4b6192c404a0bcb2a9b564c4518900a 100644
--- a/src/backoffice/templates/backoffice/teams/detail.html
+++ b/src/backoffice/templates/backoffice/teams/detail.html
@@ -8,29 +8,32 @@
   {% trans "Details" %} | {% trans "Team" %} | {{ team.name }}
 {% endblock title %}
 {% block scripts %}
+  {% has_perm 'core.delete_team' request.user team as can_delete %}
   <script src="{% static "backoffice/form-add.js" %}"></script>
   <script src="{% static "backoffice/modal.js" %}"></script>
-  <script nonce="{{ request.csp_nonce }}">
-  $(document).ready(() => {
-    showModal = registerModal()
-    deleteSubmit = document.getElementById('TeamDeleteSubmit')
-    if(deleteSubmit) {
-      deleteSubmit.addEventListener('click', (e) => {
-        e.preventDefault();
-        showModal(
-          () => {
-            form = document.getElementById('TeamDeleteForm')
-            form.action = form.action + '?confirmation=true'
-            form.submit()
-          },
-          'danger',
-          '{% trans "Team__delete__warning__header" %}',
-          '{% blocktrans %}Team__delete__warning__text {{ team }}{% endblocktrans %}',
-          '{% trans "Team__delete__submit" %}')
-      })
-    }
-  });
-  </script>
+  {% if can_delete %}
+    <script nonce="{{ request.csp_nonce }}">
+    $(document).ready(() => {
+      showModal = registerModal()
+      deleteSubmit = document.getElementById('TeamDeleteSubmit')
+      if(deleteSubmit) {
+        deleteSubmit.addEventListener('click', (e) => {
+          e.preventDefault();
+          showModal(
+            () => {
+              form = document.getElementById('TeamDeleteForm')
+              form.action = form.action + '?confirmation=true'
+              form.submit()
+            },
+            'danger',
+            '{% trans "Team__delete__warning__header" %}',
+            '{% blocktrans %}Team__delete__warning__text {{ team }}{% endblocktrans %}',
+            '{% trans "Team__delete__submit" %}')
+          })
+        }
+      });
+    </script>
+  {% endif %}
 {% endblock scripts %}
 {% block content %}
   {% has_perm 'core.change_team' request.user team as can_change %}
@@ -42,8 +45,8 @@
       <div class="row">
         <label for="team-name" class="form-label col-sm-2">{% trans "Team__name" %}</label>
         <span class="col-sm-4 " id="team-name">{{ team.name }}</span>
-        <label for="team-staff" class="form-label col-sm-2">{% trans "Team__require_staff" %}</label>
-        <span class="col-sm-4 " id="team-staff">{{ team.require_staff|yesno }}</span>
+        <label for="team-staff" class="form-label col-sm-2">{% trans "Team__visibility" %}</label>
+        <span class="col-sm-4 " id="team-staff">{{ team.get_visibility_display }}</span>
       </div>
       <div class="row">
         <label for="team-description" class="form-label">{% trans "Team__description" %}</label>
@@ -117,8 +120,8 @@
       </table>
     </div>
     <div class="card-footer text-end">
-      {% if member_id %}
-        <a href="{% url "backoffice:team-member-delete" team=team.uuid pk=member_id %}"
+      {% if team.member_id %}
+        <a href="{% url "backoffice:team-member-delete" team=team.uuid pk=team.member_id %}"
            class="btn btn-danger btn-sm">{% trans "TeamMember__leave" %}</a>
       {% else %}
         <a href="{% url "backoffice:invitation-send" type="member_to_team" requester_id=user.uuid requested_id=team.uuid %}"
diff --git a/src/backoffice/templates/backoffice/teams/list.html b/src/backoffice/templates/backoffice/teams/list.html
index 56c9086dbd4f0f3ad829b60d8bd6c80c0e6e8350..d904f6c70d077e24d9ede674122109043c30231f 100644
--- a/src/backoffice/templates/backoffice/teams/list.html
+++ b/src/backoffice/templates/backoffice/teams/list.html
@@ -33,8 +33,8 @@
         <thead>
           <tr>
             <th>{% trans "Team__name" %}</th>
-            <th>{% trans "Team__require_staff" %}</th>
-            {% if user.is_staff %}
+            <th>{% trans "Team__visibility" %}</th>
+            {% if conferencemember.is_staff %}
               <th>{% trans "Team__member_count" %}</th>
             {% endif %}
             <th>{% trans "TeamMember__can_manage" %}</th>
@@ -46,11 +46,11 @@
               <td>
                 <a href="{% url 'backoffice:team' uuid=team.uuid %}">{{ team.name }}</a>
               </td>
-              <td>{{ team.require_staff|yesno }}</td>
+              <td>{{ team.get_visibility_display }}</td>
               {% if user.is_staff %}<td>{{ team.members_count }}</td>{% endif %}
               <td>
-                {% if user.id in team.member_list %}
-                  {{ team.can_manage|yesno }}
+                {% if team.is_member %}
+                  {{ team.is_manager|yesno }}
                 {% else %}
                   {% trans "Team__list__not_a_member" %}
                 {% endif %}
diff --git a/src/backoffice/tests/teams/members.py b/src/backoffice/tests/teams/members.py
index ea2d3313b0c62c5d3b84794ccf5c3df331732944..56d9744885a9ffe75079da3e8b0ca5b873c0c769 100644
--- a/src/backoffice/tests/teams/members.py
+++ b/src/backoffice/tests/teams/members.py
@@ -17,6 +17,7 @@ class TeamMemberDeleteViewTestCase(BackOfficeTestCase):
         self.team = Team.objects.create(
             name='team',
             conference=self.conf,
+            visibility=Team.TeamVisibility.PUBLIC,
         )
         self.team_member_staff = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
         self.team_member_staff_2 = TeamMember.objects.create(team=self.team, user=self.staff_2, can_manage=True)
diff --git a/src/backoffice/tests/teams/teams.py b/src/backoffice/tests/teams/teams.py
index 576965c9df517a461a7ff6ac2794f42ba883a644..1ea090c54b86a3dd025adcc8ccb69d985bad16dd 100644
--- a/src/backoffice/tests/teams/teams.py
+++ b/src/backoffice/tests/teams/teams.py
@@ -1,265 +1,305 @@
-from unittest.mock import patch
-
 from django.urls import reverse
 from django.utils.translation import activate
 from django.utils.translation import gettext as _
 
+from core.forms.teams import TeamForm
 from core.models import (
-    ConferenceMember,
     PlatformUser,
     Team,
     TeamMember,
 )
-from core.tests.mock import mocktrans
 
 from backoffice.tests.base import BackOfficeTestCase
 
 
 class TeamListViewTestCase(BackOfficeTestCase):
+    users = ['admin', 'staff', 'user', 'team_manager', 'team_user']
+    detail_matrix = {
+        Team.TeamVisibility.PUBLIC: ['admin', 'staff', 'user', 'team_manager', 'team_user'],
+        Team.TeamVisibility.STAFF: ['admin', 'staff', 'team_manager', 'team_user'],
+        Team.TeamVisibility.HIDDEN: ['admin', 'team_manager', 'team_user'],
+    }
+    create_button = ['admin']
+
     def setUp(self):
         super().setUp()
+        self.team_user = PlatformUser.objects.create_user(username='team_user')
+        self.team_manager = PlatformUser.objects.create_user(username='team_manager')
         self.teams = {}
-        for team in ['team1', 'team2', 'team3']:
-            self.teams[team] = Team.objects.create(
-                name=team,
-                conference=self.conf,
-            )
-        self.teams['team1'].require_staff = True
-        TeamMember.objects.create(team=self.teams['team1'], user=self.staff)
-        TeamMember.objects.create(team=self.teams['team1'], user=self.admin, can_manage=True)
-        TeamMember.objects.create(team=self.teams['team2'], user=self.staff, can_manage=True)
-        TeamMember.objects.create(team=self.teams['team3'], user=self.admin)
+        self.team = Team.objects.create(
+            name='team',
+            conference=self.conf,
+        )
+        TeamMember.objects.create(team=self.team, user=self.team_manager, can_manage=True)
+        TeamMember.objects.create(team=self.team, user=self.team_user)
 
-    def test_team_list_unauthenticated(self):
+    def test_team_list_unauthenicated(self):
         activate('en')
-        response = self.client.get(reverse('backoffice:teams'))
-        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:teams'))
+        for visibility in Team.TeamVisibility:
+            with self.subTest(visibility=visibility):
+                self.team.visibility = visibility
+                self.team.save()
+                response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+                self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
 
     def test_team_list_admin(self):
-        self.client.force_login(self.admin)
-        with patch('backoffice.views.teams.teams._', mocktrans):
-            response = self.client.get(reverse('backoffice:teams'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/list.html')
-        self.assertQuerySetEqual(Team.objects.all().order_by('name'), response.context['teams'])
-        for team in self.teams:
-            self.assertContains(response, team)
-        self.assertContains(response, _('Team__create__button'))
-
-    def test_team_list_staff(self):
-        self.client.force_login(self.staff)
-        response = self.client.get(reverse('backoffice:teams'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/list.html')
-        self.assertQuerySetEqual(Team.objects.all().order_by('name'), response.context['teams'])
-        for team in self.teams:
-            self.assertContains(response, team)
-        self.assertNotContains(response, _('Team__create__button'))
-
-    def test_team_list_user(self):
-        self.client.force_login(self.user)
-        with patch('backoffice.views.teams.teams._', mocktrans):
-            response = self.client.get(reverse('backoffice:teams'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/list.html')
-        self.assertQuerySetEqual(Team.objects.filter(require_staff=False).order_by('name'), response.context['teams'])
-        for team in self.teams:
-            self.assertContains(response, team)
-        self.assertNotContains(response, _('Team__create__button'))
+        activate('en')
+        for user in self.users:
+            for visibility, users in self.detail_matrix.items():
+                with self.subTest(user=user, visibility=visibility):
+                    self.team.visibility = visibility
+                    self.team.save()
+                    self.client.force_login(getattr(self, user))
+                    response = self.client.get(reverse('backoffice:teams'))
+                    self.assertEqual(response.status_code, 200)
+                    self.assertTemplateUsed(response, 'backoffice/teams/list.html')
+                    if user in self.create_button:
+                        self.assertContains(response, _('Team__create__button'))
+                    else:
+                        self.assertNotContains(response, _('Team__create__button'))
+                    if user in users:
+                        self.assertIn(self.team, response.context['teams'])
+                    else:
+                        self.assertNotIn(self.team, response.context['teams'])
 
 
 class TeamDetailViewTestCase(BackOfficeTestCase):
+    detail_matrix = {
+        Team.TeamVisibility.PUBLIC: {
+            'admin': ['edit', 'delete', 'join'],
+            'staff': ['join'],
+            'team_manager': ['edit', 'leave'],
+            'user': ['leave'],
+            'user_2': ['join'],
+        },
+        Team.TeamVisibility.STAFF: {
+            'admin': ['edit', 'delete', 'join'],
+            'staff': ['join'],
+            'team_manager': ['edit', 'leave'],
+            'user': ['leave'],
+            'user_2': False,
+        },
+        Team.TeamVisibility.HIDDEN: {
+            'admin': ['edit', 'delete', 'join'],
+            'staff': False,
+            'team_manager': ['edit', 'leave'],
+            'user': ['leave'],
+            'user_2': False,
+        },
+    }
+    button_texts = {
+        'edit': _('Team__edit__button'),
+        'delete': _('Team__delete__button'),
+        'join': _('TeamMember__join'),
+        'leave': _('TeamMember__leave'),
+    }
+
     def setUp(self):
         super().setUp()
+        self.team_manager = PlatformUser.objects.create_user(
+            username='team_mamanger',
+        )
         self.user_2 = PlatformUser.objects.create_user(
             username='user2',
         )
-        self.user_2_cm = ConferenceMember.objects.create(conference=self.conf, user=self.user_2)
         self.team = Team.objects.create(
             name='team',
             conference=self.conf,
         )
-        self.team_member = TeamMember.objects.create(team=self.team, user=self.admin)
-        self.team_member2 = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
-        self.team_member3 = TeamMember.objects.create(team=self.team, user=self.user)
+        self.team_member_manager = TeamMember.objects.create(team=self.team, user=self.team_manager, can_manage=True)
+        self.team_member_user = TeamMember.objects.create(team=self.team, user=self.user)
 
     def test_team_detail_unauthenicated(self):
         activate('en')
-        response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-
-    def test_team_detail_admin(self):
-        self.client.force_login(self.admin)
-        response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/detail.html')
-        self.assertEqual(self.team, response.context['team'])
-        self.assertContains(response, _('Team__edit__button'))
-        self.assertContains(response, _('Team__delete__button'))
-        self.assertNotContains(response, _('TeamMember__join'))
-
-    def test_team_detail_staff(self):
-        self.client.force_login(self.staff)
-        response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/detail.html')
-        self.assertEqual(self.team, response.context['team'])
-        self.assertContains(response, _('Team__edit__button'))
-        self.assertNotContains(response, _('Team__delete__button'))
-        self.assertNotContains(response, _('TeamMember__join'))
-
-    def test_team_detail_team_member(self):
-        self.client.force_login(self.user)
-        response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/detail.html')
-        self.assertEqual(self.team, response.context['team'])
-        self.assertNotContains(response, _('Team__edit__button'))
-        self.assertNotContains(response, _('Team__delete__button'))
-        self.assertNotContains(response, _('TeamMember__join'))
-
-    def test_team_detail_non_team_member(self):
-        self.client.force_login(self.user_2)
-        response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/detail.html')
-        self.assertEqual(self.team, response.context['team'])
-        self.assertNotContains(response, _('Team__edit__button'))
-        self.assertNotContains(response, _('Team__delete__button'))
-        self.assertContains(response, _('TeamMember__join'))
+        for visibility in Team.TeamVisibility:
+            with self.subTest(visibility=visibility):
+                self.team.visibility = visibility
+                self.team.save()
+                response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+                self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+
+    def test_team_detail(self):
+        activate('en')
+        for visibility, matrix in self.detail_matrix.items():
+            for user, buttons in matrix.items():
+                with self.subTest(visibility=visibility, user=user):
+                    self.team.visibility = visibility
+                    self.team.save()
+                    self.client.force_login(getattr(self, user))
+                    response = self.client.get(reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+                    if buttons is False:
+                        self.assertEqual(response.status_code, 403)
+                    else:
+                        self.assertEqual(response.status_code, 200)
+                        self.assertTemplateUsed(response, 'backoffice/teams/detail.html')
+                        self.assertEqual(self.team, response.context['team'])
+                        for button, text in self.button_texts.items():
+                            if button not in buttons:
+                                self.assertNotContains(response, text)
+                            else:
+                                self.assertContains(response, text)
 
 
 class TeamCreateViewTestCase(BackOfficeTestCase):
+    expected_results = {
+        'admin': True,
+        'staff': False,
+        'user': False,
+    }
+
     def test_team_create_unauthenicated(self):
         activate('en')
         response = self.client.get(reverse('backoffice:team-create'))
         self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-create'))
 
-    def test_team_create_admin(self):
-        self.client.force_login(self.admin)
-        response = self.client.get(reverse('backoffice:team-create'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
-        self.assertContains(response, _('create'))
-        self.assertContains(response, _('Team__create__title'))
-
-        response = self.client.post(reverse('backoffice:team-create'), data={'name': 'team'})
-        self.assertTrue(Team.objects.filter(name='team').exists())
-        team = Team.objects.get(name='team')
-        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': team.uuid}))
-
-    def test_team_create_staff(self):
-        self.client.force_login(self.staff)
-        response = self.client.get(reverse('backoffice:team-create'))
-        self.assertEqual(response.status_code, 403)
-
-    def test_team_create_team_member(self):
-        self.client.force_login(self.user)
-        response = self.client.get(reverse('backoffice:team-create'))
-        self.assertEqual(response.status_code, 403)
+    def test_team_create(self):
+        for user, result in self.expected_results.items():
+            with self.subTest(user=user):
+                self.client.force_login(getattr(self, user))
+                response = self.client.get(reverse('backoffice:team-create'))
+                if result is True:
+                    self.assertEqual(response.status_code, 200)
+                    self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
+                    self.assertContains(response, _('create'))
+                    self.assertContains(response, _('Team__create__title'))
+
+                    response = self.client.post(reverse('backoffice:team-create'), data={'name': 'team', 'visibility': Team.TeamVisibility.PUBLIC})
+                    team = Team.objects.filter(name='team', visibility=Team.TeamVisibility.PUBLIC).first()
+                    self.assertIsNotNone(team, 'Team not created')
+                    self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': team.uuid}))
+                else:
+                    self.assertEqual(response.status_code, 403)
 
 
 class TeamUpdateViewTestCase(BackOfficeTestCase):
+    fields = {
+        'name': 'test-name',
+        'description_en': 'test-en',
+        'description_de': 'test-de',
+        'visibility': Team.TeamVisibility.PUBLIC.value,
+    }
+    expected_results = {
+        'admin': ['name', 'description_en', 'description_de', 'visibility'],
+        'staff': False,
+        'team_manager': ['name', 'description_en', 'description_de'],
+        'user': False,
+    }
+
     def setUp(self):
         super().setUp()
-        self.user_2 = PlatformUser.objects.create_user(
-            username='user2',
-        )
-        self.user_2_cm = ConferenceMember.objects.create(conference=self.conf, user=self.user_2)
+        self.team_manager = PlatformUser.objects.create_user(username='team_mamanger')
+        self.user_2 = PlatformUser.objects.create_user(username='user2')
+
+    def repeatable_setUp(self):
+        for team in Team.objects.all():
+            team.delete()
         self.team = Team.objects.create(
             name='team',
             conference=self.conf,
         )
-        self.team_member = TeamMember.objects.create(team=self.team, user=self.admin)
-        self.team_member2 = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
-        self.team_member3 = TeamMember.objects.create(team=self.team, user=self.user)
+        self.team_member_manager = TeamMember.objects.create(team=self.team, user=self.team_manager, can_manage=True)
+        self.team_member_user = TeamMember.objects.create(team=self.team, user=self.user)
 
-    def test_team_update_unauthenicated(self):
-        activate('en')
-        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
-        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
-
-    def test_team_edit_admin(self):
-        self.client.force_login(self.admin)
-        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
-        self.assertNotContains(response, _('create'))
-        # TODO: Add test for title
-        self.assertContains(response, _('Team__edit__submit'))
-
-        response = self.client.post(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}), data={'name': 'team'})
-        self.assertTrue(Team.objects.filter(name='team').exists())
-        team = Team.objects.get(name='team')
-        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': team.uuid}))
+    def test_all_fields(self):
+        self.assertListEqual(list(self.fields.keys()), TeamForm.Meta.fields)
 
-    def test_team_edit_staff(self):
-        self.client.force_login(self.staff)
-        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
-        self.assertNotContains(response, _('create'))
-        # TODO: Add test for title
-        self.assertContains(response, _('Team__edit__submit'))
-
-        response = self.client.post(
-            reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}),
-            data={'name': 'team', 'description_de': 'description_de', 'description_en': 'description_en'},
-        )
-        self.assertTrue(Team.objects.filter(name='team').exists())
-        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
-        self.team.refresh_from_db()
-        self.assertEqual(self.team.description_de, 'description_de')
-        self.assertEqual(self.team.description_en, 'description_en')
-
-    def test_team_edit_team_member(self):
-        self.client.force_login(self.user)
-        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 403)
+    def test_team_edit(self):
+        activate('en')
+        for visibility in Team.TeamVisibility:
+            for user, user_fields in self.expected_results.items():
+                with self.subTest(visibility=visibility, user=user):
+                    self.repeatable_setUp()
+                    self.team.visibility = visibility
+                    self.team.save()
+                    self.client.force_login(getattr(self, user))
+                    response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+                    if user_fields:
+                        self.assertEqual(response.status_code, 200)
+                        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
+                        self.assertNotContains(response, _('create'))
+                        self.assertContains(response, _('Team__edit__submit'))
+                        for field in self.fields:
+                            if field in user_fields:
+                                self.assertIn(field, response.context['form'].fields)
+                            else:
+                                self.assertNotIn(field, response.context['form'].fields)
+                    else:
+                        self.assertEqual(response.status_code, 403)
+                    response = self.client.post(
+                        reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}),
+                        data={field: self.fields[field] for field in user_fields} if user_fields else {'name': self.fields['name']},
+                    )
+                    if user_fields:
+                        self.team.refresh_from_db()
+                        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+                        for field in [x for x in self.fields if x in user_fields]:
+                            self.assertEqual(getattr(self.team, field), self.fields[field])
+                    else:
+                        self.assertEqual(response.status_code, 403)
 
 
 class TeamDeleteViewTestCase(BackOfficeTestCase):
+    expected_results = {
+        'admin': True,
+        'staff': False,
+        'team_manager': False,
+        'user': False,
+    }
+
     def setUp(self):
         super().setUp()
+        self.team_manager = PlatformUser.objects.create_user(username='team_mamanger')
+
+    def repeatable_setUp(self, visibility=Team.TeamVisibility.STAFF):
+        if team := Team.objects.filter(name='team').first():
+            team.delete()
         self.team = Team.objects.create(
             name='team',
             conference=self.conf,
+            visibility=visibility,
         )
-        self.team_member = TeamMember.objects.create(team=self.team, user=self.admin)
-        self.team_member2 = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
-        self.team_member3 = TeamMember.objects.create(team=self.team, user=self.user)
+        self.team_member_manager = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
+        self.team_member_user = TeamMember.objects.create(team=self.team, user=self.user)
 
     def test_team_delete_unauthenicated(self):
-        activate('en')
-        response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
-        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
+        self.repeatable_setUp()
+        for visibility in Team.TeamVisibility:
+            with self.subTest(visibility=visibility):
+                self.team.visibility = visibility
+                self.team.save()
+                response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
+                self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
 
     def test_team_delete_admin(self):
-        self.client.force_login(self.admin)
-        response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/components/confirmation_modal.html')
-        self.assertContains(response, _('Team__delete__warning__header'))
-        self.assertContains(response, _('Team__delete__submit'))
-
-        response = self.client.post(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
-        # No confirmation, should not delete
-        self.assertTrue(Team.objects.filter(name='team').exists())
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'backoffice/components/confirmation_modal.html')
-        self.assertContains(response, _('Team__delete__warning__header'))
-        self.assertContains(response, _('Team__delete__submit'))
-
-        response = self.client.post(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}), data={'confirmation': 'true'})
-        self.assertFalse(Team.objects.filter(name='team').exists())
-        self.assertRedirects(response, reverse('backoffice:teams'))
-
-    def test_team_delete_staff(self):
-        self.client.force_login(self.staff)
-        response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 403)
-
-    def test_team_delete_team_member(self):
-        self.client.force_login(self.user)
-        response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
-        self.assertEqual(response.status_code, 403)
+        activate('en')
+        for user, result in self.expected_results.items():
+            for visibility in Team.TeamVisibility:
+                with self.subTest(user=user, visibility=visibility):
+                    self.repeatable_setUp(visibility)
+                    self.client.force_login(getattr(self, user))
+                    response = self.client.get(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
+                    if result is True:
+                        self.assertEqual(response.status_code, 200)
+                        self.assertTemplateUsed(response, 'backoffice/components/confirmation_modal.html')
+                        self.assertContains(response, _('Team__delete__warning__header'))
+                        self.assertContains(response, _('Team__delete__submit'))
+                    else:
+                        self.assertEqual(response.status_code, 403)
+
+                    response = self.client.post(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}))
+                    if result is True:
+                        # No confirmation, should not delete
+                        self.assertTrue(Team.objects.filter(name='team').exists())
+                        self.assertEqual(response.status_code, 200)
+                        self.assertTemplateUsed(response, 'backoffice/components/confirmation_modal.html')
+                        self.assertContains(response, _('Team__delete__warning__header'))
+                        self.assertContains(response, _('Team__delete__submit'))
+                    else:
+                        self.assertEqual(response.status_code, 403)
+
+                    response = self.client.post(reverse('backoffice:team-delete', kwargs={'uuid': self.team.uuid}), data={'confirmation': 'true'})
+                    if result is True:
+                        self.assertFalse(Team.objects.filter(name='team').exists())
+                        self.assertRedirects(response, reverse('backoffice:teams'))
+                    else:
+                        self.assertEqual(response.status_code, 403)
diff --git a/src/backoffice/views/teams/teams.py b/src/backoffice/views/teams/teams.py
index 0fb6c7a49887ad50e8f0c2f6116c0c36bcbcd3e5..1a473645fe4b1b94c0f668a7140e2c284b32a6e7 100644
--- a/src/backoffice/views/teams/teams.py
+++ b/src/backoffice/views/teams/teams.py
@@ -1,7 +1,6 @@
 from typing import Any
 
-from django.contrib.postgres.aggregates.general import ArrayAgg
-from django.db.models import BooleanField, Case, Count, When
+from django.db.models import Count, Exists, OuterRef, Subquery
 from django.db.models.query import Q, QuerySet
 from django.http import HttpRequest, HttpResponse
 from django.urls import reverse, reverse_lazy
@@ -11,7 +10,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView
 from rules.contrib.views import AutoPermissionRequiredMixin
 
 from core.forms import TeamForm
-from core.models import ConferenceMember, Invitation, Team
+from core.models import ConferenceMember, Invitation, Team, TeamMember
 from core.views.mixins import FormMesssageMixin
 
 from backoffice.views.mixins import ConferenceRuleLoginRequiredMixin, SingleUUIDObjectMixin, guess_active_sidebar_item
@@ -19,27 +18,19 @@ from backoffice.views.mixins import ConferenceRuleLoginRequiredMixin, SingleUUID
 
 class TeamNavContextMixin(ConferenceRuleLoginRequiredMixin):
     def get_context_data(self, **kwargs) -> dict[str, Any]:
-        member = ConferenceMember.get_member(
-            conference=self.conference,
-            user=self.request.user,
-        )
         teams = [
             {
-                'caption': team['name'],
+                'caption': team.name,
                 'link': reverse(
                     'backoffice:team',
                     kwargs={
-                        'uuid': team['uuid'],
+                        'uuid': team.uuid,
                     },
                 ),
                 'classes': [],
             }
-            for team in Team.objects.all().values(
-                'uuid',
-                'name',
-                'require_staff',
-            )
-            if member.is_staff or not team['require_staff']
+            for team in Team.objects.all()
+            if self.request.user.has_perm('core.view_team', team)
         ]
         team_entry = {
             'caption': _('Teams'),
@@ -77,37 +68,42 @@ class TeamListView(TeamNavContextMixin, ListView):
             conference=self.conference,
             user=self.request.user,
         )
-        qs = super().get_queryset().order_by('name')
-        if member.is_staff:
-            qs = qs.annotate(
+        qs = (
+            super()
+            .get_queryset()
+            .annotate(
                 members_count=Count('members'),
+                is_member=Exists(TeamMember.objects.filter(team=OuterRef('pk'), user=self.request.user)),
+                is_manager=Exists(TeamMember.objects.filter(team=OuterRef('pk'), user=self.request.user, can_manage=True)),
             )
-        else:
-            qs = qs.filter(require_staff=False)
-        return qs.annotate(
-            member_list=ArrayAgg('members__user'),
-            self_manage_count=Count('members', filter=Q(members__user=self.request.user, members__can_manage=True)),
-        ).annotate(
-            can_manage=Case(When(self_manage_count__gt=0, then=True), default=False, output_field=BooleanField()),
         )
 
+        if self.request.user.is_superuser:
+            return qs
+        if member.is_staff:
+            return qs.exclude(visibility=Team.TeamVisibility.HIDDEN, is_member=False)
+        return qs.filter(Q(visibility=Team.TeamVisibility.PUBLIC) | Q(is_member=True))
+
 
 class TeamDetailView(SingleUUIDObjectMixin, TeamNavContextMixin, AutoPermissionRequiredMixin, DetailView):
     model = Team
     template_name = 'backoffice/teams/detail.html'
     context_object_name = 'team'
 
-    def get_queryset(self) -> QuerySet[Team]:
-        return super().get_queryset().prefetch_related('members__user')
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .prefetch_related('members', 'members__user')
+            .annotate(member_id=Subquery(TeamMember.objects.filter(team=OuterRef('pk'), user=self.request.user).values('id')[:1]))
+        )
 
     def get_context_data(self, **kwargs) -> dict[str, Any]:
         if not Team.type_is(team := self.object):  # pragma: no cover
             raise ValueError('Invalid object type')
-        member_id = self.get_queryset().filter(uuid=team.uuid, members__user=self.request.user).values_list('members__id', flat=True).first()
         log_entries = team.logentries.order_by('-timestamp')
         return {
             **super().get_context_data(**kwargs),
-            'member_id': member_id,
             'received_invitations': team.received_invitations.filter(state=Invitation.RequestsState.REQUESTED),
             'sent_invitations': team.sent_invitations.filter(state=Invitation.RequestsState.REQUESTED),
             'all_log_entries': log_entries.all(),
diff --git a/src/core/admin.py b/src/core/admin.py
index 729f8bbaecd9b6301f3d29ecd1f40b7acd73728d..216cefcfa5c5cd90120f02881de53950b16d0b83 100644
--- a/src/core/admin.py
+++ b/src/core/admin.py
@@ -322,9 +322,9 @@ class TeamMemberInline(admin.TabularInline):
 class TeamAdmin(GroupAdmin):
     model = Team
     inlines = [TeamMemberInline]
-    list_display = ['name', 'description', 'require_staff', 'member_count']
+    list_display = ['name', 'description', 'visibility', 'member_count']
     list_display_links = ['name']
-    list_filter = ['require_staff']
+    list_filter = ['visibility']
     readonly_fields = ['description_html']
     search_fields = ['name', 'description']
 
diff --git a/src/core/forms/teams.py b/src/core/forms/teams.py
index e1f91be9ee1bb9352402e47035ff3b2fb6127059..57d18711818e0a5a2e910079801c3503ae036e60 100644
--- a/src/core/forms/teams.py
+++ b/src/core/forms/teams.py
@@ -11,7 +11,7 @@ class TeamForm(ModelForm):
             'name',
             'description_en',
             'description_de',
-            'require_staff',
+            'visibility',
         ]
         help_texts = {
             'name': _('Team__name__help'),
@@ -22,7 +22,7 @@ class TeamForm(ModelForm):
         self.conference = conference
         super().__init__(*args, instance=instance, **kwargs)
         if not super_user:
-            del self.fields['require_staff']
+            del self.fields['visibility']
 
     def save(self, commit: bool = True) -> Team:
         team = super().save(commit=False)
diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index 0ea55dfcb048f77cba45eb3165692da184c03e35..1175719fd8d344f3adf5fb0bc55f6f8c6ee52a96 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -2008,6 +2008,15 @@ msgstr "Dieses Mitglied ist das letzte mit Verwaltungsrechten, sie können nicht
 msgid "TeamMember__delete__cannot_delete_last_manager"
 msgstr "Du kannst dieses Mitglied nicht entfernen, da es das letzte Mitglied mit Verwaltungsrechten ist."
 
+msgid "Team__visibility__Public"
+msgstr "öffentlich"
+
+msgid "Team__visibility__Staff"
+msgstr "Konferenz-Team"
+
+msgid "Team__visibility__Hidden"
+msgstr "Admins"
+
 msgid "Teams"
 msgstr ""
 
@@ -2023,11 +2032,11 @@ msgstr "öffentliche Information (Markdown unterstützt)."
 msgid "Team__description_html"
 msgstr "Das gerenderte HTML der Beschreibung"
 
-msgid "Team__require_staff"
-msgstr "Nur für Konferenz-Team Mitglieder"
+msgid "Team__visibility"
+msgstr "Sichtbarkeit"
 
-msgid "Team__require_staff__help"
-msgstr "Dieses Team ist nur für Konferenz-Team Mitglieder zugreifbar."
+msgid "Team__visibility__help"
+msgstr "Für wen ist dieses Team sichtbar?"
 
 msgid "ConferenceMemberTicket__token_wrong_conference"
 msgstr "Das Ticket ist nicht für diese Konferenz."
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index 13724a859864f24fe1229afe95f922f91cf711e0..9492069223bcb22df38ddabf0ae025211a646e19 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -2006,6 +2006,15 @@ msgstr "You cannot remove this members rights, as they are the last manager of t
 msgid "TeamMember__delete__cannot_delete_last_manager"
 msgstr "You cannot remove this member, as it is the last manager of the team"
 
+msgid "Team__visibility__Public"
+msgstr "public"
+
+msgid "Team__visibility__Staff"
+msgstr "conference team"
+
+msgid "Team__visibility__Hidden"
+msgstr "admins"
+
 msgid "Teams"
 msgstr ""
 
@@ -2021,11 +2030,11 @@ msgstr "public information (markdown supported)."
 msgid "Team__description_html"
 msgstr "the html rendered from the description"
 
-msgid "Team__require_staff"
-msgstr "require staff status"
+msgid "Team__visibility"
+msgstr "visibility"
 
-msgid "Team__require_staff__help"
-msgstr "This team is only accessible to persons with the staff status."
+msgid "Team__visibility__help"
+msgstr "Who may see this team?"
 
 msgid "ConferenceMemberTicket__token_wrong_conference"
 msgstr "This ticket is for another conference."
diff --git a/src/core/migrations/0171_remove_team_require_staff_team_visibility.py b/src/core/migrations/0171_remove_team_require_staff_team_visibility.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc5b51f7edf9f943a1f153a9e2c0f68489795cc9
--- /dev/null
+++ b/src/core/migrations/0171_remove_team_require_staff_team_visibility.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.1.3 on 2024-12-24 01:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("core", "0170_alter_assembly_slug"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="team",
+            name="require_staff",
+        ),
+        migrations.AddField(
+            model_name="team",
+            name="visibility",
+            field=models.CharField(
+                choices=[
+                    ("public", "Team__visibility__Public"),
+                    ("staff", "Team__visibility__Staff"),
+                    ("hidden", "Team__visibility__Hidden"),
+                ],
+                default="staff",
+                help_text="Team__visibility__help",
+                verbose_name="Team__visibility",
+            ),
+        ),
+    ]
diff --git a/src/core/models/teams/teams.py b/src/core/models/teams/teams.py
index ccd8b07cfc970a2a44999cf243ddec78cd914f97..5a445ecc4326faa73865ea34fe5ccee6870971fa 100644
--- a/src/core/models/teams/teams.py
+++ b/src/core/models/teams/teams.py
@@ -32,24 +32,36 @@ def is_team_manager(user: 'PlatformUser', team: 'Team | None' = None) -> bool:
     return user.is_authenticated and team.members.filter(user=user, can_manage=True).exists()
 
 
+@predicate
+def is_staff_team(user: 'PlatformUser', team: 'Team | None' = None) -> bool:
+    if team is None:  # pragma: no cover
+        return False
+    return team.visibility == Team.TeamVisibility.STAFF
+
+
 @predicate
 def is_public_team(user: 'PlatformUser', team: 'Team | None' = None) -> bool:
     # If team is None, we are checking for the permission to view the list of teams, everybody can see it.
     if team is None:
         return True
-    return not team.require_staff
+    return team.visibility == Team.TeamVisibility.PUBLIC
 
 
 class Team(RulesModelMixin, ActivityLogMixin, Group, metaclass=RulesModelBase):
+    class TeamVisibility(models.TextChoices):
+        PUBLIC = 'public', _('Team__visibility__Public')
+        STAFF = 'staff', _('Team__visibility__Staff')
+        HIDDEN = 'hidden', _('Team__visibility__Hidden')
+
     class Meta:
         rules_permissions = {
-            'view': is_conference_staff | is_public_team,
+            'view': is_team_member | is_public_team | is_superuser | (is_conference_staff & is_staff_team),
             'view_details': is_team_member | is_superuser,
             'add': is_superuser,
             'change': is_team_manager | is_superuser,
             'change_permissions': is_superuser,
             'delete': is_superuser,
-            'can_join': is_conference_staff | is_public_team,
+            'can_join': (is_conference_staff & is_staff_team) | is_public_team | is_superuser,
         }
         indexes = [
             models.Index(fields=['uuid']),
@@ -72,11 +84,11 @@ class Team(RulesModelMixin, ActivityLogMixin, Group, metaclass=RulesModelBase):
         null=True,
         verbose_name=_('Team__description_html'),
     )
-    # Is this ream only for staff members (e.g. assembly team)?
-    require_staff = models.BooleanField(
-        default=False,
-        verbose_name=_('Team__require_staff'),
-        help_text=_('Team__require_staff__help'),
+    visibility = models.CharField(
+        default=TeamVisibility.STAFF,
+        verbose_name=_('Team__visibility'),
+        help_text=_('Team__visibility__help'),
+        choices=TeamVisibility.choices,
     )
 
     sent_invitations = GenericRelation(