diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index d0e5a547649912039b3095276fb48acebc686ef2..fa090226c37c8032680aa7c0d75bd1b75aa40276 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1,6 +1,7 @@
 from datetime import date, datetime
 from typing import Optional, Iterable, Union, Sequence, List
 
+import jsonstore
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group as DjangoGroup
 from django.contrib.contenttypes.fields import GenericForeignKey
@@ -11,6 +12,7 @@ from django.db.models import QuerySet
 from django.forms.widgets import Media
 from django.urls import reverse
 from django.utils import timezone
+from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 from dynamic_preferences.models import PerInstancePreferenceModel
 from image_cropping import ImageCropField, ImageRatioField
@@ -23,6 +25,24 @@ from .util.core_helpers import get_site_preferences, now_tomorrow
 from .util.model_helpers import ICONS
 
 
+FIELD_CHOICES = (
+    ("booleanfield", "BooleanField"),
+    ("charfield", "CharField"),
+    ("datefield", "DateField"),
+    ("datetimefield", "DateTimeField"),
+    ("decimalfield", "DecimalField"),
+    ("emailfield", "EmailField"),
+    ("floatfield", "FloatField"),
+    ("Integerfield", "IntegerField"),
+    ("ipaddressfield", "IPAddressField"),
+    ("genericipaddressfield", "GenericIPAddressField"),
+    ("nullbooleanfield", "NullBooleanField"),
+    ("textfield", "TextField"),
+    ("timefield", "TimeField"),
+    ("urlfield", "URLField"),
+)
+
+
 class Person(ExtensibleModel):
     """ A model describing any person related to a school, including, but not
     limited to, students, teachers and guardians (parents).
@@ -197,7 +217,16 @@ class DummyPerson(Person):
 
     def save(self, *args, **kwargs):
         pass
-        
+
+
+class AdditionalField(ExtensibleModel):
+    title = models.CharField(verbose_name=_("Title of field"), max_length=50)
+    field_type = models.CharField(verbose_name=_("Type of field"), choices=FIELD_CHOICES, max_length=50)
+
+    class Meta:
+        verbose_name = _("Addtitional field")
+        verbose_name_plural = _("Addtitional fields")
+
 
 class Group(ExtensibleModel):
     """Any kind of group of persons in a school, including, but not limited
@@ -217,7 +246,7 @@ class Group(ExtensibleModel):
     name = models.CharField(verbose_name=_("Long name of group"), max_length=255, unique=True)
     short_name = models.CharField(verbose_name=_("Short name of group"), max_length=255, unique=True, blank=True, null=True)
 
-    members = models.ManyToManyField("Person", related_name="member_of", blank=True)
+    members = models.ManyToManyField("Person", related_name="member_of", blank=True, through="PersonGroupThrough")
     owners = models.ManyToManyField("Person", related_name="owner_of", blank=True)
 
     parent_groups = models.ManyToManyField(
@@ -229,6 +258,7 @@ class Group(ExtensibleModel):
     )
 
     type = models.ForeignKey("GroupType", on_delete=models.CASCADE, related_name="type", verbose_name=_("Type of group"), null=True, blank=True)
+    additional_fields = models.ManyToManyField(AdditionalField)
 
 
     def get_absolute_url(self) -> str:
@@ -256,6 +286,19 @@ class Group(ExtensibleModel):
         dj_group.save()
 
 
+class PersonGroupThrough(ExtensibleModel):
+    group = models.ForeignKey(Group, on_delete=models.CASCADE)
+    person = models.ForeignKey(Person, on_delete=models.CASCADE)
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        for field in self.group.additional_fields:
+            field_class = getattr(jsonstore, field.get_field_type_display())
+            field_name = slugify(field.title).replace("-", "_")
+            field_instance = field_class(verbose_name=field.title)
+            setattr(self, field_name, field_instance)
+
 class Activity(ExtensibleModel):
     user = models.ForeignKey("Person", on_delete=models.CASCADE, related_name="activities")