diff --git a/AKDashboard/templates/AKDashboard/dashboard_row.html b/AKDashboard/templates/AKDashboard/dashboard_row.html
index 36c1af1e08a44c32890061bdce9df9427869ef80..001d8b6f336cfd520b8744ccf12c8d71b9bfe933 100644
--- a/AKDashboard/templates/AKDashboard/dashboard_row.html
+++ b/AKDashboard/templates/AKDashboard/dashboard_row.html
@@ -64,13 +64,15 @@
         </div>
     </a>
     {% if 'AKPreferencePoll'|check_app_installed and event.active %}
-        <a class="dashboard-box btn btn-primary"
-           href="{% url 'poll:poll' event_slug=event.slug %}">
-            <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
-                <span class="fa fa-poll"></span>
-                <span class='text'>{% trans 'AK Preferences' %}</span>
-            </div>
-        </a>
+        {% if not event.poll_hidden or user.is_staff %}
+            <a class="dashboard-box btn btn-primary"
+            href="{% url 'poll:poll' event_slug=event.slug %}">
+                <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
+                    <span class="fa fa-poll"></span>
+                    <span class='text'>{% trans 'AK Preferences' %}</span>
+                </div>
+            </a>
+        {% endif %}
     {% endif %}
     {% for button in event.dashboardbutton_set.all %}
         <a class="dashboard-box btn btn-{{ button.get_color_display }}"
diff --git a/AKDashboard/tests.py b/AKDashboard/tests.py
index 59328adf517d22a1793df63f67782254b0958f94..a45a7e22c2d1fe93dc1624667185d1b07009837a 100644
--- a/AKDashboard/tests.py
+++ b/AKDashboard/tests.py
@@ -29,6 +29,7 @@ class DashboardTests(TestCase):
                 end=now(),
                 active=True,
                 plan_hidden=False,
