diff --git a/extensions/links.py b/extensions/links.py
index a3fb160bbc3d46efe4bfb93ca9175677756644a5..47fde02cbb93f0c81b45569d645f04fcfdaf1a99 100644
--- a/extensions/links.py
+++ b/extensions/links.py
@@ -1,80 +1,71 @@
-import json
-
 import discord
 from discord import app_commands, Interaction
 from discord.ext import commands
 
+import models
+from modals.link_modal import LinkModal, LinkCategoryModal
+
 
 @app_commands.guild_only()
 class Links(commands.GroupCog, name="links", description="Linkverwaltung für Kanäle."):
     def __init__(self, bot):
         self.bot = bot
-        self.links = {}
-        self.links_file = "data/links.json"
-        self.load_links()
-
-    def load_links(self):
-        links_file = open(self.links_file, 'r')
-        self.links = json.load(links_file)
-
-    def save_links(self):
-        links_file = open(self.links_file, 'w')
-        json.dump(self.links, links_file)
-
-    @app_commands.command(name="list", description="Liste Links für diesen Kanal auf.")
-    @app_commands.describe(topic="Zeige nur Links für dieses Thema an.",
-                           public="Zeige die Ausgabe des Commands öffentlich, für alle Mitglieder sichtbar.")
-    async def cmd_list(self, interaction: Interaction, topic: str = None, public: bool = False):
+
+    @app_commands.command(name="show", description="Zeige Links für diesen Kanal an.")
+    @app_commands.describe(category="Zeige nur Links für diese Kategorie an.", public="Zeige die Linkliste für alle.")
+    async def cmd_show(self, interaction: Interaction, category: str = None, public: bool = False):
         await interaction.response.defer(ephemeral=not public)
 
-        if channel_links := self.links.get(str(interaction.channel_id)):
-            embed = discord.Embed(title=f"Folgende Links sind in diesem Channel hinterlegt:\n")
-            if topic:
-                topic = topic.lower()
-                if topic_links := channel_links.get(topic):
-                    value = f""
-                    for title, link in topic_links.items():
-                        value += f"- [{title}]({link})\n"
-                    embed.add_field(name=topic.capitalize(), value=value, inline=False)
-                    await interaction.edit_original_response(embed=embed)
-                else:
-                    await interaction.edit_original_response(
-                        content=f"Für das Thema `{topic}` sind in diesem Channel keine Links hinterlegt. Versuch es "
-                                f"noch mal mit einem anderen Thema, oder lass dir mit `!links` alle Links in diesem "
-                                f"Channel ausgeben")
+        embed = discord.Embed(title=f"Links")
+        if not models.LinkCategory.has_links(interaction.channel_id):
+            embed.description = "Für diesen Channel sind noch keine Links hinterlegt."
+        if category and not models.LinkCategory.has_links(interaction.channel_id, category=category):
+            embed.description = f"Für die Kategorie `{category}` sind in diesem Channel keine Links hinterlegt. " \
+                                f"Versuch es noch mal mit einer anderen Kategorie, oder lass dir mit `/links show` " \
+                                f"alle Links in diesem Channel ausgeben."
+
+        for category in models.LinkCategory.get_categories(interaction.channel_id, category=category):
+            if category.links.count() > 0:
+                category.append_field(embed)
             else:
-                for topic, links in channel_links.items():
-                    value = f""
-                    for title, link in links.items():
-                        value += f"- [{title}]({link})\n"
-                    embed.add_field(name=topic.capitalize(), value=value, inline=False)
-                await interaction.edit_original_response(embed=embed)
-        else:
-            await interaction.edit_original_response(content="Für diesen Channel sind noch keine Links hinterlegt.")
-
-    @app_commands.command(name="add", description="Füge einen neuen Link hinzu.")
-    @app_commands.describe(topic="Thema, zu dem dieser Link hinzugefügt werden soll.",
-                           link="Link, der hinzugefügt werden soll.", title="Titel des Links.")
-    async def cmd_add(self, interaction: Interaction, topic: str, link: str, title: str):
-        await interaction.response.defer(ephemeral=True)
-        topic = topic.lower()
-        if not (channel_links := self.links.get(str(interaction.channel_id))):
-            self.links[str(interaction.channel_id)] = {}
-            channel_links = self.links.get(str(interaction.channel_id))
+                category.delete_instance()
 
