diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index a9a28902caffb7a90eb837e27e832e8932380055..db629a7edf4c0072820a67757cdb43afd496e44a 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -18,7 +18,7 @@ from dynamic_preferences.types import (
 from oauth2_provider.models import AbstractApplication
 
 from .mixins import CalendarEventMixin, PublicFilePreferenceMixin
-from .models import Group, Person
+from .models import Group, GroupType, Person
 from .registries import person_preferences_registry, site_preferences_registry
 from .util.notifications import get_notification_choices_lazy
 
@@ -544,3 +544,47 @@ class DisallowedUids(LongStringPreference):
     )
     required = False
     verbose_name = _("Comma-separated list of disallowed usernames")
+
+
+@site_preferences_registry.register
+class GroupTypesGroupsOwners(ModelMultipleChoicePreference):
+    section = general
+    name = "group_types_groups_owners"
+    required = False
+    default = []
+    model = GroupType
+    verbose_name = _("User is allowed to see groups the user is an owner of with these group types")
+
+
+@site_preferences_registry.register
+class GroupTypesGroupMembersOwners(ModelMultipleChoicePreference):
+    section = general
+    name = "group_types_group_members_owners"
+    required = False
+    default = []
+    model = GroupType
+    verbose_name = _(
+        "User is allowed to see members of groups the user is an owner of with these group types"
+    )
+
+
+@site_preferences_registry.register
+class GroupTypesGroupMembersOwnersAllowedInformation(MultipleChoicePreference):
+    section = general
+    name = "group_types_group_members_owners_allowed_information"
+    default = []
+    widget = SelectMultiple
+    verbose_name = _(
+        "Information user is allowed to see about members of groups the user is an owner"
+    )
+    required = False
+
+    field_attribute = {"initial": []}
+    choices = [
+        ("personal_details", _("Personal details")),
+        ("address", _("Address")),
+        ("contact_details", _("Contact details")),
+        ("photo", _("Photo")),
+        ("avatar", _("Avatar")),
+        ("groups", _("Groups")),
+    ]
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 8dd99d60482bfe62bed8d72a38938e442c68592f..06dc986c4d8f24722387dcc121fd5239c570754e 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -1,15 +1,20 @@
 import rules
 from rules import is_superuser
 
-from .models import Announcement, Group, GroupType, Holiday, Person
+from .models import Announcement, GroupType, Holiday
 from .util.predicates import (
+    has_any_group,
     has_any_object,
+    has_any_person,
     has_global_perm,
     has_object_perm,
     has_person,
     is_anonymous,
     is_current_person,
     is_group_owner,
+    is_group_owner_allowed_information,
+    is_group_owner_of_person_with_group_type,
+    is_group_owner_with_group_type,
     is_notification_recipient,
     is_own_celery_task,
     is_personal_event_owner,
@@ -54,20 +59,24 @@ search_predicate = has_person & has_global_perm("core.search")
 rules.add_perm("core.search_rule", search_predicate)
 
 # View persons
-view_persons_predicate = has_person & (
-    has_global_perm("core.view_person") | has_any_object("core.view_person", Person)
-)
+view_persons_predicate = has_person & (has_global_perm("core.view_person") | has_any_person)
 rules.add_perm("core.view_persons_rule", view_persons_predicate)
 
 # View person
 view_person_predicate = has_person & (
-    is_current_person | has_global_perm("core.view_person") | has_object_perm("core.view_person")
+    is_current_person
+    | has_global_perm("core.view_person")
+    | has_object_perm("core.view_person")
+    | is_group_owner_of_person_with_group_type
 )
 rules.add_perm("core.view_person_rule", view_person_predicate)
 
 # View person address
 view_address_predicate = has_person & (
-    is_current_person | has_global_perm("core.view_address") | has_object_perm("core.view_address")
+    is_current_person
+    | has_global_perm("core.view_address")
+    | has_object_perm("core.view_address")
+    | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("address"))
 )
 rules.add_perm("core.view_address_rule", view_address_predicate)
 