+                poll_hidden=False,
         )
         cls.default_category = AKCategory.objects.create(
                 name="Test Category",
@@ -146,6 +147,26 @@ class DashboardTests(TestCase):
             self.assertContains(response, "Current AKs")
             self.assertContains(response, "AK Wall")
 
+    def test_poll_hidden(self):
+        """
+        Test visibility of poll buttons with regard to poll visibility status for a given event
+        """
+        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
+
+        if apps.is_installed('AKPreferencePoll'):
+            # Poll hidden? No buttons should show up
+            self.event.poll_hidden = True
+            self.event.save()
+            response = self.client.get(url_event_dashboard)
+            self.assertNotContains(response, "AK Preferences")
+
+            # Poll not hidden?
+            # Buttons to preference poll should be on the page
+            self.event.poll_hidden = False
+            self.event.save()
+            response = self.client.get(url_event_dashboard)
+            self.assertContains(response, "AK Preferences")
+
     def test_dashboard_buttons(self):
         """
         Make sure manually added buttons are displayed correctly
diff --git a/AKModel/admin.py b/AKModel/admin.py
index 0b17c5523d0e26263bd5487e72dd8b2796b6bdb6..8b7584f1328ff309655ddde1e18444273a106fdd 100644
--- a/AKModel/admin.py
+++ b/AKModel/admin.py
@@ -51,12 +51,16 @@ class EventAdmin(admin.ModelAdmin):
     wizard.
     """
     model = Event
-    list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden']
+    list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden', 'poll_hidden']
     list_filter = ['active']
     list_editable = ['active']
     ordering = ['-start']
-    readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at', 'toggle_plan_visibility']
-    actions = ['publish', 'unpublish']
+    readonly_fields = [
+        'status_url',
+        'plan_hidden', 'plan_published_at', 'toggle_plan_visibility',
+        'poll_hidden', 'poll_published_at', 'toggle_poll_visibility',
+    ]
+    actions = ['publish_plan', 'unpublish_plan', 'publish_poll', 'unpublish_poll']
 
     def add_view(self, request, form_url='', extra_context=None):
         # Override
@@ -115,13 +119,31 @@ class EventAdmin(admin.ModelAdmin):
             text = _('Unpublish plan')
         return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
 
+    @display(description=_("Toggle poll visibility"))
+    def toggle_poll_visibility(self, obj):
+        """
+        Define a read-only field to toggle the visibility of the preference poll of this event
+        This will choose from two different link targets/views depending on the current visibility status
+
+        :param obj: event to change the visibility of the plan for
+        :return: toggling link (HTML)
+        :rtype: str
+        """
+        if obj.poll_hidden:
+            url = f"{reverse_lazy('admin:poll-publish')}?pks={obj.pk}"
+            text = _('Publish preference poll')
+        else:
+            url = f"{reverse_lazy('admin:poll-unpublish')}?pks={obj.pk}"
+            text = _('Unpublish preference poll')
+        return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
+
     def get_form(self, request, obj=None, change=False, **kwargs):
         # Override (update) form rendering to make sure the timezone of the event is used
         timezone.activate(obj.timezone)
         return super().get_form(request, obj, change, **kwargs)
 
     @action(description=_('Publish plan'))
-    def publish(self, request, queryset):
+    def publish_plan(self, request, queryset):
         """
         Admin action to publish the plan
         """
@@ -129,7 +151,7 @@ class EventAdmin(admin.ModelAdmin):
         return HttpResponseRedirect(f"{reverse_lazy('admin:plan-publish')}?pks={','.join(str(pk) for pk in selected)}")
 
     @action(description=_('Unpublish plan'))
-    def unpublish(self, request, queryset):
+    def unpublish_plan(self, request, queryset):
         """
         Admin action to hide the plan
         """
@@ -137,6 +159,23 @@ class EventAdmin(admin.ModelAdmin):
         return HttpResponseRedirect(
             f"{reverse_lazy('admin:plan-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
 
+    @action(description=_('Publish preference poll'))
+    def publish_poll(self, request, queryset):
+        """
+        Admin action to publish the preference poll
+        """
+        selected = queryset.values_list('pk', flat=True)
+        return HttpResponseRedirect(f"{reverse_lazy('admin:poll-publish')}?pks={','.join(str(pk) for pk in selected)}")
+
+    @action(description=_('Unpublish preference poll'))
+    def unpublish_poll(self, request, queryset):
+        """
+        Admin action to hide the preference poll
+        """
+        selected = queryset.values_list('pk', flat=True)
+        return HttpResponseRedirect(
+            f"{reverse_lazy('admin:poll-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
+
 
 class PrepopulateWithNextActiveEventMixin:
     """
diff --git a/AKModel/forms.py b/AKModel/forms.py
index eb60a04071ba1267681176ccd32d7e79e6437492..7c655058e49d1886f0d2bbe80da907c934d7efda 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -38,9 +38,10 @@ class NewEventWizardStartForm(forms.ModelForm):
     """
     class Meta:
         model = Event
-        fields = ['name', 'slug', 'timezone', 'plan_hidden']
+        fields = ['name', 'slug', 'timezone', 'plan_hidden', 'poll_hidden']
         widgets = {
             'plan_hidden': forms.HiddenInput(),
+            'poll_hidden': forms.HiddenInput(),
         }
 
     # Special hidden field for wizard state handling
@@ -57,7 +58,7 @@ class NewEventWizardSettingsForm(forms.ModelForm):
     class Meta:
         model = Event
         fields = "__all__"
-        exclude = ['plan_published_at']
+        exclude = ['plan_published_at', 'poll_published_at']
         widgets = {
             'name': forms.HiddenInput(),
             'slug': forms.HiddenInput(),
@@ -69,6 +70,7 @@ class NewEventWizardSettingsForm(forms.ModelForm):
             'interest_end': DateTimeInput(),
             'reso_deadline': DateTimeInput(),
             'plan_hidden': forms.HiddenInput(),
+            'poll_hidden': forms.HiddenInput(),
         }
 
 
diff --git a/AKModel/migrations/0070_event_poll_hidden_event_poll_published_at.py b/AKModel/migrations/0070_event_poll_hidden_event_poll_published_at.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ca184058798a6623f9e99dc8a41803614d742a7
--- /dev/null
+++ b/AKModel/migrations/0070_event_poll_hidden_event_poll_published_at.py
@@ -0,0 +1,32 @@
+# Generated by Django 5.1.6 on 2025-04-04 11:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("AKModel", "0069_alter_akpreference_preference"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="event",
+            name="poll_hidden",
+            field=models.BooleanField(
+                default=True,
+                help_text="Hides preference poll for non-staff users",
+                verbose_name="Poll Hidden",
+            ),
+        ),
+        migrations.AddField(
+            model_name="event",
+            name="poll_published_at",
+            field=models.DateTimeField(
+                blank=True,
+                help_text="Timestamp at which the preference poll was published",
+                null=True,
+                verbose_name="Poll published at",
+            ),
+        ),
+    ]
diff --git a/AKModel/models.py b/AKModel/models.py
index f86d85ba1a9ebbe07f1c306aa31095393a76b532..a635c662e2d28b9d6fd4c51a9c56f2c79a82fc07 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -158,6 +158,12 @@ class Event(models.Model):
     plan_published_at = models.DateTimeField(verbose_name=_('Plan published at'), blank=True, null=True,
                                              help_text=_('Timestamp at which the plan was published'))
 
+    poll_hidden = models.BooleanField(verbose_name=_('Poll Hidden'),
+                                      help_text=_('Hides preference poll for non-staff users'),
+                                      default=True)
+    poll_published_at = models.DateTimeField(verbose_name=_('Poll published at'), blank=True, null=True,
+                                             help_text=_('Timestamp at which the preference poll was published'))
+
     base_url = models.URLField(verbose_name=_("Base URL"), help_text=_("Prefix for wiki link construction"), blank=True)
     wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50)
     default_slot = models.DecimalField(max_digits=4, decimal_places=2, default=2, verbose_name=_('Default Slot Length'),
diff --git a/AKModel/urls.py b/AKModel/urls.py
index 9c10340546b787c92cbd02cb2c3dbbeb6fe1ff94..522a3171c0aec495307ecb9edf7e7d76c87a2cc3 100644
--- a/AKModel/urls.py
+++ b/AKModel/urls.py
@@ -4,8 +4,8 @@ from django.urls import include, path
 from rest_framework.routers import DefaultRouter
 
 import AKModel.views.api
-from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
-    AKsByUserView, AKScheduleJSONImportView
+from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, PollPublishView, PollUnpublishView, \
+    DefaultSlotEditorView, AKsByUserView, AKScheduleJSONImportView
 from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKJSONExportView, AKWikiExportView, \
      AKMessageDeleteView
 from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
@@ -108,6 +108,8 @@ def get_admin_urls_event(admin_site):
         path('<slug:event_slug>/ak-slide-export/', admin_site.admin_view(ExportSlidesView.as_view()), name="ak_slide_export"),
         path('plan/publish/', admin_site.admin_view(PlanPublishView.as_view()), name="plan-publish"),
         path('plan/unpublish/', admin_site.admin_view(PlanUnpublishView.as_view()), name="plan-unpublish"),
+        path('poll/publish/', admin_site.admin_view(PollPublishView.as_view()), name="poll-publish"),
+        path('poll/unpublish/', admin_site.admin_view(PollUnpublishView.as_view()), name="poll-unpublish"),
         path('<slug:event_slug>/defaultSlots/', admin_site.admin_view(DefaultSlotEditorView.as_view()),
              name="default-slots-editor"),
         path('<slug:event_slug>/importRooms/', admin_site.admin_view(RoomBatchCreationView.as_view()),
diff --git a/AKModel/views/manage.py b/AKModel/views/manage.py
index 1a7f1930b079edc4061ac5bc0a145466640bd574..fa88adb3cb053a6e7aefda1a2ca5bc662d6a6598 100644
--- a/AKModel/views/manage.py
+++ b/AKModel/views/manage.py
@@ -159,6 +159,31 @@ class PlanUnpublishView(IntermediateAdminActionView):
         self.entities.update(plan_published_at=None, plan_hidden=True)
 
 
+class PollPublishView(IntermediateAdminActionView):
+    """
+    Admin action view: Publish the preference poll of one or multitple event(s)
+    """
+    title = _('Publish preference poll')
+    model = Event
+    confirmation_message = _('Publish the plan(s) of:')
+    success_message = _('Preference poll published')
+
+    def action(self, form):
+        self.entities.update(poll_published_at=Now(), poll_hidden=False)
+
+
+class PollUnpublishView(IntermediateAdminActionView):
+    """
+    Admin action view: Unpublish the preference poll of one or multitple event(s)
+    """
+    title = _('Unpublish preference poll')
+    model = Event
+    confirmation_message = _('Unpublish the preference poll(s) of:')
+    success_message = _('Preference poll unpublished')
+
+    def action(self, form):
+        self.entities.update(poll_published_at=None, poll_hidden=True)
+
 class DefaultSlotEditorView(EventSlugMixin, IntermediateAdminView):
     """
     Admin view: Allow to edit the default slots of an event
diff --git a/AKPreferencePoll/tests.py b/AKPreferencePoll/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..14d51e65dae617c326acede6802641c062c770d7
--- /dev/null
+++ b/AKPreferencePoll/tests.py
@@ -0,0 +1,40 @@
+from django.urls import reverse
+from django.test import TestCase
+
+from AKModel.models import Event
+from AKModel.tests.test_views import BasicViewTests
+
+class PollViewTests(BasicViewTests, TestCase):
+    """
+    Tests for AKPreferencePoll
+    """
+    fixtures = ['model.json']
+    APP_NAME = 'poll'
+
+    def test_poll_redirect(self):
+        """
+        Test: Make sure that user is redirected from poll to dashboard when poll is hidden
+        """
+        event = Event.objects.get(slug='kif42')
+        _, url_poll = self._name_and_url(('poll', {'event_slug': event.slug}))
+        url_dashboard = reverse("dashboard:dashboard_event", kwargs={"slug": event.slug})
+
+        event.poll_hidden = True
+        event.save()
+
+        self.client.logout()
+        response = self.client.get(url_poll)
+
+        self.assertRedirects(response, url_dashboard,
+                             msg_prefix=f"Redirect away from poll not working ({url_poll} -> {url_dashboard})")
+
+        self.client.force_login(self.staff_user)
+        response = self.client.get(url_poll)
+
+        self.assertEqual(
+            response.status_code,
+            200,
+            msg=f"{url_poll} broken",
+        )
+
+        self.assertTemplateUsed(response, "AKPreferencePoll/poll.html", msg_prefix="Poll is not visible for staff user")
diff --git a/AKPreferencePoll/views.py b/AKPreferencePoll/views.py
index 8f36c4c220810c9370a1d8297acb5c09c87733c2..70ac8b43cecbe13a9616af5fb99b99ad712d80f3 100644
--- a/AKPreferencePoll/views.py
+++ b/AKPreferencePoll/views.py
@@ -1,7 +1,7 @@
 from django import forms
 from django.contrib import messages
 from django.contrib.messages.views import SuccessMessageMixin
-from django.http import HttpResponseRedirect
+from django.shortcuts import redirect
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import FormView
@@ -39,6 +39,13 @@ class PreferencePollCreateView(EventSlugMixin, SuccessMessageMixin, FormView):
             extra=0,
         )
 
+    def get(self, request, *args, **kwargs):
+        s = super().get(request, *args, **kwargs)
+        # Don't show preference form when event is not active or poll is hidden -> redirect to dashboard
+        if not self.event.active or (self.event.poll_hidden and not request.user.is_staff):
+            return redirect(self.get_success_url())
+        return s
+
     def get_success_url(self):
         return reverse_lazy(
             "dashboard:dashboard_event", kwargs={"slug": self.event.slug}
@@ -96,4 +103,4 @@ class PreferencePollCreateView(EventSlugMixin, SuccessMessageMixin, FormView):
         success_message = self.get_success_message(participant_form.cleaned_data)
         if success_message:
             messages.success(self.request, success_message)
-        return HttpResponseRedirect(self.get_success_url())
+        return redirect(self.get_success_url())