-        if not (topic_links := channel_links.get(topic)):
-            channel_links[topic] = {}
-            topic_links = channel_links.get(topic)
+        await interaction.edit_original_response(embed=embed)
 
-        self.add_link(topic_links, link, title)
-        self.save_links()
-        await interaction.edit_original_response(content="Link hinzugefügt.")
+    @app_commands.command(name="add", description="Füge einen neuen Link hinzu.")
+    async def cmd_add(self, interaction: Interaction):
+        await interaction.response.send_modal(LinkModal())
 
-    def add_link(self, topic_links, link, title):
-        if topic_links.get(title):
-            self.add_link(topic_links, link, title + str(1))
+    @app_commands.command(name="edit-link", description="Einen bestehenden Link in der Liste bearbeiten.")
+    @app_commands.describe(category="Kategorie zu der der zu bearbeitende Link gehört.",
+                           title="Titel des zu bearbeitenden Links.")
+    async def cmd_edit_link(self, interaction: Interaction, category: str, title: str):
+        if db_category := models.LinkCategory.get_or_none(models.LinkCategory.channel == interaction.channel_id,
+                                                          models.LinkCategory.name == category):
+            if link := models.Link.get_or_none(models.Link.title == title, models.Link.category == db_category.id):
+                await interaction.response.send_modal(
+                    LinkModal(category=link.category.name, link_title=link.title, link=link.link, link_id=link.id,
+                              title="Link bearbeiten"))
+            else:
+                await interaction.response.send_message(content='Ich konnte den Link leider nicht finden.',
+                                                        ephemeral=True)
+        else:
+            await interaction.response.send_message(content='Ich konnte die Kategorie leider nicht finden.',
+                                                    ephemeral=True)
+
+    @app_commands.command(name="rename-category", description="Kategorie bearbeiten.")
+    @app_commands.describe(category="Zu bearbeitende Kategorie")
+    async def cmd_rename_category(self, interaction: Interaction, category: str):
+        if not models.LinkCategory.has_links(interaction.channel_id):
+            await interaction.response.send_message(content="Für diesen Channel sind noch keine Links hinterlegt.",
+                                                    ephemeral=True)
+            return
+
+        if db_category := models.LinkCategory.get_or_none(models.LinkCategory.channel == interaction.channel_id,
+                                                          models.LinkCategory.name == category):
+            await interaction.response.send_modal(LinkCategoryModal(db_category=db_category))
         else:
