diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py
index ecbd78eb930b83bd7fa6ba493e37ee242fb37fc9..e748adbdd9f696aa30a00d3a8b4af98863aaac7d 100644
--- a/aleksis/core/schema/__init__.py
+++ b/aleksis/core/schema/__init__.py
@@ -30,6 +30,13 @@ from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType
 from .custom_menu import CustomMenuType
 from .dynamic_routes import DynamicRouteType
 from .group import GroupType
+from .holiday import (
+    HolidayBatchDeleteMutation,
+    HolidayBatchPatchMutation,
+    HolidayCreateMutation,
+    HolidayDeleteMutation,
+    HolidayType,
+)
 from .installed_apps import AppType
 from .message import MessageType
 from .notification import MarkNotificationReadMutation, NotificationType
@@ -94,6 +101,8 @@ class Query(graphene.ObjectType):
 
     school_terms = FilterOrderList(SchoolTermType)
 
+    holidays = FilterOrderList(HolidayType)
+
     def resolve_ping(root, info, payload) -> str:
         return payload
 
@@ -236,6 +245,11 @@ class Mutation(graphene.ObjectType):
     delete_school_terms = SchoolTermBatchDeleteMutation.Field()
     update_school_terms = SchoolTermBatchPatchMutation.Field()
 
+    create_holiday = HolidayCreateMutation.Field()
+    delete_holiday = HolidayDeleteMutation.Field()
+    delete_holidays = HolidayBatchDeleteMutation.Field()
+    update_holidays = HolidayBatchPatchMutation.Field()
+
 
 def build_global_schema():
     """Build global GraphQL schema from all apps."""
diff --git a/aleksis/core/schema/holiday.py b/aleksis/core/schema/holiday.py
new file mode 100644
index 0000000000000000000000000000000000000000..80d82045441ea16c9184a61ac8eed636e1c5daa6
--- /dev/null
+++ b/aleksis/core/schema/holiday.py
@@ -0,0 +1,52 @@
+from graphene_django import DjangoObjectType
+from graphene_django_cud.mutations import DjangoBatchDeleteMutation, DjangoBatchPatchMutation, DjangoCreateMutation
+
+from ..models import Holiday
+from .base import (
+    DeleteMutation,
+    DjangoFilterMixin,
+    PermissionBatchPatchMixin,
+    PermissionsTypeMixin,
+    PermissionBatchDeleteMixin,
+)
+
+
+class HolidayType(PermissionsTypeMixin, DjangoFilterMixin,  DjangoObjectType):
+    class Meta:
+        model = Holiday
+        fields = ("id", "holiday_name", "date_start", "date_end")
+        filter_fields = {
+            "id": ["exact", "lte", "gte"],
+            "holiday_name": ["icontains"],
+            "date_start": ["exact", "lte", "gte"],
+            "date_end": ["exact", "lte", "gte"],
+        }
+
+    @classmethod
+    def get_queryset(cls, queryset, info):
+        return queryset  # FIXME filter this queryset based on permissions
+
+
+class HolidayCreateMutation(DjangoCreateMutation):
+    class Meta:
+        model = Holiday
+        permissions = ("core.create_holiday",)
+        only_fields = ("holiday_name", "date_start", "date_end")
+
+
+class HolidayDeleteMutation(DeleteMutation):
+    klass = Holiday
+    permission_required = "core.delete_holiday"
+
+
+class HolidayBatchDeleteMutation(PermissionBatchDeleteMixin, DjangoBatchDeleteMutation):
+    class Meta:
+        model = Holiday
+        permissions = ("core.delete_holiday",)
+
+
+class HolidayBatchPatchMutation(PermissionBatchPatchMixin, DjangoBatchPatchMutation):
+    class Meta:
+        model = Holiday
+        permissions = ("core.change_holiday",)
+        only_fields = ("holiday_name", "date_start", "date_end")