diff --git a/Gemfile b/Gemfile
index 0101fe068..45b631ba2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -39,6 +39,7 @@ gem 'jwt'
 gem 'stimulus-rails'
 gem 'jsbundling-rails'
 gem 'pdf-reader'
+gem "acts_as_list"
 gem 'grover'
 
 # Monitoring
diff --git a/Gemfile.lock b/Gemfile.lock
index 4362ba58e..51dbba4a4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,6 +76,8 @@ GEM
       minitest (>= 5.1)
       mutex_m
       tzinfo (~> 2.0)
+    acts_as_list (1.1.0)
+      activerecord (>= 4.2)
     addressable (2.8.6)
       public_suffix (>= 2.0.2, < 6.0)
     afm (0.2.2)
@@ -508,6 +510,7 @@ PLATFORMS
   x86_64-linux
 
 DEPENDENCIES
+  acts_as_list
   annotate
   bootsnap (>= 1.4.4)
   brakeman
diff --git a/app/components/common/icon_component.html.erb b/app/components/common/icon_component.html.erb
index 623f6628f..f68c46e36 100644
--- a/app/components/common/icon_component.html.erb
+++ b/app/components/common/icon_component.html.erb
@@ -1,3 +1,9 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="<%= @stroke_width %>" stroke="currentColor" class="shrink-0 <%= @classes.present? ? @classes : "w-6 h-6" %>">
-  <path stroke-linecap="round" stroke-linejoin="round" d="<%= @svg %>" />
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="<%= @stroke_width %>" stroke="currentColor" class="shrink-0 <%= @classes.presence || "w-6 h-6" %>">
+  <% if @svg.class == Array %>
+    <% @svg.each do |svg| %>
+      <path stroke-linecap="round" stroke-linejoin="round" d="<%= svg %>" />
+    <% end %>
+  <% else %>
+    <path stroke-linecap="round" stroke-linejoin="round" d="<%= @svg %>" />
+  <% end %>
 </svg>
diff --git a/app/components/common/icon_component.rb b/app/components/common/icon_component.rb
index a4d509ca3..e02049fba 100644
--- a/app/components/common/icon_component.rb
+++ b/app/components/common/icon_component.rb
@@ -16,6 +16,7 @@ class IconComponent < ViewComponent::Base
       "bell-slash" => "M9.143 17.082a24.248 24.248 0 003.844.148m-3.844-.148a23.856 23.856 0 01-5.455-1.31 8.964 8.964 0 002.3-5.542m3.155 6.852a3 3 0 005.667 1.97m1.965-2.277L21 21m-4.225-4.225a23.81 23.81 0 003.536-1.003A8.967 8.967 0 0118 9.75V9A6 6 0 006.53 6.53m10.245 10.245L6.53 6.53M3 3l3.53 3.53",
       "chat-bubble-left-right" => "M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155",
       "chevron-down" => "m19.5 8.25-7.5 7.5-7.5-7.5",
+      "chevron-up" => "M4.5 15.75l7.5-7.5 7.5 7.5",
       "archive-box" => "M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z",
       "archive-box-x-mark" => "m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5m6 4.125 2.25 2.25m0 0 2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25 2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z",
       "trash" => "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0",
@@ -23,6 +24,8 @@ class IconComponent < ViewComponent::Base
       "exclamation-triangle" => "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z",
       "clock" => "M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
       "cloud-arrow-down" => "M12 9.75v6.75m0 0-3-3m3 3 3-3m-8.25 6a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z",
+      "tag" => ["M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z", "M6 6h.008v.008H6V6Z"],
+      "bookmark" => "M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z",
       "paper-airplane" => "M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5",
       "document-arrow-down" => "M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z",
       "document-text" => "M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z",
diff --git a/app/components/filters/filter_form_component.html.erb b/app/components/filters/filter_form_component.html.erb
index 76b3d5a19..d0782ce07 100644
--- a/app/components/filters/filter_form_component.html.erb
+++ b/app/components/filters/filter_form_component.html.erb
@@ -10,15 +10,26 @@
           <%= render Common::CloseButtonComponent.new(link_to: filters_path) %>
         </div>
         <%= form_with model: @filter, data: { turbo_frame: "_top" } do |form| %>
+          <%= form.hidden_field :type %>
           <div class="flex flex-col justify-start items-start self-stretch w-96">
             <div class="flex justify-start items-center self-stretch gap-4 p-6">
               <div class="flex flex-col justify-start items-start self-stretch flex-grow overflow-hidden gap-2 rounded-md">
-                <%= form.text_field :name, placeholder: "Názov filtra", class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %>
+                <%= form.label :name, "Názov filtra" %>
+                <%= form.text_field :name, class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %>
               </div>
             </div>
+            <% if @filter.is_a? FulltextFilter %>
+              <div class="flex justify-start items-center self-stretch gap-4 p-6 border-t-0 border-r-0 border-b border-l-0 border-gray-200">
+                <div class="flex flex-col justify-start items-start self-stretch flex-grow overflow-hidden gap-2 rounded-md">
+                  <%= form.label :query, "Dopyt na vyhľadávanie" %>
+                  <%= form.text_field :query, class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %>
+                </div>
+              </div>
+            <% end %>
             <div class="flex justify-start items-center self-stretch gap-4 p-6 border-t-0 border-r-0 border-b border-l-0 border-gray-200">
               <div class="flex flex-col justify-start items-start self-stretch flex-grow overflow-hidden gap-2 rounded-md">
-                <%= form.text_field :query, placeholder: "Dopyt na vyhľadávanie", class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %>
+                <%= form.label :icon, "Ikona" %>
+                <%= form.select :icon, helpers.icon_select_options, {}, class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %>
               </div>
             </div>
           </div>
diff --git a/app/components/filters/filters_list_row_component.html.erb b/app/components/filters/filters_list_row_component.html.erb
index a83295cdd..c85612097 100644
--- a/app/components/filters/filters_list_row_component.html.erb
+++ b/app/components/filters/filters_list_row_component.html.erb
@@ -1,20 +1,28 @@
 <div class="self-stretch p-6 border-b border-gray-200 justify-start items-center gap-4 inline-flex">
   <div class="relative inline-flex items-center justify-center w-16 h-16 overflow-hidden bg-blue-600 rounded-full">
-    <span class="text-white text-2xl font-normal"><%= @filter.name[0] %></span>
+    <% if (@filter.icon || @filter.tag&.icon).presence %>
+      <%= render Common::IconComponent.new(@filter.icon || @filter.tag.icon, classes: "w-6 h-6 stroke-gray-300", stroke_width: 2) %>
+    <% else %>
+      <span class="text-white text-2xl font-normal"><%= @filter.name[0] %></span>
+    <% end %>
   </div>
   <div class="grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex">
     <div class="text-center text-gray-900 text-lg font-medium leading-loose">
       <%= render Common::InlineRenameComponent.new(name: @filter.name, model: @filter) %>
     </div>
     <div class="text-center text-gray-500 text-base font-normal leading-normal">
-      <%= @filter.query %>
+      <% if @filter.is_a? TagFilter %>
+        <%= render Common::TagComponent.new(@filter.tag) %>
+      <% else %>
+        <%= @filter.query %>
+      <% end %>
     </div>
   </div>
   <div class="justify-start items-start gap-2 flex">
     <%= link_to edit_filter_path(@filter), title: "Upraviť filter" do %>
       <%= render Common::EditButtonComponent.new %>
     <% end %>
-    <%= link_to filter_path(@filter), title: "Zmazať filter", data: { turbo_confirm: "Naozaj?", turbo_method: :delete } do %>
+    <%= button_to filter_path(@filter), title: "Zmazať filter", method: :delete, data: { turbo_confirm: "Naozaj chcete zmazať filter?" } do %>
       <%= render Common::DeleteButtonComponent.new %>
     <% end %>
   </div>
diff --git a/app/components/layout/filter_list_component.html.erb b/app/components/layout/filter_list_component.html.erb
index 0c3bfe5bf..c7f28080f 100644
--- a/app/components/layout/filter_list_component.html.erb
+++ b/app/components/layout/filter_list_component.html.erb
@@ -1,14 +1,30 @@
 <% if @filters.present? %>
-  <div class="flex flex-col justify-start items-start gap-2" data-test="filters">
-    <div class="flex justify-start items-start px-4 py-2">
-      <p class="text-sm text-center text-gray-400">Filtre</p>
-    </div>
+  <div
+    <% if @sortable %>
+    data-controller="sortable"
+    data-sortable-url-value="<%= sort_filters_path %>"
+    data-sortable-draggable-class=".item"
+    data-sortable-handle-class=".handle"
+    <% end %>
+    class="w-full flex flex-col justify-start items-start gap-2"
+    data-test="filters"
+    >
+    <% if @sortable %>
+      <%= link_to 'Sort', '', class: 'hidden', data: { turbo_method: :patch, sortable_target: 'submit', url: sort_filters_path } %>
+    <% end %>
+    <% if @label.present? %>
+      <div class="flex justify-start items-start px-4 py-2">
+        <p class="text-sm text-center text-gray-400"><%= @label %></p>
+      </div>
+    <% end %>
     <% @filters.each do |filter| %>