-            topic_links[title] = link
+            await interaction.response.send_message(content='Ich konnte das Thema leider nicht finden.', ephemeral=True)
 
     @app_commands.command(name="remove-link", description="Einen Link entfernen.")
     @app_commands.describe(topic="Theme zu dem der zu entfernende Link gehört.",
@@ -83,21 +74,19 @@ class Links(commands.GroupCog, name="links", description="Linkverwaltung für Ka
         await interaction.response.defer(ephemeral=True)
         topic = topic.lower()
 
-        if channel_links := self.links.get(str(interaction.channel_id)):
-            if topic_links := channel_links.get(topic):
-                if title in topic_links:
-                    topic_links.pop(title)
-                    if not topic_links:
-                        channel_links.pop(topic)
-                    await interaction.edit_original_response(content="Link entfernt.")
-                else:
-                    await interaction.edit_original_response(content='Ich konnte den Link leider nicht finden.')
+        if not models.LinkCategory.has_links(interaction.channel_id):
+            await interaction.edit_original_response(content="Für diesen Channel sind noch keine Links hinterlegt.")
+            return
+        if topic_entity := models.LinkCategory.get_or_none(models.LinkCategory.channel == interaction.channel_id,
+                                                           models.LinkCategory.name == topic):
+            if link := models.Link.get_or_none(models.Link.title == title, models.Link.topic == topic_entity.id):
+                link.delete_instance(recursive=True)
+                await interaction.edit_original_response(content=f'Link {title} entfernt')
             else:
-                await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
+                await interaction.edit_original_response(content='Ich konnte den Link leider nicht finden.')
         else:
-            await interaction.edit_original_response(content='Für diesen Channel sind keine Links hinterlegt.')
-
-        self.save_links()
+            await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
+            return
 
     @app_commands.command(name="remove-topic", description="Ein Thema mit allen zugehörigen Links entfernen.")
     @app_commands.describe(topic="Zu entfernendes Thema.")
@@ -105,67 +94,16 @@ class Links(commands.GroupCog, name="links", description="Linkverwaltung für Ka
         await interaction.response.defer(ephemeral=True)
         topic = topic.lower()
 
-        if channel_links := self.links.get(str(interaction.channel_id)):
-            if channel_links.get(topic):
-                channel_links.pop(topic)
-                await interaction.edit_original_response(content="Thema entfernt")
-            else:
-                await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
-        else:
-            await interaction.edit_original_response(content='Für diesen Channel sind keine Links hinterlegt.')
-
-        self.save_links()
-
-    @app_commands.command(name="edit-link", description="Einen bestehenden Link in der Liste bearbeiten.")
-    @app_commands.describe(topic="Thema zu dem der zu bearbeitende Link gehört.",
-                           title="Titel des zu bearbeitenden Links.", new_title="Neuer Titel des Links.",
-                           new_topic="Neues Thema des Links.", new_link="Neuer Link.")
-    async def cmd_edit_link(self, interaction: Interaction, topic: str, title: str, new_title: str,
-                            new_topic: str = None, new_link: str = None):
-        await interaction.response.defer(ephemeral=True)
-        topic = topic.lower()
-        new_topic = new_topic.lower() if new_topic else topic
-
-        if channel_links := self.links.get(str(interaction.channel_id)):
-            if topic_links := channel_links.get(topic):
-                if topic_links.get(title):
-                    new_link = new_link if new_link else topic_links.get(title)
-                    del topic_links[title]
-                else:
-                    await interaction.edit_original_response(content='Ich konnte den Link leider nicht finden.')
-                    return
-            else:
-                await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
-                return
-            new_title = new_title if new_title else title
-            if topic_links := channel_links.get(new_topic):
-                topic_links[new_title] = new_link
-            else:
-                channel_links[new_topic] = {new_title: new_link}
-            await interaction.edit_original_response(content="Link erfolgreich editiert.")
-        else:
-            await interaction.edit_original_response(content='Für diesen Channel sind keine Links hinterlegt.')
-
-        self.save_links()
-
-    @app_commands.command(name="edit-topic", description="Thema bearbeiten.")
-    @app_commands.describe(topic="Zu bearbeitendes Thema", new_topic="Neues Thema")
-    async def cmd_edit_topic(self, interaction: Interaction, topic: str, new_topic: str):
-        await interaction.response.defer(ephemeral=True)
-        topic = topic.lower()
-        new_topic = new_topic.lower()
-
-        if channel_links := self.links.get(str(interaction.channel_id)):
-            if topic_links := channel_links.get(topic):
-                channel_links[new_topic] = topic_links
-                del channel_links[topic]
-                await interaction.edit_original_response(content="Thema aktualisiert.")
-            else:
-                await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
+        if not models.LinkCategory.has_links(interaction.channel_id):
+            await interaction.edit_original_response(content="Für diesen Channel sind noch keine Links hinterlegt.")
+            return
+        if topic_entity := models.LinkCategory.get_or_none(models.LinkCategory.channel == interaction.channel_id,
+                                                           models.LinkCategory.name == topic):
+            topic_entity.delete_instance(recursive=True)
+            await interaction.edit_original_response(content=f'Thema {topic} mit allen zugehörigen Links entfernt')
         else:
-            await interaction.edit_original_response(content='Für diesen Channel sind keine Links hinterlegt.')
-
-        self.save_links()
+            await interaction.edit_original_response(content='Ich konnte das Thema leider nicht finden.')
+            return
 
 
 async def setup(bot: commands.Bot) -> None:
diff --git a/json_import.py b/json_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e8406f5aee760f14099385f5543a9537ddca520
--- /dev/null
+++ b/json_import.py
@@ -0,0 +1,22 @@
+import json
+
+import models
+
+
+def import_links(json_file: str) -> None:
+    file = open(json_file, mode="r")
+    links = json.load(file)
+
+    for channel, categories in links.items():
+        for category, links in categories.items():
+            category = category.capitalize()
+            db_category = models.LinkCategory.get_or_create(channel=int(channel), name=category)
+            for title, link in links.items():
+                models.Link.create(link=link, title=title, category=db_category[0].id)
+
+
+if __name__ == "__main__":
+    """
+    Make sure to create a database backup before you import data from json files.
+    """
+    # import_links("data/links.json")
diff --git a/modals/link_modal.py b/modals/link_modal.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5572725d4c6e9983676d445f0c8189c234cb32b
--- /dev/null
+++ b/modals/link_modal.py
@@ -0,0 +1,83 @@
+import re
+import traceback
+from typing import Optional
+
+import discord as discord
+from discord import ui
+from discord.utils import MISSING
+
+import models
+
+
+class InvalidLinkError(Exception):
+    pass
+
+
+class LinkDoesNotExistError(Exception):
+    pass
+
+
+class LinkModal(ui.Modal, title='Link hinzufügen'):
+    def __init__(self, *, category: str = None, link_title: str = None, link: str = None, link_id: int = None,
+                 title: str = MISSING, timeout: Optional[float] = None, custom_id: str = MISSING) -> None:
+        super().__init__(title=title, timeout=timeout, custom_id=custom_id)
+        self.category.default = category
+        self.link_title.default = link_title
+        self.link.default = link
+        self.link_id = link_id
+
+    category = ui.TextInput(label='Kategorie')
+    link_title = ui.TextInput(label='Titel')
+    link = ui.TextInput(label='Link')
+
+    def validate_link(self):
+        if not re.match("^https?://.+", self.link.value):
+            raise InvalidLinkError(f"`{self.link}` ist kein gültiger Link")
+
+    async def on_submit(self, interaction: discord.Interaction):
+        self.validate_link()
+        db_category = models.LinkCategory.get_or_create(channel=interaction.channel_id, name=self.category)
+
+        if self.link_id is None:
+            models.Link.create(link=self.link, title=self.link_title, category=db_category[0].id)
+            await interaction.response.send_message(content="Link erfolgreich hinzugefügt.", ephemeral=True)
+        else:
+            if link := models.Link.get_or_none(models.Link.id == self.link_id):
+                link_category = link.category
+                link.update(title=self.link_title, link=self.link, category=db_category[0].id).where(
+                    models.Link.id == link.id).execute()
+
+                if link_category.id != db_category[0].id and link.category.links.count() == 0:
+                    link_category.delete_instance()
+            else:
+                raise LinkDoesNotExistError(f"Der Link `{self.link_title}` existiert nicht.")
+
+            await interaction.response.send_message(content="Link erfolgreich bearbeitet.", ephemeral=True)
+
+    async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
+        if type(error) in [InvalidLinkError, LinkDoesNotExistError]:
+            await interaction.response.send_message(content=error, ephemeral=True)
+        else:
+            await interaction.response.send_message(content="Fehler beim Hinzufügen/Bearbeiten eines Links.",
+                                                    ephemeral=True)
+        traceback.print_exception(type(error), error, error.__traceback__)
+
+
+class LinkCategoryModal(ui.Modal, title='Kategorie umbenennen'):
+    def __init__(self, *, db_category: str = None, link_id: int = None,
+                 title: str = MISSING, timeout: Optional[float] = None, custom_id: str = MISSING) -> None:
+        super().__init__(title=title, timeout=timeout, custom_id=custom_id)
+        self.db_category = db_category
+        self.category.default = db_category.name
+        self.link_id = link_id
+
+    category = ui.TextInput(label='Kategorie')
+
+    async def on_submit(self, interaction: discord.Interaction):
+        self.db_category.update(name=self.category).where(models.LinkCategory.id == self.db_category.id).execute()
+        await interaction.response.send_message(content="Kategorie erfolgreich umbenannt.", ephemeral=True)
+
+    async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
+        await interaction.response.send_message(content=f"Fehler beim umbenennen der Kategorie `{self.category}`.",
+                                                ephemeral=True)
+        traceback.print_exception(type(error), error, error.__traceback__)
diff --git a/models.py b/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..71393bcaa14fb4bc67cdcd6fa78cac2efb806a7a
--- /dev/null
+++ b/models.py
@@ -0,0 +1,45 @@
+import discord
+from peewee import *
+from peewee import ModelSelect
+
+db = SqliteDatabase("db.sqlite3")
+
+
+class BaseModel(Model):
+    class Meta:
+        database = db
+        legacy_table_names = False
+
+
+class LinkCategory(BaseModel):
+    channel = IntegerField()
+    name = CharField()
+
+    @classmethod
+    def get_categories(cls, channel: int, category: str = None) -> ModelSelect:
+        categories: ModelSelect = cls.select().where(LinkCategory.channel == channel)
+        return categories.where(LinkCategory.name == category) if category else categories
+
+    @classmethod
+    def has_links(cls, channel: int, category: str = None) -> bool:
+        for category in cls.get_categories(channel, category=category):
+            if category.links.count() > 0:
+                return True
+
+        return False
+
+    def append_field(self, embed: discord.Embed) -> None:
+        value = ""
+        for link in self.links:
+            value += f"- [{link.title}]({link.link})\n"
+
+        embed.add_field(name=self.name, value=value, inline=False)
+
+
+class Link(BaseModel):
+    link = CharField()
+    title = CharField()
+    category = ForeignKeyField(LinkCategory, backref='links')
+
+
+db.create_tables([LinkCategory, Link], safe=True)
diff --git a/requirements.txt b/requirements.txt
index cbc5aa1fe5cf3dc9b6e21e2261e94f6af8286e5b..bd6ab03792daaaa080ad70bd01bfa23c60b9ede4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,11 +6,13 @@ beautifulsoup4==4.11.1
 certifi==2022.12.7
 cffi==1.15.1
 charset-normalizer==2.1.1
-discord.py==2.1.0
+discord==2.2.3
+discord.py==2.2.3
 emoji==2.2.0
 frozenlist==1.3.3
 idna==3.4
 multidict==6.0.4
+peewee==3.16.2
 pycparser==2.21
 PyNaCl==1.5.0
 python-dotenv==0.21.0
diff --git a/views/poll_view.py b/views/poll_view.py
index d6494d7a07c8d8a51e1a0a953c5bf4d007d0912f..390eb4e74658210a1d0438da2e8cc65d3c57ecaa 100644
--- a/views/poll_view.py
+++ b/views/poll_view.py
@@ -15,7 +15,7 @@ async def show_participants(interaction, poll, ephemeral):
         choice_msg = f"{choices[idx][0]} {choices[idx][1]} ({choices[idx][2]}):"
         choice_msg += "<@" if choices[idx][2] > 0 else ""
         choice_msg += ">, <@".join(participants)
-        choice_msg += ">\n" if choices[idx][2] > 0 else "\n"
+        choice_msg += ">\n" if choices[idx][2] > 0 else ""
         if len(msg) + len(choice_msg) >= utils.MAX_MESSAGE_LEN:
             await interaction.followup.send(msg, ephemeral=ephemeral)
             msg = choice_msg
@@ -110,7 +110,7 @@ class PollDropdown(discord.ui.Select):
         for idx, choice in enumerate(self.poll["choices"]):
             choice[2] = choices[idx]
 
-        await self.message.edit(embed=self.polls.get_embed(self.poll), view=PollView(self.polls))
+        await self.message.edit(embed=self.polls.get_embed(self.poll), view=PollView(self.poll))
         self.polls.save()
 
     def is_default(self, participant, idx):