diff --git a/app/src/main/java/org/futo/circles/feature/timeline/TimelineViewModel.kt b/app/src/main/java/org/futo/circles/feature/timeline/TimelineViewModel.kt
index 9f75a896c98b095fef1f644d5e8982379e0129e3..d28a35a7e92cc3a7ffaaa4b5777256095e19c6fe 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/TimelineViewModel.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/TimelineViewModel.kt
@@ -7,8 +7,8 @@ import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import org.futo.circles.core.base.SingleEventLiveData
 import org.futo.circles.core.extensions.Response
-import org.futo.circles.core.extensions.getOrThrow
 import org.futo.circles.core.extensions.launchBg
+import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManager
 import org.futo.circles.core.feature.room.RoomNotificationsDataSource
 import org.futo.circles.core.feature.room.knoks.KnockRequestsDataSource
 import org.futo.circles.core.feature.timeline.BaseTimelineViewModel
@@ -39,11 +39,13 @@ class TimelineViewModel @Inject constructor(
     private val sendMessageDataSource: SendMessageDataSource,
     private val postOptionsDataSource: PostOptionsDataSource,
     private val userOptionsDataSource: UserOptionsDataSource,
-    private val readMessageDataSource: ReadMessageDataSource
-) : BaseTimelineViewModel(timelineDataSourceFactory.create(savedStateHandle.get<String>("timelineId") != null)) {
-
-    private val roomId: String = savedStateHandle.getOrThrow("roomId")
-    private val timelineId: String? = savedStateHandle["timelineId"]
+    private val readMessageDataSource: ReadMessageDataSource,
+    circleFilterAccountDataManager: CircleFilterAccountDataManager
+) : BaseTimelineViewModel(
+    savedStateHandle,
+    timelineDataSourceFactory.create(savedStateHandle.get<String>("timelineId") != null),
+    circleFilterAccountDataManager
+) {
 
     val session = MatrixSessionProvider.currentSession
     val profileLiveData = session?.userService()?.getUserLive(session.myUserId)
diff --git a/core/src/main/java/org/futo/circles/core/feature/circles/filter/CircleFilterAccountDataManager.kt b/core/src/main/java/org/futo/circles/core/feature/circles/filter/CircleFilterAccountDataManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4b72cd9967d442a2aa1c83232d31838960b27db8
--- /dev/null
+++ b/core/src/main/java/org/futo/circles/core/feature/circles/filter/CircleFilterAccountDataManager.kt
@@ -0,0 +1,53 @@
+package org.futo.circles.core.feature.circles.filter
+
+import org.futo.circles.core.extensions.createResult
+import org.futo.circles.core.provider.MatrixSessionProvider
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.util.JsonDict
+import javax.inject.Inject
+
+class CircleFilterAccountDataManager @Inject constructor() {
+
+    private val session = MatrixSessionProvider.getSessionOrThrow()
+
+    fun getCircleFilterLive(circleId: String) = session.getRoom(circleId)?.roomAccountDataService()
+        ?.getLiveAccountDataEvent(CIRCLE_FILTER_EVENT_TYPE)
+
+    fun getCircleFilter(circleId: String): Set<String> {
+        val content = session.getRoom(circleId)?.roomAccountDataService()
+            ?.getAccountDataEvent(CIRCLE_FILTER_EVENT_TYPE)?.content ?: return getAllTimelinesIds(
+            circleId
+        )
+        return getEventContentAsSet(content, circleId)
+    }
+
+    fun getEventContentAsSet(content: JsonDict?, circleId: String): Set<String> {
+        content ?: return getAllTimelinesIds(circleId)
+        return (content[TIMELINES_KEY] as? List<*>)?.map { it.toString() }?.toSet()
+            ?: getAllTimelinesIds(circleId)
+    }
+
+    fun getAllTimelinesIds(circleId: String): Set<String> {
+        val children = session.getRoom(circleId)?.roomSummary()?.spaceChildren ?: emptyList()
+        return children.mapNotNull {
+            val timelineSummary =
+                session.getRoom(it.childRoomId)?.roomSummary()?.takeIf { summary ->
+                    summary.membership.isActive()
+                }
+            timelineSummary?.roomId
+        }.toSet()
+    }
+
+    suspend fun updateFilter(circleId: String, timelineIds: Set<String>) = createResult {
+        session.getRoom(circleId)?.roomAccountDataService()
+            ?.updateAccountData(
+                CIRCLE_FILTER_EVENT_TYPE,
+                mapOf(TIMELINES_KEY to timelineIds)
+            )
+    }
+
+    companion object {
+        private const val CIRCLE_FILTER_EVENT_TYPE = "m.circle.filter"
+        private const val TIMELINES_KEY = "timelines"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt b/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt
index 3d52fe475553b3061e9ad21561fd234ff4ff4ce3..bec1d17722bc635f48e5c3ae76f69bbde83f21f7 100644
--- a/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt
@@ -1,25 +1,26 @@
 package org.futo.circles.core.feature.picker.gallery.media
 
 import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.asFlow
 import androidx.lifecycle.asLiveData
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import org.futo.circles.core.model.GalleryContentListItem
-import org.futo.circles.core.model.MediaContent
-import org.futo.circles.core.model.PostContentType
+import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManager
 import org.futo.circles.core.feature.picker.gallery.PickGalleryMediaDialogFragment.Companion.IS_VIDEO_AVAILABLE
 import org.futo.circles.core.feature.timeline.BaseTimelineViewModel
 import org.futo.circles.core.feature.timeline.data_source.SingleTimelineDataSource
+import org.futo.circles.core.model.GalleryContentListItem
+import org.futo.circles.core.model.MediaContent
+import org.futo.circles.core.model.PostContentType
 import javax.inject.Inject
 
 @HiltViewModel
 class PickMediaItemViewModel @Inject constructor(
     savedStateHandle: SavedStateHandle,
-    timelineDataSource: SingleTimelineDataSource
-) : BaseTimelineViewModel(timelineDataSource) {
+    timelineDataSource: SingleTimelineDataSource,
+    circleFilterAccountDataManager: CircleFilterAccountDataManager
+) : BaseTimelineViewModel(savedStateHandle, timelineDataSource, circleFilterAccountDataManager) {
 
     private val isVideoAvailable: Boolean = savedStateHandle[IS_VIDEO_AVAILABLE] ?: true
 
diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt
index 179a0c1894c00c40772323dfe03bfd5bc0b7da54..f0bbff3b8345419e21fcd619f5041b93cb8d2c61 100644
--- a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt
+++ b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt
@@ -6,8 +6,8 @@ import androidx.lifecycle.asLiveData
 import androidx.lifecycle.map
 import dagger.hilt.android.lifecycle.HiltViewModel
 import org.futo.circles.core.base.SingleEventLiveData
-import org.futo.circles.core.extensions.getOrThrow
 import org.futo.circles.core.extensions.launchBg
+import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManager
 import org.futo.circles.core.feature.timeline.BaseTimelineViewModel
 import org.futo.circles.core.feature.timeline.data_source.AccessLevelDataSource
 import org.futo.circles.core.feature.timeline.data_source.SingleTimelineDataSource
@@ -27,10 +27,9 @@ class GalleryViewModel @Inject constructor(
     private val sendMessageDataSource: SendMessageDataSource,
     private val mediaDataSource: PostContentDataSource,
     private val postOptionsDataSource: PostOptionsDataSource,
-    private val accessLevelDataSource: AccessLevelDataSource
-) : BaseTimelineViewModel(timelineDataSource) {
-
-    private val roomId: String = savedStateHandle.getOrThrow("roomId")
+    accessLevelDataSource: AccessLevelDataSource,
+    circleFilterAccountDataManager: CircleFilterAccountDataManager
+) : BaseTimelineViewModel(savedStateHandle, timelineDataSource, circleFilterAccountDataManager) {
 
     val accessLevelLiveData = accessLevelDataSource.accessLevelFlow.asLiveData()