-      <% url = message_threads_path(q: filter.query) %>
-      <%= link_to url, class: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group w-72 flex gap-x-3 rounded-md p-2 px-4 text-sm leading-6 font-semibold data-[active=true]:bg-gray-50 data-[active=true]:text-indigo-600", data: { active: current_page?(url) } do %>
-        <%= render Icons::BookmarkComponent.new %>
-        <p class="truncate text-base font-medium"><%= filter.name %></p>
-      <% end %>
+      <%= render TW::SidebarMenuItemComponent.new(
+        name: filter.name,
+        url: filtered_message_threads_path(filter:),
+        icon: icon_for(filter),
+        variant: :light,
+        classes: "item #{!@sortable ? 'pl-4' : ''}",
+      ) %>
     <% end %>
   </div>
-<% end %>
+<% end %>
\ No newline at end of file
diff --git a/app/components/layout/filter_list_component.rb b/app/components/layout/filter_list_component.rb
index d3ba8594b..a77931d34 100644
--- a/app/components/layout/filter_list_component.rb
+++ b/app/components/layout/filter_list_component.rb
@@ -1,5 +1,20 @@
 class Layout::FilterListComponent < ViewComponent::Base
-  def initialize(filters:)
+  include MessageThreadHelper
+
+  def initialize(label: nil, filters:, sortable: false)
+    @label = label
     @filters = filters
+    @sortable = sortable
+  end
+
+  def icon_for(filter)
+    return Common::IconComponent.new(filter.icon) if filter.icon.present?
+
+    if filter.tag.present?
+      return Common::IconComponent.new(filter.tag.icon) if filter.tag.icon.present?
+      return Icons::TagComponent.new
+    end
+
+    Icons::BookmarkComponent.new
   end
 end
diff --git a/app/components/message_options_component.html.erb b/app/components/message_options_component.html.erb
index 06127ebd5..0a36d3c43 100644
--- a/app/components/message_options_component.html.erb
+++ b/app/components/message_options_component.html.erb
@@ -32,12 +32,12 @@
       <% if @mode == :thread_view && @message.collapsible? %>
         <% if @message.collapsed %>
           <%= button_to message_path(@message), params: { collapsed: false }, method: :patch, class: 'whitespace-nowrap flex gap-3', role: 'menu-item', tabindex: -1 do %>
-              <%= render Icons::ChevronDownComponent.new(css_classes: "w-5 h-5") %>
+              <%= render Common::IconComponent.new("chevron-down", classes: "w-5 h-5") %>
               Vždy zobrazovať
           <% end %>
         <% else %>
           <%= button_to message_path(@message), params: { collapsed: true }, method: :patch, class: 'whitespace-nowrap flex gap-3', role: 'menu-item', tabindex: -1 do %>
-            <%= render Icons::ChevronUpComponent.new(css_classes: "w-5 h-5") %>
+            <%= render Common::IconComponent.new("chevron-up", classes: "w-5 h-5") %>
             Zbaliť a už nerozbaľovať
           <% end %>
         <% end %>
diff --git a/app/components/message_threads_bulk_actions_component.html.erb b/app/components/message_threads_bulk_actions_component.html.erb
index cff6cc728..b73c25a4a 100644
--- a/app/components/message_threads_bulk_actions_component.html.erb
+++ b/app/components/message_threads_bulk_actions_component.html.erb
@@ -1,8 +1,7 @@
 <%= tag.turbo_frame id: "bulk_actions" do %>
   <div class="flex justify-stretch items-center gap-4 px-4 lg:py-4 border-t-0 border-r-0 border-b border-l-0 border-gray-200 min-h-[3rem] sm:min-h-[5rem]">
     <%= check_box_tag("", nil, false, { class: "hidden sm:block h-4 w-4 rounded border-gray-300 text-blue-500 focus:ring-0", type: "checkbox", id: "checkbox-all", data: { action: "all-checkboxes#toggle", "all-checkboxes-target": "checkbox" } }) %>
-
-    <span class="grow sm:text-xl text-base font-semibold text-left text-gray-900"><%= @ids.present? ? t(:selected_message, count: @ids.count) : "Správy v schránke" %>
+    <span class="grow sm:text-xl text-base font-semibold text-left text-gray-900"><%= title %>
       <% if @filter %>
         <% if @filter_subscription %>
           <%= link_to edit_filter_filter_subscription_path(@filter, @filter_subscription), title: "Nastaviť notifikácie", "data-turbo-frame": :modal, form_class: "inline" do %>
@@ -25,7 +24,7 @@
           <div>
             <button type="button" data-action="dropdown#toggle click@window->dropdown#hide" class="inline-flex w-full justify-center items-center gap-x-1.5 rounded-md bg-white px-2 py-1 sm:px-3 sm:py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" id="menu-button" aria-expanded="true" aria-haspopup="true">
               Hromadné akcie
-              <%= render Icons::ChevronDownComponent.new(css_classes: "w-4 h-4 text-gray-400", stroke_width: 2) %>
+              <%= render Common::IconComponent.new('chevron-down', classes: "w-4 h-4 text-gray-400", stroke_width: 2) %>
             </button>
           </div>
 
@@ -127,7 +126,7 @@
       <div>
         <button type="button" data-action="dropdown#toggle click@window->dropdown#hide" class="inline-flex w-full justify-center items-center gap-x-1.5 rounded-md bg-white px-2 py-1 sm:px-3 sm:py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" id="menu-button" aria-expanded="true" aria-haspopup="true">
           Vytvoriť novú správu
-          <%= render Icons::ChevronDownComponent.new(css_classes: "w-4 h-4 text-gray-400", stroke_width: 2) %>
+          <%= render Common::IconComponent.new('chevron-down', classes: "w-4 h-4 text-gray-400", stroke_width: 2) %>
         </button>
       </div>
 
diff --git a/app/components/message_threads_bulk_actions_component.rb b/app/components/message_threads_bulk_actions_component.rb
index 90f5e523b..7b847f216 100644
--- a/app/components/message_threads_bulk_actions_component.rb
+++ b/app/components/message_threads_bulk_actions_component.rb
@@ -1,8 +1,18 @@
 class MessageThreadsBulkActionsComponent < ViewComponent::Base
-  def initialize(ids:, signable:, filter: nil, filter_subscription: nil)
+  def initialize(ids: nil, signable:, filter: nil, query: nil, filter_subscription: nil)
     @ids = ids
     @signable = signable
     @filter = filter
+    @query = query
     @filter_subscription = filter_subscription
   end
+
+  def title
+    return t(:selected_message, count: @ids.count) if @ids.present?
+    return @filter.name if @filter.present? && @filter.is_a?(EverythingFilter)
+    return "Správy z filtra '#{@filter.name}'" if @filter.present?
+    return "Hľadaný výraz '#{@query}'" if @query.present?
+
+    "Správy v schránke"
+  end
 end
diff --git a/app/components/message_threads_table_component.html.erb b/app/components/message_threads_table_component.html.erb
index dd789a1c4..3659a8b5b 100644
--- a/app/components/message_threads_table_component.html.erb
+++ b/app/components/message_threads_table_component.html.erb
@@ -1,6 +1,6 @@
 <div class="flex flex-col justify-stretch items-stretch gap-4 sm:p-4">
   <div class="flex flex-col justify-stretch items-stretch sm:rounded-md bg-white sm:border sm:border-gray-200" data-controller="form all-checkboxes">
-    <%= render MessageThreadsBulkActionsComponent.new(ids: [], filter: @filter, filter_subscription: @filter_subscription, signable: Current.user.signer?) %>
+    <%= render MessageThreadsBulkActionsComponent.new(ids: [], filter:, query:, filter_subscription:, signable: Current.user.signer?) %>
     <%= form_with url: bulk_actions_message_threads_path, data: { "form-target": "form", "all-checkboxes-target": "form" } do %>
       <ul role="list" id="message_threads" data-controller="visited-links" class="divide-y divide-gray-100">
         <% message_threads.each do |message_thread| %>
diff --git a/app/components/message_threads_table_component.rb b/app/components/message_threads_table_component.rb
index 5254c5708..c8114d03f 100644
--- a/app/components/message_threads_table_component.rb
+++ b/app/components/message_threads_table_component.rb
@@ -2,8 +2,11 @@ class MessageThreadsTableComponent < ViewComponent::Base
   renders_many :message_threads
   renders_one :next_page_area
 
-  def initialize(filter:, filter_subscription:)
+  attr_reader :filter, :query, :filter_subscription
+
+  def initialize(filter: nil, query: nil, filter_subscription:)
     @filter = filter
+    @query = query
     @filter_subscription = filter_subscription
   end
 end