@@ -76,28 +85,39 @@ view_contact_details_predicate = has_person & (
     is_current_person
     | has_global_perm("core.view_contact_details")
     | has_object_perm("core.view_contact_details")
+    | (
+        is_group_owner_of_person_with_group_type
+        & is_group_owner_allowed_information("contact_details")
+    )
 )
 rules.add_perm("core.view_contact_details_rule", view_contact_details_predicate)
 
 # View person photo
 view_photo_predicate = has_person & (
-    is_current_person | has_global_perm("core.view_photo") | has_object_perm("core.view_photo")
+    is_current_person
+    | has_global_perm("core.view_photo")
+    | has_object_perm("core.view_photo")
+    | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("photo"))
 )
 rules.add_perm("core.view_photo_rule", view_photo_predicate)
 
 # View person avatar image
 view_avatar_predicate = has_person & (
-    is_current_person | has_global_perm("core.view_avatar") | has_object_perm("core.view_avatar")
+    is_current_person
+    | has_global_perm("core.view_avatar")
+    | has_object_perm("core.view_avatar")
+    | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("avatar"))
 )
 rules.add_perm("core.view_avatar_rule", view_avatar_predicate)
 
 # View persons groups
-view_groups_predicate = has_person & (
+view_person_groups_predicate = has_person & (
     is_current_person
     | has_global_perm("core.view_person_groups")
     | has_object_perm("core.view_person_groups")
+    | (is_group_owner_of_person_with_group_type & is_group_owner_allowed_information("groups"))
 )
-rules.add_perm("core.view_person_groups_rule", view_groups_predicate)
+rules.add_perm("core.view_person_groups_rule", view_person_groups_predicate)
 
 # Edit person
 edit_person_predicate = has_person & (
@@ -114,14 +134,14 @@ delete_person_predicate = has_person & (
 rules.add_perm("core.delete_person_rule", delete_person_predicate)
 
 # View groups
-view_groups_predicate = has_person & (
-    has_global_perm("core.view_group") | has_any_object("core.view_group", Group)
-)
+view_groups_predicate = has_person & (has_global_perm("core.view_group") | has_any_group)
 rules.add_perm("core.view_groups_rule", view_groups_predicate)
 
 # View group
 view_group_predicate = has_person & (
-    has_global_perm("core.view_group") | has_object_perm("core.view_group")
+    is_group_owner_with_group_type
+    | has_global_perm("core.view_group")
+    | has_object_perm("core.view_group")
 )
 rules.add_perm("core.view_group_rule", view_group_predicate)
 
@@ -195,6 +215,10 @@ view_personal_details_predicate = has_person & (
     is_current_person
     | has_global_perm("core.view_personal_details")
     | has_object_perm("core.view_personal_details")
+    | (
+        is_group_owner_of_person_with_group_type
+        & is_group_owner_allowed_information("personal_details")
+    )
 )
 rules.add_perm("core.view_personal_details_rule", view_personal_details_predicate)
 
@@ -232,6 +256,9 @@ view_group_type_predicate = has_person & (
 )
 rules.add_perm("core.view_grouptype_rule", view_group_type_predicate)
 
+fetch_group_types_predicate = has_person
+rules.add_perm("core.fetch_grouptypes_rule", fetch_group_types_predicate)
+
 # Edit group type
 change_group_type_predicate = has_person & (
     has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype")
@@ -273,6 +300,10 @@ rules.add_perm("core.create_group_rule", create_group_predicate)
 view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm")
 rules.add_perm("core.view_schoolterm_rule", view_school_term_predicate)
 
+fetch_school_terms_predicate = has_person
+rules.add_perm("core.fetch_schoolterms_rule", fetch_school_terms_predicate)
+
+
 create_school_term_predicate = has_person & has_global_perm("core.add_schoolterm")
 rules.add_perm("core.create_schoolterm_rule", create_school_term_predicate)
 
@@ -286,7 +317,9 @@ rules.add_perm("core.delete_schoolterm_rule", delete_schoolterm_predicate)
 
 # View group stats
 view_group_stats_predicate = has_person & (
-    has_global_perm("core.view_group_stats") | has_object_perm("core.view_group_stats")
+    is_group_owner_with_group_type
+    | has_global_perm("core.view_group_stats")
+    | has_object_perm("core.view_group_stats")
 )
 rules.add_perm("core.view_group_stats_rule", view_group_stats_predicate)
 
diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py
index 41eac3b485ed01454fe7e7f09bff0f7a7e73a7e7..12b8bb1f112b1e46971beb36347c1523613f578f 100644
--- a/aleksis/core/schema/__init__.py
+++ b/aleksis/core/schema/__init__.py
@@ -21,7 +21,13 @@ from ..models import (
     TaskUserAssignment,
 )
 from ..util.apps import AppConfig
-from ..util.core_helpers import get_allowed_object_ids, get_app_module, get_app_packages, has_person
+from ..util.core_helpers import (
+    get_allowed_object_ids,
+    get_app_module,
+    get_app_packages,
+    get_site_preferences,
+    has_person,
+)
 from .base import FilterOrderList
 from .calendar import CalendarBaseType, SetCalendarStatusMutation
 from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType
@@ -130,12 +136,25 @@ class Query(graphene.ObjectType):
         )
 
     def resolve_persons(root, info, **kwargs):
-        return get_objects_for_user(info.context.user, "core.view_person", Person.objects.all())
+        qs = get_objects_for_user(info.context.user, "core.view_person", Person.objects.all())
+        if has_person(info.context.user):
+            qs = qs | Person.objects.filter(id=info.context.user.person.id)
+        return (
+            qs
+            | Person.objects.filter(
+                member_of__in=Group.objects.filter(
+                    owners=info.context.user.person,
+                    group_type__in=get_site_preferences()[
+                        "general__group_types_group_members_owners"
+                    ],
+                ),
+            )
+        ).distinct()
 
     def resolve_person_by_id(root, info, id):  # noqa
         person = Person.objects.get(pk=id)
         if not info.context.user.has_perm("core.view_person_rule", person):
-            raise PermissionDenied()
+            return None
         return person
 
     def resolve_person_by_id_or_me(root, info, **kwargs):  # noqa
@@ -145,12 +164,19 @@ class Query(graphene.ObjectType):
 
         person = Person.objects.get(pk=kwargs["id"])
         if not info.context.user.has_perm("core.view_person_rule", person):
-            raise PermissionDenied()
+            return None
         return person
 
     @staticmethod
     def resolve_groups(root, info, **kwargs):
-        return get_objects_for_user(info.context.user, "core.view_group", Group)
+        qs = get_objects_for_user(info.context.user, "core.view_group", Group)
+        return (
+            qs
+            | Group.objects.filter(
+                owners=info.context.user.person,
+                group_type__in=get_site_preferences()["general__group_types_groups_owners"],
+            )
+        ).distinct()
 
     @staticmethod
     def resolve_group_by_id(root, info, id):  # noqa
diff --git a/aleksis/core/schema/group.py b/aleksis/core/schema/group.py
index a5b0d96d3564d23baafffb089117dc0a5229c7d9..2a79b8b95a0cf09f068d339577b226c2e572a4d4 100644
--- a/aleksis/core/schema/group.py
+++ b/aleksis/core/schema/group.py
@@ -1,11 +1,9 @@
-from django.core.exceptions import PermissionDenied
-
 import graphene
 from graphene_django import DjangoObjectType
 from guardian.shortcuts import get_objects_for_user
 
 from ..models import Group, Person
-from ..util.core_helpers import has_person
+from ..util.core_helpers import get_site_preferences, has_person
 from .base import BaseBatchDeleteMutation, DjangoFilterMixin, PermissionsTypeMixin
 
 
@@ -38,14 +36,27 @@ class GroupType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
 
     @staticmethod
     def resolve_parent_groups(root, info, **kwargs):
-        return get_objects_for_user(info.context.user, "core.view_group", root.parent_groups.all())
+        qs = root.parent_groups.all()
+        if info.context.user.has_perm("core.view_group_rule", root):
+            return qs
+        return get_objects_for_user(info.context.user, "core.view_group", qs)
 
     @staticmethod
     def resolve_child_groups(root, info, **kwargs):
-        return get_objects_for_user(info.context.user, "core.view_group", root.child_groups.all())
+        qs = root.child_groups.all()
+        if info.context.user.has_perm("core.view_group_rule", root):
+            return qs
+        return get_objects_for_user(info.context.user, "core.view_group", qs)
 
     @staticmethod
     def resolve_members(root, info, **kwargs):
+        if (
+            has_person(info.context.user)
+            and root.group_type
+            in get_site_preferences()["general__group_types_group_members_owners"]
+            and info.context.user.person in root.owners.all()
+        ):
+            return root.members.all()
         persons = get_objects_for_user(info.context.user, "core.view_person", root.members.all())
         if has_person(info.context.user) and [
             m for m in root.members.all() if m.pk == info.context.user.person.pk
@@ -62,14 +73,10 @@ class GroupType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
             persons = (persons | Person.objects.filter(pk=info.context.user.person.pk)).distinct()
         return persons
 
-    @staticmethod
-    def resolve_group_type(root, info, **kwargs):
-        if info.context.user.has_perm("core.view_grouptype_rule", root.group_type):
-            return root.group_type
-        raise PermissionDenied()
-
     @staticmethod
     def resolve_statistics(root: Group, info, **kwargs):
+        if not info.context.user.has_perm("core.view_group_stats_rule", root):
+            return None
         return root.get_group_stats
 
 
diff --git a/aleksis/core/schema/group_type.py b/aleksis/core/schema/group_type.py
index 4d5e00cff5d187bd6fd63ce5e80244b597ec0a55..77febfb9099398adb23359350b1bc982b4ca568e 100644
--- a/aleksis/core/schema/group_type.py
+++ b/aleksis/core/schema/group_type.py
@@ -1,5 +1,4 @@
 from graphene_django import DjangoObjectType
-from guardian.shortcuts import get_objects_for_user
 
 from ..models import GroupType
 from .base import (
@@ -22,7 +21,9 @@ class GroupTypeType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
 
     @classmethod
     def get_queryset(cls, queryset, info):
-        return get_objects_for_user(info.context.user, "core.view_grouptype", GroupType)
+        if not info.context.user.has_perm("core.fetch_grouptypes_rule"):
+            return []
+        return queryset
 
 
 class GroupTypeBatchCreateMutation(BaseBatchCreateMutation):
diff --git a/aleksis/core/schema/person.py b/aleksis/core/schema/person.py
index 6e62933255957fa3199a6f935244d02b5115ff45..3b24bbafea5e9558dfdf2718d0d74cfe2d0561b6 100644
--- a/aleksis/core/schema/person.py
+++ b/aleksis/core/schema/person.py
@@ -1,11 +1,9 @@
 from typing import Union
 
-from django.core.exceptions import PermissionDenied
 from django.utils import timezone
 
 import graphene
 from graphene_django import DjangoObjectType
-from guardian.shortcuts import get_objects_for_user
 
 from ..filters import PersonFilter
 from ..models import DummyPerson, Person
@@ -137,28 +135,28 @@ class PersonType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
 
     def resolve_children(root, info, **kwargs):  # noqa
         if info.context.user.has_perm("core.view_personal_details_rule", root):
-            return get_objects_for_user(info.context.user, "core.view_person", root.children.all())
+            return root.children.all()
         return []
 
     def resolve_guardians(root, info, **kwargs):  # noqa
         if info.context.user.has_perm("core.view_personal_details_rule", root):
-            return get_objects_for_user(info.context.user, "core.view_person", root.guardians.all())
+            return root.guardians.all()
         return []
 
     def resolve_member_of(root, info, **kwargs):  # noqa
         if info.context.user.has_perm("core.view_person_groups_rule", root):
-            return get_objects_for_user(info.context.user, "core.view_group", root.member_of.all())
+            return root.member_of.all()
         return []
 
     def resolve_owner_of(root, info, **kwargs):  # noqa
         if info.context.user.has_perm("core.view_person_groups_rule", root):
-            return get_objects_for_user(info.context.user, "core.view_group", root.owner_of.all())
+            return root.owner_of.all()
         return []
 
     def resolve_primary_group(root, info, **kwargs):  # noqa
-        if info.context.user.has_perm("core.view_group_rule", root.primary_group):
+        if info.context.user.has_perm("core.view_person_groups_rule", root):
             return root.primary_group
-        raise PermissionDenied()
+        return None
 
     def resolve_username(root, info, **kwargs):  # noqa
         return root.user.username if root.user else None
diff --git a/aleksis/core/schema/school_term.py b/aleksis/core/schema/school_term.py
index a8b64d80faa99d95d93e8468586eb8f12e310de6..8dd36b47a8978bd448a28ba08f8948f2564cd2bd 100644
--- a/aleksis/core/schema/school_term.py
+++ b/aleksis/core/schema/school_term.py
@@ -1,5 +1,3 @@
-from django.core.exceptions import PermissionDenied
-
 from graphene_django import DjangoObjectType
 
 from ..models import SchoolTerm
@@ -24,9 +22,8 @@ class SchoolTermType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
 
     @classmethod
     def get_queryset(cls, queryset, info, **kwargs):
-        if not info.context.user.has_perm("core.view_schoolterm_rule"):
-            raise PermissionDenied
-
+        if not info.context.user.has_perm("core.fetch_schoolterms_rule"):
+            return []
         return queryset
 
 
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index e161cc7f9736d60d56a7f47852d0e859b52fd37f..6cc71cd032335313f46eae1d64ae2afa5363d4f1 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -434,9 +434,7 @@ REST_FRAMEWORK = {
 }
 
 # Configuration for GraphQL framework
-GRAPHENE = {
-    "SCHEMA": "aleksis.core.schema.schema",
-}
+GRAPHENE = {"SCHEMA": "aleksis.core.schema.schema", "TESTING_ENDPOINT": "/graphql/"}
 
 # LDAP config
 if _settings.get("ldap.uri", None):
diff --git a/aleksis/core/tests/schema/test_groups.py b/aleksis/core/tests/schema/test_groups.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ca647404d3e73e6dbd0e904620cb3704389beb6
--- /dev/null
+++ b/aleksis/core/tests/schema/test_groups.py
@@ -0,0 +1,72 @@
+import json
+import pytest
+from graphql.error.graphql_error import GraphQLError
+
+from aleksis.core.models import Group, GroupType, Person
+from django.contrib.auth.models import Permission
+
+from aleksis.core.util.core_helpers import get_site_preferences
+
+
+pytestmark = pytest.mark.django_db
+
+
+def test_groups_query(client_query):
+    p = Person.objects.first()
+    correct_group_type = GroupType.objects.create(name="correct")
+    wrong_group_type = GroupType.objects.create(name="wrong")
+
+    group_not_owner = Group.objects.create(name="not_owner")
+    group_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type)
+
+    group2_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type)
+    group_wrong_group_type_owner = Group.objects.create(name="wrong_group_type_owner", group_type=wrong_group_type)
+    group_no_group_type_owner = Group.objects.create(name="no_group_type_owner")
+
+    for g in (group_correct_group_type_owner, group2_correct_group_type_owner, group_wrong_group_type_owner, group_no_group_type_owner):
+        g.owners.add(p)
+
+    get_site_preferences()["general__group_types_groups_owners"] = []
+
+    response, content = client_query(
+        "{groups{id}}"
+    )
+    assert len(content["data"]["groups"]) == 0
+
+    for g in Group.objects.all():
+        with pytest.raises(GraphQLError):
+            response, content = client_query(
+                "query groupById($id: ID) {object: groupById(id: $id) { id } }",
+                variables={"id": g.id},
+            )
+
+    global_permission = Permission.objects.get(codename="view_group", content_type__app_label="core")
+    p.user.user_permissions.add(global_permission)
+
+    response, content = client_query(
+        "{groups{id}}"
+    )
+    assert set(int(g["id"]) for g in content["data"]["groups"]) == set(Group.objects.values_list("id", flat=True))
+
+    p.user.user_permissions.remove(global_permission)
+
+    get_site_preferences()["general__group_types_groups_owners"] = [correct_group_type]
+
+    response, content = client_query(
+        "{groups{id}}"
+    )
+    assert set(int(g["id"]) for g in content["data"]["groups"]) == {group_correct_group_type_owner.id, group2_correct_group_type_owner.id}
+
+    for g in (group_correct_group_type_owner, group2_correct_group_type_owner):
+        response, content = client_query(
+            "query groupById($id: ID) {object: groupById(id: $id) { id } }",
+            variables={"id": g.id},
+        )
+        assert content["data"]["object"]["id"] == str(g.id)
+
+    for g in (group_not_owner,  group_wrong_group_type_owner, group_no_group_type_owner):
+        with pytest.raises(GraphQLError):
+            response, content = client_query(
+                "query groupById($id: ID) {object: groupById(id: $id) { id } }",
+                variables={"id": g.id},
+            )
diff --git a/aleksis/core/tests/schema/test_persons.py b/aleksis/core/tests/schema/test_persons.py
new file mode 100644
index 0000000000000000000000000000000000000000..565d90cabaa65ad17af7810f82299167b0c125d0
--- /dev/null
+++ b/aleksis/core/tests/schema/test_persons.py
@@ -0,0 +1,83 @@
+import json
+import pytest
+from graphql.error.graphql_error import GraphQLError
+
+from aleksis.core.models import Group, GroupType, Person
+from django.contrib.auth.models import Permission
+
+from aleksis.core.util.core_helpers import get_site_preferences
+
+
+pytestmark = pytest.mark.django_db
+
+
+def test_persons_query(client_query):
+    p = Person.objects.first()
+    correct_group_type = GroupType.objects.create(name="correct")
+    wrong_group_type = GroupType.objects.create(name="wrong")
+
+    group_not_owner = Group.objects.create(name="not_owner")
+    group_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type)
+
+    group2_correct_group_type_owner = Group.objects.create(name="correct_group_type_owner", group_type=correct_group_type)
+    group_wrong_group_type_owner = Group.objects.create(name="wrong_group_type_owner", group_type=wrong_group_type)
+    group_no_group_type_owner = Group.objects.create(name="no_group_type_owner")
+
+    for g in (group_correct_group_type_owner, group2_correct_group_type_owner, group_wrong_group_type_owner, group_no_group_type_owner):
+        g.owners.add(p)
+
+    correct_member = Person.objects.create(first_name="correct_member", last_name="correct_member")
+    correct_member_2 = Person.objects.create(first_name="correct_member_2", last_name="correct_member_2")
+    wrong_member = Person.objects.create(first_name="wrong_member", last_name="wrong_member")
+
+    for g in (group_correct_group_type_owner, group2_correct_group_type_owner):
+        g.members.add(correct_member, correct_member_2)
+
+    for g in (group_not_owner, group_wrong_group_type_owner, group_no_group_type_owner):
+        g.members.add(wrong_member)
+
+
+    get_site_preferences()["general__group_types_group_members_owners"] = []
+
+    response, content = client_query(
+        "{persons{id}}"
+    )
+    assert len(content["data"]["persons"]) == 1
+    assert content["data"]["persons"][0]["id"] == str(p.id)
+
+    for g in Person.objects.exclude(pk=p.id):
+        response, content = client_query(
+            "query personById($id: ID) {object: personById(id: $id) { id } }",
+            variables={"id": g.id},
+        )
+        assert content["data"]["object"] == None
+
+    global_permission = Permission.objects.get(codename="view_person", content_type__app_label="core")
+    p.user.user_permissions.add(global_permission)
+
+    response, content = client_query(
+        "{persons{id}}"
+    )
+    assert set(int(g["id"]) for g in content["data"]["persons"]) == set(Person.objects.values_list("id", flat=True))
+
+    p.user.user_permissions.remove(global_permission)
+
+    get_site_preferences()["general__group_types_group_members_owners"] = [correct_group_type]
+
+    response, content = client_query(
+        "{persons{id}}"
+    )
+    assert set(int(g["id"]) for g in content["data"]["persons"]) == {p.id, correct_member.id, correct_member_2.id}
+
+    for g in (correct_member, correct_member_2):
+        response, content = client_query(
+            "query personById($id: ID) {object: personById(id: $id) { id } }",
+            variables={"id": g.id},
+        )
+        assert content["data"]["object"]["id"] == str(g.id)
+
+    response, content = client_query(
+        "query personById($id: ID) {object: personById(id: $id) { id } }",
+        variables={"id": wrong_member.id},
+    )
+    assert content["data"]["object"] == None
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index 3d91751938c785baf694f379b4126fb879e6a0fc..c01d7ca940930ceb22b1bcd6b67a5bf428c93fc0 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -11,7 +11,7 @@ from guardian.shortcuts import get_objects_for_user
 from rules import predicate
 
 from ..mixins import ExtensibleModel
-from ..models import Group, PersonalEvent
+from ..models import Group, Person, PersonalEvent
 from .core_helpers import get_content_type_by_perm, get_site_preferences, queryset_rules_filter
 from .core_helpers import has_person as has_person_helper
 
@@ -177,3 +177,59 @@ def is_own_celery_task(user: User, obj: Model) -> bool:
 def is_personal_event_owner(user: User, personal_event: PersonalEvent) -> bool:
     """Predicate which checks if the user is the owner of the provided custom event."""
     return user.person == personal_event.owner
+
+
+@predicate
+def is_group_owner_with_group_type(user: User, group: Group) -> bool:
+    """Predicate whick checks if the user is a owner of the group (with allowed group type)."""
+    if group.group_type not in get_site_preferences()["general__group_types_groups_owners"]:
+        return False
+    return user.person in group.owners.all()
+
+
+@predicate
+def is_group_owner_of_person_with_group_type(user: User, person: Person) -> bool:
+    """Check if the user is a group owner of the person (with allowed group type)."""
+    return person.member_of.filter(
+        group_type__in=get_site_preferences()["general__group_types_group_members_owners"],
+        owners=user.person,
+    ).exists()
+
+
+@predicate
+def is_group_owner_allowed_information(key: str):
+    """Predicate which checks if the information is allowed for group owners."""
+    name = f"is_group_owner_allowed_information:{key}"
+
+    @predicate(name)
+    def fn() -> bool:
+        return (
+            key
+            in get_site_preferences()[
+                "general__group_types_group_members_owners_allowed_information"
+            ]
+        )
+
+    return fn
+
+
+@predicate
+def has_any_group(user: User):
+    qs = get_objects_for_user(user, "core.view_group", Group)
+    qs = qs | Group.objects.filter(
+        owners=user.person,
+        group_type__in=get_site_preferences()["general__group_types_groups_owners"],
+    )
+    return qs.exists()
+
+
+@predicate
+def has_any_person(user: User):
+    qs = get_objects_for_user(user, "core.view_person", Person.objects.all())
+    qs = qs | Person.objects.filter(
+        member_of__in=Group.objects.filter(
+            owners=user.person,
+            group_type__in=get_site_preferences()["general__group_types_group_members_owners"],
+        ),
+    )
+    return qs.exists()
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index cca5f2e36e17e4fc8d3880f1963089e75b911bf6..9d3864a1d3b3e2c9a8c8e935262a93a4b50d5412 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -274,6 +274,13 @@ def groups(request: HttpRequest) -> HttpResponse:
 
     # Get all groups
     groups = get_objects_for_user(request.user, "core.view_group", Group)
+    groups = (
+        groups
+        | Group.objects.filter(
+            owners=request.user.person,
+            group_type__in=get_site_preferences()["general__group_types_groups_owners"],
+        )
+    ).distinct()
 
     # Get filter
     groups_filter = GroupFilter(request.GET, queryset=groups)
diff --git a/conftest.py b/conftest.py
index e0de0cc90762c0d5230d0aaa7c296d0b7d121b24..1a448a6f93b2cc6ae536c916c8a9ab56d5b9fe5a 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1 +1,66 @@
+import json
+
+import pytest
+from graphene_django.settings import graphene_settings
+
 pytest_plugins = ("celery.contrib.pytest",)
+
+
+def graphql_query(
+    query,
+    operation_name=None,
+    input_data=None,
+    variables=None,
+    headers=None,
+    client=None,
+):
+    """Do a GraphQL query for testing."""
+    graphql_url = graphene_settings.TESTING_ENDPOINT
+    body = {"query": query}
+    if operation_name:
+        body["operationName"] = operation_name
+    if variables:
+        body["variables"] = variables
+    if input_data:
+        if "variables" in body:
+            body["variables"]["input"] = input_data
+        else:
+            body["variables"] = {"input": input_data}
+    if headers:
+        header_params = {"headers": headers}
+        resp = client.post(
+            graphql_url,
+            json.dumps([body]),
+            content_type="application/json",
+            **header_params,
+        )
+    else:
+        resp = client.post(
+            graphql_url, json.dumps([body]), content_type="application/json"
+        )
+    content = json.loads(resp.content)[0]
+    return resp, content
+
+
+@pytest.fixture
+def logged_in_client(client, django_user_model):
+    """Provide a logged-in client for testing."""
+    from aleksis.core.models import Person
+    username = "foo"
+    password = "bar"
+
+    user = django_user_model.objects.create_user(username=username, password=password)
+    Person.objects.create(user=user, first_name="John", last_name="Doe")
+
+    client.login(username=username, password=password)
+
+    return client
+
+
+@pytest.fixture
+def client_query(logged_in_client):
+    """Do a GraphQL query with a logged-in client."""
+    def func(*args, **kwargs):
+        return graphql_query(*args, **kwargs, client=logged_in_client)
+
+    return func