diff --git a/app/components/settings/user_filter_visibilities/list_component.html.erb b/app/components/settings/user_filter_visibilities/list_component.html.erb
new file mode 100644
index 000000000..86362c676
--- /dev/null
+++ b/app/components/settings/user_filter_visibilities/list_component.html.erb
@@ -0,0 +1,11 @@
+<div class="w-full p-4 flex-col justify-start items-start gap-4 inline-flex">
+  <div class="self-stretch bg-white rounded-md border border-gray-200 flex-col justify-start items-start flex">
+    <div class="flex-col self-stretch p-6 border-b border-gray-200 justify-start items-start gap-4 inline-flex">
+      <div class="grow shrink basis-0 text-gray-900 text-xl font-semibold leading-[35px]">Filtre</div>
+      Nastavte si osobnú preferenciu viditeľnosti filtrov v ľavom menu
+    </div>
+    <div class="self-stretch flex-col justify-start items-start flex">
+      <%= render Settings::UserFilterVisibilities::ListRowComponent.with_collection(@visibilities) %>
+    </div>
+  </div>
+</div>
diff --git a/app/components/settings/user_filter_visibilities/list_component.rb b/app/components/settings/user_filter_visibilities/list_component.rb
new file mode 100644
index 000000000..b21ad237e
--- /dev/null
+++ b/app/components/settings/user_filter_visibilities/list_component.rb
@@ -0,0 +1,5 @@
+class Settings::UserFilterVisibilities::ListComponent < ViewComponent::Base
+  def initialize(visibilities)
+    @visibilities = visibilities
+  end
+end
diff --git a/app/components/settings/user_filter_visibilities/list_row_component.html.erb b/app/components/settings/user_filter_visibilities/list_row_component.html.erb
new file mode 100644
index 000000000..b60639d1a
--- /dev/null
+++ b/app/components/settings/user_filter_visibilities/list_row_component.html.erb
@@ -0,0 +1,51 @@
+<div class="self-stretch p-6 border-b border-gray-200 items-center gap-4 inline-flex">
+  <div class="grow shrink basis-0 gap-1">
+    <div class="text-gray-900 text-lg font-medium leading-loose w-fit">
+      <%= @filter.name %>
+    </div>
+  </div>
+  <% if @visibility.new_record? %>
+    <%= form_with model: [:settings, Current.user.user_filter_visibilities.new(filter: @filter)], method: :post do |form| %>
+      <%= form.hidden_field :filter_id, value: @filter.id %>
+      <%= form.hidden_field :visible, value: !@visibility.visible %>
+      <%= form.button class: class_names('relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2', { "bg-gray-200": @visibility.hidden, "bg-indigo-600": @visibility.visible }), role: :switch, aria: { checked: @visibility.hidden.to_s } do %>
+        <span class="sr-only">Use setting</span>
+        <span
+          aria-hidden="true"
+          class="<%= class_names(
+            'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-2',
+            { 'translate-x-0': @visibility.hidden, 'translate-x-5': @visibility.visible }
+          ) %>"
+        ></span>
+      <% end %>
+    <% end %>
+  <% else %>
+    <%= form_with model: [:settings, @visibility], method: :patch do |form| %>
+      <%= form.hidden_field :visible, value: !@visibility.visible %>
+      <%= form.button class: class_names('relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2', { "bg-gray-200": @visibility.hidden, "bg-indigo-600": @visibility.visible }), role: :switch, aria: { checked: @visibility.hidden.to_s } do %>
+        <span class="sr-only">Use setting</span>
+        <span
+          aria-hidden="true"
+          class="<%= class_names(
+            'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
+            { 'translate-x-0': @visibility.hidden, 'translate-x-5': @visibility.visible }
+          ) %>"
+        ></span>
+      <% end %>
+    <% end %>
+    <%= form_with model: [:move_higher, :settings, @visibility], method: :post do |form| %>
+      <%= form.button do %>
+        <div class="flex justify-center items-center flex-grow-0 flex-shrink-0 relative overflow-hidden gap-2.5 px-3.5 py-2.5 rounded-md bg-white border border-gray-300 hover:bg-gray-100">
+          <%= render Common::IconComponent.new("chevron-up") %>
+        </div>
+      <% end %>
+    <% end %>
+    <%= form_with model: [:move_lower, :settings, @visibility], method: :post do |form| %>
+      <%= form.button do %>
+         <div class="flex justify-center items-center flex-grow-0 flex-shrink-0 relative overflow-hidden gap-2.5 px-3.5 py-2.5 rounded-md bg-white border border-gray-300 hover:bg-gray-100">
+          <%= render Common::IconComponent.new("chevron-down") %>
+        </div>
+      <% end %>
+    <% end %>
+  <% end %>
+</div>
diff --git a/app/components/settings/user_filter_visibilities/list_row_component.rb b/app/components/settings/user_filter_visibilities/list_row_component.rb
new file mode 100644
index 000000000..99cf8ca6e
--- /dev/null
+++ b/app/components/settings/user_filter_visibilities/list_row_component.rb
@@ -0,0 +1,7 @@
+class Settings::UserFilterVisibilities::ListRowComponent < ViewComponent::Base
+  with_collection_parameter :visibility
+  def initialize(visibility:)
+    @visibility = visibility
+    @filter = visibility.filter
+  end
+end
diff --git a/app/components/t_w/create_filter_component.html.erb b/app/components/t_w/create_filter_component.html.erb
index 60fe2fa46..23ce9a2cc 100644
--- a/app/components/t_w/create_filter_component.html.erb
+++ b/app/components/t_w/create_filter_component.html.erb
@@ -11,6 +11,7 @@
         </div>
       </div>
 
+      <%= f.hidden_field :type %>
       <%= f.hidden_field :query %>
 
       <%= render Common::ModalActionsComponent.new do |actions| %>
diff --git a/app/components/t_w/sidebar_menu_item_component.html.erb b/app/components/t_w/sidebar_menu_item_component.html.erb
index a5888f916..d68d22672 100644
--- a/app/components/t_w/sidebar_menu_item_component.html.erb
+++ b/app/components/t_w/sidebar_menu_item_component.html.erb
@@ -1,6 +1,19 @@
-<%= link_to @url, class: "items-center text-gray-700 hover:text-indigo-600 hover:bg-gray-50 w-full group flex gap-x-3 rounded-md p-4 leading-6 font-semibold data-[active=true]:bg-gray-50 data-[active=true]:text-indigo-600", data: { active: current_page?(@url, check_parameters: true) } do %>
-  <% if @icon_component %>
-    <%= render @icon_component %>
+<div
+  class="flex items-center text-gray-700 hover:text-indigo-600 hover:bg-gray-50 w-full group rounded-md leading-6 font-semibold data-[active=true]:bg-gray-50 data-[active=true]:text-indigo-600 <%= @classes %>"
+  <%= tag.attributes(data: { active: current_page?(@url, check_parameters: true) } ) %>
+>
+  <% if leading %>
+    <%= leading %>
   <% end %>
-  <%= @name %>
-<% end %>
+  <%= link_to \
+        @url,
+        class: "w-full flex gap-x-3 py-4 grow" do %>
+    <% if @icon_component %>
+      <%= render @icon_component %>
+    <% end %>
+    <span class="truncate text-base <%= light? ? 'font-medium' : '' %>"><%= @name %></span>
+  <% end %>
+  <% if trailing %>
+    <%= trailing %>
+  <% end %>
+</div>
diff --git a/app/components/t_w/sidebar_menu_item_component.rb b/app/components/t_w/sidebar_menu_item_component.rb
index 868c2e347..4a05ee7c5 100644
--- a/app/components/t_w/sidebar_menu_item_component.rb
+++ b/app/components/t_w/sidebar_menu_item_component.rb
@@ -1,7 +1,20 @@
 class TW::SidebarMenuItemComponent < ViewComponent::Base
-  def initialize(name:, url:, icon:)
+  renders_one :leading
+  renders_one :trailing
+
+  def initialize(name:, url:, icon:, variant: :regular, classes: 'pl-4')
     @name = name
     @url = url
     @icon_component = icon
+    @variant = variant
+    @classes = classes
+  end
+
+  def regular?
+    @variant == :regular
+  end
+
+  def light?
+    @variant == :light
   end
 end
diff --git a/app/components/t_w/top_navigation_component.html.erb b/app/components/t_w/top_navigation_component.html.erb
index 0a616cf2f..586a3885f 100644
--- a/app/components/t_w/top_navigation_component.html.erb
+++ b/app/components/t_w/top_navigation_component.html.erb
@@ -10,10 +10,11 @@
 <div class="flex justify-start items-stretch grow relative gap-3 rounded-md bg-white">
   <%= form_with url: message_threads_path, method: :get, html: { class: 'relative flex flex-1' } do |f| %>
     <%= render Icons::MagnifyingGlassComponent.gray %>
-    <%= f.search_field :q, id: "search", value: params[:q], placeholder: 'Vyhľadaj správu', class: 'block h-full w-full border-0 py-0 pr-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-base' %>
+    <%= f.hidden_field :filter_id, value: query_params[:filter_id] %>
+    <%= f.search_field :q, id: "search", value: query, placeholder: 'Vyhľadaj správu', class: 'block h-full w-full border-0 py-0 pr-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-base' %>
   <% end %>
-  <% if params[:q].present? %>
-    <%= link_to new_filter_path(query: params[:q]), data: { turbo_frame: "modal" }, class: "flex items-center gap-2 text-gray-500 hover:text-gray-900", title: "Pridať filter" do %>
+  <% if query.present? %>
+    <%= link_to new_filter_path(query:), data: { turbo_frame: "modal" }, class: "flex items-center gap-2 text-gray-500 hover:text-gray-900", title: "Pridať filter" do %>
       <%= render Icons::BookmarkComponent.new %>
     <% end %>
   <% end %>
diff --git a/app/components/t_w/top_navigation_component.rb b/app/components/t_w/top_navigation_component.rb
index 13b29e70f..2898fc7e5 100644
--- a/app/components/t_w/top_navigation_component.rb
+++ b/app/components/t_w/top_navigation_component.rb
@@ -1,2 +1,17 @@
 class TW::TopNavigationComponent < ViewComponent::Base
+  include MessageThreadHelper
+
+  def query
+    Searchable::QueryBuilder.new(
+      filter_id: nil,
+      query: query_params[:q],
+      user: Current.user
+    ).build
+  end
+
+  def query_params
+    params
+      .permit(:filter_id, :q)
+      .slice(:filter_id, :q)
+  end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 24bdd32ee..7ec65e093 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -13,8 +13,8 @@ def pundit_user
   def set_menu_context
     return unless Current.user
 
-    @tags = policy_scope(Tag, policy_scope_class: TagPolicy::ScopeListable).where(visible: true).order(:name)
-    @filters = policy_scope(Filter, policy_scope_class: FilterPolicy::ScopeShowable).order(:position)
-    @menu = SidebarMenu.new(controller_name, action_name, { tags: @tags, filters: @filters }).menu
+    @filters = Current.user.visible_filters
+    @menu = SidebarMenu.new(controller_name, action_name, tags: @tags, filters: @filters).menu
+    @current_tenant_boxes_count = Current.tenant.boxes.count
   end
 end
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index f8d0b806b..42d759e90 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -11,10 +11,10 @@ def new
     authorize Filter
 
     if params[:query].present?
-      @filter = Filter.new(query: params[:query])
+      @filter = FulltextFilter.new(query: params[:query])
       render :new_in_modal
     else
-      @filter = Filter.new
+      @filter = FulltextFilter.new
       render :new
     end
   end
@@ -22,17 +22,17 @@ def new
   def create
     authorize Filter
 
-    @filter = Current.tenant.filters.build(filter_params.merge({author_id: Current.user.id}))
+    @filter = Current.tenant.filters.build(filter_params.merge({ author_id: Current.user.id }))
     if @filter.save
       flash[:notice] = 'Filter bol úspešne vytvorený'
       if params[:to] == 'search'
-        redirect_to message_threads_path(q: @filter.query)
+        redirect_to helpers.filtered_message_threads_path(filter: @filter)
       else
         redirect_to filters_path
       end
     else
       if params[:to] == 'search'
-        redirect_to message_threads_path(q: @filter.query), alert: 'Filter sa nepodarilo vytvoriť :('
+        redirect_to helpers.filtered_message_threads_path(query: @filter.query), alert: 'Filter sa nepodarilo vytvoriť :('
       else
         render :new
       end
@@ -59,10 +59,29 @@ def destroy
     redirect_to filters_path, notice: 'Filter bol úspešne odstránený'
   end
 
+  def sort
+    filters = filter_scope
+      .where(id: params[:filter_ids])
+      .reorder('')
+      .in_order_of(:id, params[:filter_ids])
+
+    filters.map do |filter|
+      authorize filter
+    end
+
+    Filter.transaction do
+      filters.map.with_index do |filter, i|
+        filter.update!(position: i + 1)
+      end
+    end
+
+    redirect_back fallback_location: filters_path
+  end
+
   private
 
   def filter_params
-    params.require(:filter).permit(:name, :query)
+    params.require(:filter).permit(:name, :query, :icon, :type)
   end
 
   def set_filter
@@ -70,6 +89,6 @@ def set_filter
   end
 
   def filter_scope
-    policy_scope(Filter, policy_scope_class: FilterPolicy::ScopeEditable).order(:position)
+    policy_scope(Filter, policy_scope_class: FilterPolicy::ScopeEditable).includes(:tag).order(:position)
   end
 end
diff --git a/app/controllers/message_threads_controller.rb b/app/controllers/message_threads_controller.rb
index 1c1f6b832..f6f857ea7 100644
--- a/app/controllers/message_threads_controller.rb
+++ b/app/controllers/message_threads_controller.rb
@@ -31,6 +31,15 @@ def update
 
   def index
     authorize MessageThread
+    if search_params[:filter_id].present?
+      @filter = policy_scope(Filter, policy_scope_class: FilterPolicy::ScopeShowable).find_by(id: search_params[:filter_id])
+    else
+      if Current.user.visible_filters.any?
+        redirect_to message_threads_path(search_params.merge(filter_id: Current.user.visible_filters.first.id))
+      end
+    end
+
+    @query = search_params[:q]
   end
 
   def scroll
@@ -70,13 +79,20 @@ def merge_threads(message_thread_ids)
   end
 
   def load_threads
+    query = Searchable::QueryBuilder.new(
+      filter: @filter,
+      filter_id: search_params[:filter_id],
+      query: search_params[:q],
+      user: Current.user,
+    ).build
+
     cursor = MessageThreadCollection.init_cursor(search_params[:cursor])
 
     result =
       MessageThreadCollection.all(
         scope: message_thread_policy_scope.includes(:tags, :box),
         search_permissions: search_permissions,
-        query: search_params[:q],
+        query:,
         cursor: cursor
       )
 
@@ -133,7 +149,7 @@ def message_thread_params
   end
 
   def search_params
-    params.permit(:q, :format, cursor: MessageThreadCollection::CURSOR_PARAMS)
+    params.permit(:q, :format, :filter_id, cursor: MessageThreadCollection::CURSOR_PARAMS)
   end
 
   def set_thread_tags
diff --git a/app/controllers/settings/user_filter_visibilities_controller.rb b/app/controllers/settings/user_filter_visibilities_controller.rb
new file mode 100644
index 000000000..1dcaf1895
--- /dev/null
+++ b/app/controllers/settings/user_filter_visibilities_controller.rb
@@ -0,0 +1,55 @@
+class Settings::UserFilterVisibilitiesController < ApplicationController
+  before_action :set_user_filter_visibility, only: [:update, :move_higher, :move_lower, :destroy]
+
+  def index
+    authorize UserFilterVisibility
+
+    set_user_filter_visibilities
+  end
+
+  def create
+    authorize UserFilterVisibility
+    Current.user.user_filter_visibilities.create(user_filter_visibility_params).tap do |visibility|
+      visibility.move_to_bottom
+    end
+    redirect_back fallback_location: request.referer
+  end
+
+  def update
+    authorize @user_filter_visibility
+    @user_filter_visibility.update(user_filter_visibility_params)
+    redirect_back fallback_location: request.referer
+  end
+
+  def move_higher
+    authorize @user_filter_visibility
+    @user_filter_visibility.move_higher
+    redirect_back fallback_location: request.referer
+  end
+
+  def move_lower
+    authorize @user_filter_visibility
+    @user_filter_visibility.move_lower
+    redirect_back fallback_location: request.referer
+  end
+
+  def destroy
+    authorize @user_filter_visibility
+    @user_filter_visibility.destroy
+    redirect_back fallback_location: request.referer
+  end
+
+  private
+
+  def set_user_filter_visibilities
+    @user_filter_visibilities = Current.user.build_filter_visibilities!
+  end
+
+  def set_user_filter_visibility
+    @user_filter_visibility = policy_scope(UserFilterVisibility).find(params[:id])
+  end
+
+  def user_filter_visibility_params
+    params.require(:user_filter_visibility).permit(:filter_id, :visible)
+  end
+end
diff --git a/app/helpers/message_thread_helper.rb b/app/helpers/message_thread_helper.rb
index f036423a5..99cd43c5a 100644
--- a/app/helpers/message_thread_helper.rb
+++ b/app/helpers/message_thread_helper.rb
@@ -1,4 +1,13 @@
 module MessageThreadHelper
+  def filtered_message_threads_path(filter: nil, query: nil)
+    args = {
+      filter_id: filter&.id,
+      q: query
+    }.compact
+
+    message_threads_path(args)
+  end
+
   def self.show_recipient?(message_thread)
     !message_thread.box.single_recipient? && message_thread.is_outbox && message_thread.recipient.present?
   end
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index d670cfc75..6fcaa5eb3 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -28,6 +28,9 @@ application.register("form", FormController)
 import MessageDraftsController from "./message_drafts_controller"
 application.register("message-drafts", MessageDraftsController)
 
+import SortableController from "./sortable_controller"
+application.register("sortable", SortableController)
+
 import DropzoneController from "./dropzone_controller"
 application.register("dropzone", DropzoneController)
 
diff --git a/app/javascript/controllers/sortable_controller.js b/app/javascript/controllers/sortable_controller.js
new file mode 100644
index 000000000..21317f1ac
--- /dev/null
+++ b/app/javascript/controllers/sortable_controller.js
@@ -0,0 +1,33 @@
+import {Controller} from "@hotwired/stimulus"
+import {Sortable} from "@shopify/draggable";
+import {post} from '@rails/request.js'
+
+export default class extends Controller {
+  static classes = ["draggable", "handle"]
+  static targets = ["item", "submit"]
+  static values = {url: String}
+
+  connect() {
+    this.sortable = new Sortable(this.element, {
+      draggable: this.draggableClass,
+      handle: this.handleClass,
+      classes: {
+        "source:dragging": "invisible",
+      }
+    })
+
+    this.sortable.on('drag:stopped', async (e) => {
+      // As of now, this is the only way to submit a PATCH request with Rails so Turbo updates the pages without a full reload
+
+      const link = this.submitTarget;
+
+      const urlSearchParams = new URLSearchParams({_method: 'patch'});
+      this.itemTargets.map((item) => {
+        urlSearchParams.append('filter_ids[]', item.dataset.id);
+      });
+      link.href = this.urlValue + '?' + urlSearchParams.toString();
+
+      link.click()
+    });
+  }
+}
diff --git a/app/lib/sidebar_menu.rb b/app/lib/sidebar_menu.rb
index 365bf319b..96560792c 100644
--- a/app/lib/sidebar_menu.rb
+++ b/app/lib/sidebar_menu.rb
@@ -1,30 +1,40 @@
 class SidebarMenu
   include Rails.application.routes.url_helpers
 
-  def initialize(controller, action, parameters = nil)
+  def initialize(controller, _action, parameters = nil, filters: [], tags: [])
     @parameters = parameters
-    @menu = initial_structure(controller, action)
+    @filters = filters
+    @menu = current_menu(controller)
   end
 
   attr_reader :menu
 
   private
 
-  def initial_structure(controller, _action)
-    return admin_menu + site_admin_menu if controller.in? %w[groups users tags tag_groups automation_rules boxes api_connections filters automation_webhooks]
+  def current_menu(controller)
+    return admin_menu + site_admin_menu if Current.user.admin? && controller.in?(%w[groups users tags tag_groups automation_rules boxes api_connections filters automation_webhooks user_filter_visibilities])
+    return settings_menu if controller.in? %w[filters tags user_filter_visibilities]
 
     default_main_menu
   end
 
   def default_main_menu
     [
-      TW::SidebarMenuItemComponent.new(name: 'Všetky správy', url: message_threads_path, icon: Common::IconComponent.new("envelope")),
-      Layout::FilterListComponent.new(filters: @parameters[:filters]),
-      Layout::TagListComponent.new(tags: @parameters[:tags]),
+      Layout::FilterListComponent.new(filters: @filters),
       TW::SidebarMenuItemComponent.new(name: 'Nastavenia', url: filters_path, icon: Icons::CogSixToothComponent.new)
     ]
   end
 
+  def settings_menu
+    [
+      Layout::BackToBoxComponent.new,
+      TW::SidebarMenuDividerComponent.new(name: 'Nastavenia'),
+      TW::SidebarMenuItemComponent.new(name: 'Filtre', url: filters_path, icon: Icons::BookmarkComponent.new),
+      TW::SidebarMenuItemComponent.new(name: 'Pravidlá', url: settings_automation_rules_path, icon: Icons::FunnelComponent.new),
+      TW::SidebarMenuItemComponent.new(name: 'Viditeľnosť filtrov', url: settings_user_filter_visibilities_path, icon: Common::IconComponent.new("bookmark"))
+    ]
+  end
+
   def admin_menu
     return [] unless Current.user.admin?
 
@@ -33,6 +43,7 @@ def admin_menu
       TW::SidebarMenuDividerComponent.new(name: 'Nastavenia'),
       TW::SidebarMenuItemComponent.new(name: 'Filtre', url: filters_path, icon: Icons::BookmarkComponent.new),
       TW::SidebarMenuItemComponent.new(name: 'Pravidlá', url: settings_automation_rules_path, icon: Icons::FunnelComponent.new),
+      TW::SidebarMenuItemComponent.new(name: 'Viditeľnosť filtrov', url: settings_user_filter_visibilities_path, icon: Common::IconComponent.new("tag")),
       TW::SidebarMenuDividerComponent.new(name: 'Administrácia'),
       TW::SidebarMenuItemComponent.new(name: 'Používatelia', url: admin_tenant_users_path(Current.tenant), icon: Icons::UsersComponent.new),
       TW::SidebarMenuItemComponent.new(name: 'Prístup', url: admin_tenant_tag_groups_path(Current.tenant), icon: Icons::LockClosedComponent.new),
diff --git a/app/models/everything_filter.rb b/app/models/everything_filter.rb
new file mode 100644
index 000000000..e1522cf78
--- /dev/null
+++ b/app/models/everything_filter.rb
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: filters
+#
+#  id         :bigint           not null, primary key
+#  icon       :string
+#  name       :string           not null
+#  position   :integer          not null
+#  query      :string
+#  type       :string           not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  author_id  :bigint
+#  tag_id     :bigint
+#  tenant_id  :bigint           not null
+#
+class EverythingFilter < TagFilter
+  before_validation :set_everything_tag
+  after_create :move_to_top
+
+  def self.model_name
+    Filter.model_name
+  end
+
+  def query
+    nil
+  end
+
+  private
+
+  def set_everything_tag
+    self.tag = tenant.everything_tag
+  end
+end
diff --git a/app/models/filter.rb b/app/models/filter.rb
index e64e2e8ef..5ccdf02a6 100644
--- a/app/models/filter.rb
+++ b/app/models/filter.rb
@@ -3,24 +3,35 @@
 # Table name: filters
 #
 #  id         :bigint           not null, primary key
+#  icon       :string
 #  name       :string           not null
 #  position   :integer          not null
-#  query      :string           not null
+#  query      :string
+#  type       :string           not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  author_id  :bigint           not null
+#  author_id  :bigint
+#  tag_id     :bigint
 #  tenant_id  :bigint           not null
 #
 class Filter < ApplicationRecord
   include AuditableEvents
+  include Iconized
 
-  belongs_to :author, class_name: 'User'
+  belongs_to :author, class_name: 'User', optional: true
   belongs_to :tenant
+  belongs_to :tag, optional: true
+  has_many :user_filter_visibilities, inverse_of: :filter, dependent: :destroy
 
-  validates :tenant_id, :author_id, :name, :query, presence: true
+  validates :tenant_id, :name, presence: true
 
   before_create :fill_position
 
+  scope :visible_for, -> (user) { joins(:user_filter_visibilities)
+    .where(user_filter_visibilities: { visible: true, user: user}) }
+
+  acts_as_list scope: :tenant_id
+
   def fill_position
     return if position.present?
 
diff --git a/app/models/fulltext_filter.rb b/app/models/fulltext_filter.rb
new file mode 100644
index 000000000..b9589760a
--- /dev/null
+++ b/app/models/fulltext_filter.rb
@@ -0,0 +1,23 @@
+# == Schema Information
+#
+# Table name: filters
+#
+#  id         :bigint           not null, primary key
+#  icon       :string
+#  name       :string           not null
+#  position   :integer          not null
+#  query      :string
+#  type       :string           not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  author_id  :bigint
+#  tag_id     :bigint
+#  tenant_id  :bigint           not null
+#
+class FulltextFilter < Filter
+  validates :query, presence: true
+
+  def self.model_name
+    Filter.model_name
+  end
+end
diff --git a/app/models/searchable/query_builder.rb b/app/models/searchable/query_builder.rb
new file mode 100644
index 000000000..a984f3ce6
--- /dev/null
+++ b/app/models/searchable/query_builder.rb
@@ -0,0 +1,24 @@
+class Searchable::QueryBuilder
+  attr_reader :filter, :filter_id, :query, :user
+
+  def initialize(filter: nil, filter_id: nil, query: nil, user: nil)
+    @filter = filter
+    @filter_id = filter_id
+    @query = query
+    @user = user
+  end
+
+  def build
+    if filter.nil? && filter_id.present?
+      @filter = FilterPolicy::ScopeShowable.new(user, Filter).resolve.find_by(id: filter_id)
+    end
+
+    [
+      filter&.query,
+      query,
+    ]
+      .compact
+      .map(&:strip)
+      .join(' ')
+  end
+end
\ No newline at end of file
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 3fcad2b2d..fa2e5a161 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -30,6 +30,8 @@ class Tag < ApplicationRecord
   has_many :automation_actions, class_name: "Automation::Action", as: :action_object, dependent: :restrict_with_error
   has_many :message_objects_tags, dependent: :destroy
   has_many :message_objects, through: :message_objects_tags
+  has_many :filters, dependent: :destroy
+  has_many :user_filter_visibilities, through: :filters
 
   validates :name, presence: true
   validates :name, uniqueness: { scope: :tenant_id, case_sensitive: false }
@@ -44,6 +46,20 @@ class Tag < ApplicationRecord
   scope :archived, -> { where(type: ArchivedTag.to_s) }
 
   after_update_commit ->(tag) { EventBus.publish(:tag_renamed, tag) if previous_changes.key?("name") }
+  after_update_commit ->(tag) do
+    tag.filters.each do |filter|
+      filter.user_filter_visibilities.update_all(visible: false)
+    end if previous_changes.key?("visible") && !tag.visible
+  end
+
+  after_create ->(tag) do
+    TagFilter.create!(
+      tenant: tag.tenant,
+      author: tag.owner,
+      name: tag.name,
+      tag:,
+    )
+  end
 
   def assign_to_message_object(message_object)
     message_object.assign_tag(self)
diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb
new file mode 100644
index 000000000..5109feaa4
--- /dev/null
+++ b/app/models/tag_filter.rb
@@ -0,0 +1,31 @@
+# == Schema Information
+#
+# Table name: filters
+#
+#  id         :bigint           not null, primary key
+#  icon       :string
+#  name       :string           not null
+#  position   :integer          not null
+#  query      :string
+#  type       :string           not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  author_id  :bigint
+#  tag_id     :bigint
+#  tenant_id  :bigint           not null
+#
+class TagFilter < Filter
+  belongs_to :tag, optional: false
+
+  def name
+    self[:name] || tag.name
+  end
+
+  def query
+    "label:(#{tag.name})"
+  end
+
+  def self.model_name
+    Filter.model_name
+  end
+end
diff --git a/app/models/tenant.rb b/app/models/tenant.rb
index 1b33c4ddf..9320d2112 100644
--- a/app/models/tenant.rb
+++ b/app/models/tenant.rb
@@ -106,8 +106,8 @@ def create_default_objects
     create_admin_group!(name: "admins")
     create_signer_group!(name: "signers")
 
-    create_draft_tag!(name: "Rozpracované", visible: true)
     create_everything_tag!(name: "Všetky správy", visible: false)
+    create_draft_tag!(name: "Rozpracované", visible: true)
     create_archived_tag!(name: "Archivované", color: "green", icon: "archive-box", visible: true)
     create_signature_requested_tag!(name: "Na podpis", visible: true, color: "yellow", icon: "pencil")
     create_signed_tag!(name: "Podpísané", visible: true, color: "green", icon: "fingerprint")
diff --git a/app/models/user.rb b/app/models/user.rb
index da15962f7..5d76ab068 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -13,6 +13,7 @@
 #  tenant_id                    :bigint
 #
 class User < ApplicationRecord
+  include Pundit::Authorization
   include AuditableEvents
 
   belongs_to :tenant
@@ -26,6 +27,7 @@ class User < ApplicationRecord
   has_many :filters, foreign_key: :author_id
   has_many :filter_subscriptions
   has_many :notifications
+  has_many :user_filter_visibilities, dependent: :destroy
 
   validates_presence_of :name, :email
   validates_uniqueness_of :name, :email, scope: :tenant_id, case_sensitive: false
@@ -48,10 +50,10 @@ def user_group
   def accessible_tags
     Tag.where(
       TagGroup.select(1)
-              .joins(:group_memberships)
-              .where("tag_groups.tag_id = tags.id")
-              .where(group_memberships: { user_id: id })
-              .arel.exists
+        .joins(:group_memberships)
+        .where("tag_groups.tag_id = tags.id")
+        .where(group_memberships: { user_id: id })
+        .arel.exists
     )
   end
 
@@ -78,8 +80,36 @@ def update_notifications_retention
     end
   end
 
+  def visible_filters
+    build_filter_visibilities!.where(visible: true).map(&:filter)
+  end
+
+  def build_filter_visibilities!
+    filters = policy_scope(Filter, policy_scope_class: FilterPolicy::ScopeShowable).order(:position)
+    visibilities = user_filter_visibilities.order(:position)
+    build_user_filter_visibilities(filters, visibilities)
+  end
+
+  def pundit_user
+    self
+  end
+
   private
 
+  def build_user_filter_visibilities(filters, visibilities)
+    user_filters_without_visibilities = filters.where.not(id: visibilities.pluck(:filter_id))
+    new_visibilities = user_filters_without_visibilities.map.with_index do |filter, i|
+      last_position = visibilities.last&.position || 0
+      visible = filter.is_a?(FulltextFilter) || filter.author_id.nil?
+      user_filter_visibilities.new(filter:, visible:, position: last_position + 1 + i)
+    end
+
+    all_visibilities = visibilities.to_a + new_visibilities
+    all_visibilities.map(&:save!)
+    user_filter_visibilities.where(id: all_visibilities).includes(filter: :tag).order(:position)
+  end
+
+
   def delete_user_group
     user_group.destroy
   end
diff --git a/app/models/user_filter_visibility.rb b/app/models/user_filter_visibility.rb
new file mode 100644
index 000000000..d6519dce2
--- /dev/null
+++ b/app/models/user_filter_visibility.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: user_filter_visibilities
+#
+#  id         :bigint           not null, primary key
+#  position   :integer
+#  visible    :boolean          default(TRUE), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  filter_id  :bigint           not null
+#  user_id    :bigint           not null
+#
+class UserFilterVisibility < ApplicationRecord
+  belongs_to :user
+  belongs_to :filter
+
+  acts_as_list scope: :user_id
+
+  def hidden
+    !visible
+  end
+end
diff --git a/app/policies/filter_policy.rb b/app/policies/filter_policy.rb
index ac38487e3..02312a42d 100644
--- a/app/policies/filter_policy.rb
+++ b/app/policies/filter_policy.rb
@@ -10,7 +10,7 @@ def initialize(user, filter)
 
   class ScopeEditable < Scope
     def resolve
-      scoped = scope.where(tenant_id: Current.tenant)
+      scoped = scope.where(tenant_id: Current.tenant).visible_for(@user)
 
       return scoped if @user.admin?
 
@@ -20,7 +20,19 @@ def resolve
 
   class ScopeShowable < Scope
     def resolve
-      scope.where(tenant_id: Current.tenant)
+      scoped = scope.where(tenant_id: Current.tenant)
+
+      return scoped if @user.admin?
+
+      scoped.left_joins(:tag)
+        .where(
+          TagGroup
+            .select(1)
+            .joins(:group_memberships)
+            .where("tag_groups.tag_id = tags.id")
+            .where(group_memberships: { user_id: @user.id })
+            .arel.exists)
+        .or(scoped.where(author_id: [nil, @user.id]))
     end
   end
 
@@ -48,6 +60,10 @@ def destroy?
     @user.admin? || is_author_current_user?
   end
 
+  def sort?
+    is_author_current_user?
+  end
+
   private
 
   def is_author_current_user?
diff --git a/app/policies/user_filter_visibility_policy.rb b/app/policies/user_filter_visibility_policy.rb
new file mode 100644
index 000000000..a3885a313
--- /dev/null
+++ b/app/policies/user_filter_visibility_policy.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class UserFilterVisibilityPolicy < ApplicationPolicy
+  attr_reader :user, :user_filter_visibility
+
+  def initialize(user, user_filter_visibility)
+    @user = user
+    @user_filter_visibility = user_filter_visibility
+  end
+
+  class Scope < Scope
+    def resolve
+      scope.where(user: Current.user)
+    end
+  end
+
+  def index?
+    true
+  end
+
+  def destroy?
+    owner?
+  end
+
+  def create?
+    true
+  end
+
+  def update?
+    owner?
+  end
+
+  def move_higher?
+    update?
+  end
+
+  def move_lower?
+    update?
+  end
+
+  private
+
+  def owner?
+    @user_filter_visibility.user == Current.user
+  end
+end
diff --git a/app/views/message_threads/index.html.erb b/app/views/message_threads/index.html.erb
index 7d9f1f4e0..4f8b5cc98 100644
--- a/app/views/message_threads/index.html.erb
+++ b/app/views/message_threads/index.html.erb
@@ -4,7 +4,9 @@
   <% end %>
 <% end %>
 
-<%= render MessageThreadsTableComponent.new(filter: @filter, filter_subscription: @filter_subscription) do |component| %>
+<%= render Common::FlashComponent.new %>
+
+<%= render MessageThreadsTableComponent.new(filter: @filter, query: @query, filter_subscription: @filter_subscription) do |component| %>
   <% component.with_message_thread do %>
     <%= render MessageThreadsTableRowComponent.with_collection(@message_threads) %>
   <% end %>
diff --git a/app/views/settings/user_filter_visibilities/index.html.erb b/app/views/settings/user_filter_visibilities/index.html.erb
new file mode 100644
index 000000000..10e38a30b
--- /dev/null
+++ b/app/views/settings/user_filter_visibilities/index.html.erb
@@ -0,0 +1 @@
+<%= render Settings::UserFilterVisibilities::ListComponent.new(@user_filter_visibilities) %>
diff --git a/config/routes.rb b/config/routes.rb
index 5e230a182..1dcdb4dd9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -22,6 +22,12 @@
     end
     resources :tags
     resource :profile
+    resources :user_filter_visibilities do
+      member do
+        post :move_higher
+        post :move_lower
+      end
+    end
   end
 
   namespace :admin do
@@ -142,6 +148,13 @@
   end
 
   resources :filters do
+    member do
+      patch :pin
+      patch :unpin
+    end
+
+    patch :sort, on: :collection
+
     resources :filter_subscriptions
   end
 
diff --git a/db/migrate/20231211132422_add_type_to_filters.rb b/db/migrate/20231211132422_add_type_to_filters.rb
new file mode 100644
index 000000000..6d3a884e6
--- /dev/null
+++ b/db/migrate/20231211132422_add_type_to_filters.rb
@@ -0,0 +1,19 @@
+class AddTypeToFilters < ActiveRecord::Migration[7.1]
+  def up
+    add_column :filters, :icon, :string, null: true
+    add_column :filters, :type, :string, null: true
+
+    Filter.where(type: nil).update_all(type: 'FulltextFilter')
+
+    change_column_null :filters, :name, false
+
+    add_index :filters, :type
+  end
+
+  def down
+    Filter.where.not(type: 'FulltextFilter').delete_all
+
+    remove_column :filters, :icon
+    remove_column :filters, :type
+  end
+end
diff --git a/db/migrate/20231211133725_add_tag_to_filters.rb b/db/migrate/20231211133725_add_tag_to_filters.rb
new file mode 100644
index 000000000..30c564abe
--- /dev/null
+++ b/db/migrate/20231211133725_add_tag_to_filters.rb
@@ -0,0 +1,5 @@
+class AddTagToFilters < ActiveRecord::Migration[7.1]
+  def change
+    add_reference :filters, :tag, null: true, foreign_key: true
+  end
+end
diff --git a/db/migrate/20231211134125_change_query_null_on_filters.rb b/db/migrate/20231211134125_change_query_null_on_filters.rb
new file mode 100644
index 000000000..af7943c92
--- /dev/null
+++ b/db/migrate/20231211134125_change_query_null_on_filters.rb
@@ -0,0 +1,5 @@
+class ChangeQueryNullOnFilters < ActiveRecord::Migration[7.1]
+  def change
+    change_column_null :filters, :query, true
+  end
+end
diff --git a/db/migrate/20231211140731_add_is_pinned_to_filters.rb b/db/migrate/20231211140731_add_is_pinned_to_filters.rb
new file mode 100644
index 000000000..73bf7e13d
--- /dev/null
+++ b/db/migrate/20231211140731_add_is_pinned_to_filters.rb
@@ -0,0 +1,6 @@
+class AddIsPinnedToFilters < ActiveRecord::Migration[7.1]
+  def change
+    add_column :filters, :is_pinned, :boolean, null: false, default: false
+    add_index :filters, :is_pinned
+  end
+end
diff --git a/db/migrate/20231213100122_remove_index_filters_on_tenant_id_and_position.rb b/db/migrate/20231213100122_remove_index_filters_on_tenant_id_and_position.rb
new file mode 100644
index 000000000..d28127e87
--- /dev/null
+++ b/db/migrate/20231213100122_remove_index_filters_on_tenant_id_and_position.rb
@@ -0,0 +1,5 @@
+class RemoveIndexFiltersOnTenantIdAndPosition < ActiveRecord::Migration[7.1]
+  def change
+    remove_index :filters, [:tenant_id, :position], unique: true
+  end
+end
diff --git a/db/migrate/20240103124622_user_hidden_items.rb b/db/migrate/20240103124622_user_hidden_items.rb
new file mode 100644
index 000000000..04a4be064
--- /dev/null
+++ b/db/migrate/20240103124622_user_hidden_items.rb
@@ -0,0 +1,10 @@
+class UserHiddenItems < ActiveRecord::Migration[7.1]
+  def change
+    create_table :user_hidden_items do |t|
+      t.references :user, null: false, foreign_key: true
+      t.string :user_hideable_type, null: false
+      t.bigint :user_hideable_id
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20240523122734_rename_user_hidden_item_to_user_item_visibility.rb b/db/migrate/20240523122734_rename_user_hidden_item_to_user_item_visibility.rb
new file mode 100644
index 000000000..967807a6c
--- /dev/null
+++ b/db/migrate/20240523122734_rename_user_hidden_item_to_user_item_visibility.rb
@@ -0,0 +1,5 @@
+class RenameUserHiddenItemToUserItemVisibility < ActiveRecord::Migration[7.1]
+  def change
+    rename_table :user_hidden_items, :user_item_visibilities
+  end
+end
diff --git a/db/migrate/20240523123256_rename_user_item_visibilities_user_hideable_to_user_item.rb b/db/migrate/20240523123256_rename_user_item_visibilities_user_hideable_to_user_item.rb
new file mode 100644
index 000000000..035c6f24c
--- /dev/null
+++ b/db/migrate/20240523123256_rename_user_item_visibilities_user_hideable_to_user_item.rb
@@ -0,0 +1,6 @@
+class RenameUserItemVisibilitiesUserHideableToUserItem < ActiveRecord::Migration[7.1]
+  def change
+    rename_column :user_item_visibilities, :user_hideable_type, :user_item_type
+    rename_column :user_item_visibilities, :user_hideable_id, :user_item_id
+  end
+end
diff --git a/db/migrate/20240523123432_add_visible_and_position_to_user_item_visibilities.rb b/db/migrate/20240523123432_add_visible_and_position_to_user_item_visibilities.rb
new file mode 100644
index 000000000..5cfa349c5
--- /dev/null
+++ b/db/migrate/20240523123432_add_visible_and_position_to_user_item_visibilities.rb
@@ -0,0 +1,6 @@
+class AddVisibleAndPositionToUserItemVisibilities < ActiveRecord::Migration[7.1]
+  def change
+    add_column :user_item_visibilities, :visible, :boolean, null: false, default: true
+    add_column :user_item_visibilities, :position, :integer, null: true
+  end
+end
diff --git a/db/migrate/20240617090128_rename_user_item_visibilities_to_user_filter_visibilities.rb b/db/migrate/20240617090128_rename_user_item_visibilities_to_user_filter_visibilities.rb
new file mode 100644
index 000000000..703544f46
--- /dev/null
+++ b/db/migrate/20240617090128_rename_user_item_visibilities_to_user_filter_visibilities.rb
@@ -0,0 +1,5 @@
+class RenameUserItemVisibilitiesToUserFilterVisibilities < ActiveRecord::Migration[7.1]
+  def change
+    rename_table :user_item_visibilities, :user_filter_visibilities
+  end
+end
diff --git a/db/migrate/20240617090456_rename_user_item_to_filter_on_user_filter_visibilities.rb b/db/migrate/20240617090456_rename_user_item_to_filter_on_user_filter_visibilities.rb
new file mode 100644
index 000000000..081affd61
--- /dev/null
+++ b/db/migrate/20240617090456_rename_user_item_to_filter_on_user_filter_visibilities.rb
@@ -0,0 +1,28 @@
+class RenameUserItemToFilterOnUserFilterVisibilities < ActiveRecord::Migration[7.1]
+  def change
+    add_reference :user_filter_visibilities, :filter, foreign_key: true, null: true
+
+    reversible do |dir|
+      dir.up do
+        UserFilterVisibility.reset_column_information
+        UserFilterVisibility.find_each do |visibility|
+          if visibility.user_item_type == 'Filter'
+            visibility.update!(filter_id: visibility.user_item_id)
+          else
+            visibility.destroy!
+          end
+        end
+      end
+
+      dir.down do
+        UserFilterVisibility.reset_column_information
+        UserFilterVisibility.find_each do |visibility|
+          visibility.update!(user_item_id: visibility.filter_id, user_item_type: visibility.filter.class.name)
+        end
+      end
+    end
+
+    change_column_null :user_filter_visibilities, :filter_id, false
+    remove_reference :user_filter_visibilities, :user_item, polymorphic: true, index: true
+  end
+end
diff --git a/db/migrate/20240619084104_change_type_null_on_filters.rb b/db/migrate/20240619084104_change_type_null_on_filters.rb
new file mode 100644
index 000000000..f7f241c01
--- /dev/null
+++ b/db/migrate/20240619084104_change_type_null_on_filters.rb
@@ -0,0 +1,14 @@
+class ChangeTypeNullOnFilters < ActiveRecord::Migration[7.1]
+  def change
+    reversible do |dir|
+      dir.up do
+        Filter.find_each do |filter|
+          if filter.type.nil?
+            filter.update!(type: 'FulltextFilter')
+          end
+        end
+      end
+    end
+    change_column_null :filters, :type, false
+  end
+end
diff --git a/db/migrate/20240624114023_change_author_id_null_on_filters.rb b/db/migrate/20240624114023_change_author_id_null_on_filters.rb
new file mode 100644
index 000000000..b9a89d10f
--- /dev/null
+++ b/db/migrate/20240624114023_change_author_id_null_on_filters.rb
@@ -0,0 +1,5 @@
+class ChangeAuthorIdNullOnFilters < ActiveRecord::Migration[7.1]
+  def change
+    change_column_null :filters, :author_id, true
+  end
+end
diff --git a/db/migrate/20240624114024_create_tag_filters_from_tags.rb b/db/migrate/20240624114024_create_tag_filters_from_tags.rb
new file mode 100644
index 000000000..e534fa493
--- /dev/null
+++ b/db/migrate/20240624114024_create_tag_filters_from_tags.rb
@@ -0,0 +1,30 @@
+class CreateTagFiltersFromTags < ActiveRecord::Migration[7.1]
+  def up
+    Tenant.find_each do |tenant|
+      tenant.tags.visible.find_each do |tag|
+        TagFilter.create!(
+          tenant:,
+          author: tag.owner,
+          name: tag.name,
+          tag:,
+        )
+      end
+
+      tenant.everything_tag.tap do |tag|
+        EverythingFilter.create!(
+          tenant:,
+          author: tag.owner,
+          name: tag.name,
+          tag:,
+        ).tap do |filter|
+          filter.move_to_top
+        end
+      end
+    end
+  end
+
+  def down
+    TagFilter.destroy_all
+    EverythingFilter.destroy_all
+  end
+end
diff --git a/db/migrate/20240814102352_add_unique_index_on_uuid_message_thread_id_to_messages.rb b/db/migrate/20240814102352_add_unique_index_on_uuid_message_thread_id_to_messages.rb
index 95e651bf0..f1a19578f 100644
--- a/db/migrate/20240814102352_add_unique_index_on_uuid_message_thread_id_to_messages.rb
+++ b/db/migrate/20240814102352_add_unique_index_on_uuid_message_thread_id_to_messages.rb
@@ -2,5 +2,9 @@ class AddUniqueIndexOnUuidMessageThreadIdToMessages < ActiveRecord::Migration[7.
   def up
     add_index :messages, [:uuid, :message_thread_id], unique: true
   end
+
+  def down
+    remove_index :messages, [:uuid, :message_thread_id], unique: true
+  end
 end
 
diff --git a/db/migrate/20240925134557_remove_is_pinned_from_filters.rb b/db/migrate/20240925134557_remove_is_pinned_from_filters.rb
new file mode 100644
index 000000000..87b1976e5
--- /dev/null
+++ b/db/migrate/20240925134557_remove_is_pinned_from_filters.rb
@@ -0,0 +1,10 @@
+class RemoveIsPinnedFromFilters < ActiveRecord::Migration[7.1]
+  def up
+    remove_column :filters, :is_pinned
+  end
+
+  def down
+    add_column :filters, :is_pinned, :boolean, null: false, default: false
+    add_index :filters, :is_pinned
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ba2de7a6b..411dbbbe2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -192,15 +192,19 @@
 
   create_table "filters", force: :cascade do |t|
     t.bigint "tenant_id", null: false
-    t.bigint "author_id", null: false
+    t.bigint "author_id"
     t.string "name", null: false
-    t.string "query", null: false
+    t.string "query"
     t.integer "position", null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.string "icon"
+    t.string "type", null: false
+    t.bigint "tag_id"
     t.index ["author_id"], name: "index_filters_on_author_id"
-    t.index ["tenant_id", "position"], name: "index_filters_on_tenant_id_and_position", unique: true
+    t.index ["tag_id"], name: "index_filters_on_tag_id"
     t.index ["tenant_id"], name: "index_filters_on_tenant_id"
+    t.index ["type"], name: "index_filters_on_type"
   end
 
   create_table "folders", force: :cascade do |t|
@@ -616,6 +620,17 @@
     t.datetime "updated_at", null: false
   end
 
+  create_table "user_filter_visibilities", force: :cascade do |t|
+    t.bigint "user_id", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.boolean "visible", default: true, null: false
+    t.integer "position"
+    t.bigint "filter_id", null: false
+    t.index ["filter_id"], name: "index_user_filter_visibilities_on_filter_id"
+    t.index ["user_id"], name: "index_user_filter_visibilities_on_user_id"
+  end
+
   create_table "users", force: :cascade do |t|
     t.bigint "tenant_id"
     t.string "email", null: false
@@ -646,6 +661,7 @@
   add_foreign_key "filter_subscriptions", "filters"
   add_foreign_key "filter_subscriptions", "tenants"
   add_foreign_key "filter_subscriptions", "users"
+  add_foreign_key "filters", "tags"
   add_foreign_key "filters", "tenants", on_delete: :cascade
   add_foreign_key "filters", "users", column: "author_id", on_delete: :cascade
   add_foreign_key "folders", "boxes"
@@ -686,5 +702,7 @@
   add_foreign_key "tags", "tenants"
   add_foreign_key "tags", "users", column: "owner_id"
   add_foreign_key "upvs_form_related_documents", "upvs_forms"
+  add_foreign_key "user_filter_visibilities", "filters"
+  add_foreign_key "user_filter_visibilities", "users"
   add_foreign_key "users", "tenants"
 end
diff --git a/package.json b/package.json
index 529308ac5..e561bd7fe 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
     "@hotwired/stimulus": "^3.2.1",
     "@hotwired/turbo-rails": "^7.0.0",
     "@rails/request.js": "^0.0.9",
+    "@shopify/draggable": "^1.1.3",
     "esbuild": "^0.19.8",
     "puppeteer": "^23.6.0",
     "tailwindcss-stimulus-components": "^4.0.4"
diff --git a/test/fixtures/filters.yml b/test/fixtures/filters.yml
index df801de55..56ac41f19 100644
--- a/test/fixtures/filters.yml
+++ b/test/fixtures/filters.yml
@@ -1,22 +1,70 @@
 # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
 
-one:
+ssd_all:
+  tenant: ssd
+  name: Everything
+  position: 1
+  type: 'EverythingFilter'
+  tag: ssd_everything
+
+ssd_general:
   tenant: ssd
   author: basic
   name: With General text
   query: general
-  position: 1
+  position: 2
+  type: 'FulltextFilter'
 
-two:
+ssd_legal:
   tenant: ssd
-  author: admin
+  author: basic
   name: With Legal text
   query: Legal
-  position: 2
+  position: 3
+  type: 'FulltextFilter'
+
+ssd_without_author:
+  tenant: ssd
+  name: Other
+  position: 4
+  tag: ssd_other
+  type: 'TagFilter'
+
+ssd_hidden:
+  tenant: ssd
+  author: basic
+  name: Hidden
+  position: 5
+  tag: ssd_construction
+  type: 'TagFilter'
+
+ssd_external:
+  tenant: ssd
+  author: basic
+  name: External
+  position: 6
+  tag: ssd_external
+  type: 'TagFilter'
+
+ssd_general_two:
+  tenant: ssd
+  author: ssd_signer
+  name: With General text
+  query: general
+  position: 6
+  type: 'FulltextFilter'
+
+solver_all:
+  tenant: solver
+  name: Everything
+  position: 1
+  type: 'EverythingFilter'
+  tag: solver_everything
 
-solver_one:
+solver_urgent:
   tenant: solver
   author: solver_other
   name: Urgent
   query: urgent
-  position: 3
+  position: 4
+  type: 'FulltextFilter'
diff --git a/test/helpers/auth_helper.rb b/test/helpers/auth_helper.rb
index 657e576a3..644b47837 100644
--- a/test/helpers/auth_helper.rb
+++ b/test/helpers/auth_helper.rb
@@ -6,7 +6,13 @@ def sign_in_as(user_fixture_name)
 
     click_on "Prihlásiť cez Google"
 
-    assert_text "Správy v schránke"
+    filter = Filter.where(tenant_id: users(user_fixture_name).tenant).order(:position).first
+
+    if filter
+      assert_text filter.name
+    else
+      assert_text "Správy v schránke"
+    end
   end
 
   def mock_omni_auth_with_user(user_fixture_name)
diff --git a/test/models/filter_subscription_test.rb b/test/models/filter_subscription_test.rb
index 134d1df36..adb896a1d 100644
--- a/test/models/filter_subscription_test.rb
+++ b/test/models/filter_subscription_test.rb
@@ -3,7 +3,7 @@
 class FilterSubscriptionTest < ActiveSupport::TestCase
   test "new thread matching subscription fires event" do
     user = users(:admin)
-    filter = filters(:one)
+    filter = filters(:ssd_general)
     s = FilterSubscription.create!(tenant: user.tenant, user: user, filter: filter, events: ["Notifications::NewMessageThread", "Notifications::NewMessage"])
 
     m = Govbox::Message.create_message_with_thread!(govbox_messages(:one))
diff --git a/test/system/filters_test.rb b/test/system/filters_test.rb
index 1166cfb5b..c0d562dad 100644
--- a/test/system/filters_test.rb
+++ b/test/system/filters_test.rb
@@ -3,7 +3,7 @@
 class FiltersTest < ApplicationSystemTestCase
   setup do
     Searchable::MessageThread.reindex_all
-    sign_in_as(:basic)
+    sign_in_as(:ssd_signer)
   end
 
   test "user can create a filter" do
@@ -41,7 +41,7 @@ class FiltersTest < ApplicationSystemTestCase
     assert_text "General"
 
     accept_alert do
-      click_link "Zmazať filter"
+      click_button "Zmazať filter"
     end
 
     assert_no_text "General"
diff --git a/test/system/message_threads_test.rb b/test/system/message_threads_test.rb
index 81ea09313..0297294c0 100644
--- a/test/system/message_threads_test.rb
+++ b/test/system/message_threads_test.rb
@@ -32,7 +32,6 @@ class MessageThreadsTest < ApplicationSystemTestCase
 
       within_tags do
         assert_text "Finance"
-        assert_text "Legal"
         assert_text "Other"
       end
     end
@@ -42,31 +41,24 @@ class MessageThreadsTest < ApplicationSystemTestCase
       assert_text "SD Services"
 
       within_tags do
+        assert_text "Archivovane"
         assert_text "Finance"
       end
     end
 
     within_sidebar do
       within_filters do
+        assert_text "Everything"
         assert_text "With General text"
         assert_text "With Legal text"
+        assert_text "Other"
 
         # other tenant
         refute_text "Urgent"
-      end
-
-      within_tags do
-        assert_text "Finance"
-        assert_text "Legal"
-        assert_text "ExtVisible"
-        assert_text "Print"
 
         # non visible
         refute_text "Hidden"
         refute_text "External"
-
-        # other tenant
-        refute_text "Special"
       end
     end
   end
@@ -99,7 +91,7 @@ class MessageThreadsTest < ApplicationSystemTestCase
     assert_no_selector "#next_page_area"
 
     within_sidebar do
-      within_tags do
+      within_filters do
         click_link "Legal"
       end
     end
diff --git a/test/system/notifications_test.rb b/test/system/notifications_test.rb
index cf2dca43c..8deda7473 100644
--- a/test/system/notifications_test.rb
+++ b/test/system/notifications_test.rb
@@ -10,8 +10,8 @@ class NotificationsTest < ApplicationSystemTestCase
     visit message_threads_path
 
     click_link "With General text"
-
-    click_link "Nastaviť notifikácie"
+    assert_text "General agenda SSD"
+    click_link "Nastaviť notifikácie" if page.has_link?("Nastaviť notifikácie")
 
     check "Nová konverzácia"
     check "Nová správa"
diff --git a/yarn.lock b/yarn.lock
index 4144e92d6..c7b230a16 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -235,6 +235,11 @@
   resolved "https://registry.yarnpkg.com/@rails/request.js/-/request.js-0.0.9.tgz#89e2a575405dc07eb8a9b3d2fe04289e1f057cd0"
   integrity sha512-VleYUyrA3rwKMvYnz7MI9Ada85Vekjb/WVz7NuGgDO24Y3Zy9FFSpDMQW+ea/tlftD+CdX/W/sUosRA9/HkDOQ==
 
+"@shopify/draggable@^1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@shopify/draggable/-/draggable-1.1.3.tgz#4c1d0415629198ec11d27b776dda0fc89eb5efa2"
+  integrity sha512-okZG6FTOX1HpfToN2uFVXTicum+NtHFGEY+0MFkZsdChP5qvLSlkffQormMxoDQVIDsK+jso3yz03tteIJ/A0w==
+
 "@tailwindcss/aspect-ratio@^0.4.2":
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz#9ffd52fee8e3c8b20623ff0dcb29e5c21fb0a9ba"