diff --git a/loader/include/Geode/platform/platform.hpp b/loader/include/Geode/platform/platform.hpp
index 7da0eabc..6e257901 100644
--- a/loader/include/Geode/platform/platform.hpp
+++ b/loader/include/Geode/platform/platform.hpp
@@ -31,9 +31,13 @@ namespace geode {
 		operator int() const { return m_value; }
 
 		template<class T>
-		static Type cast(T t) {
+		static PlatformID from(T t) {
 			return static_cast<Type>(t);
 		}
+		template<class T>
+		T to() const {
+			return static_cast<T>(m_value);
+		}
 
 		static constexpr const char* toString(Type lp) {
 			switch (lp) {
diff --git a/loader/src/index/Index.cpp b/loader/src/index/Index.cpp
index 5388833f..76cc47b5 100644
--- a/loader/src/index/Index.cpp
+++ b/loader/src/index/Index.cpp
@@ -383,12 +383,25 @@ std::vector<IndexItem> const& Index::getItems() const {
     return m_items;
 }
 
-std::vector<IndexItem> Index::getUninstalledItems() const {
+std::vector<IndexItem> Index::getNoninstalledItems(
+    std::optional<std::unordered_set<PlatformID>> const& platforms
+) const {
     std::vector<IndexItem> items;
     for (auto& item : m_items) {
         if (!Loader::get()->isModInstalled(item.m_info.m_id)) {
-            if (item.m_download.m_platforms.count(GEODE_PLATFORM_TARGET)) {
-                items.push_back(item);
+            // return whatever is available on requested platforms
+            if (platforms) {
+                for (auto& plat : platforms.value()) {
+                    if (item.m_download.m_platforms.count(plat)) {
+                        items.push_back(item);
+                    }
+                }
+            }
+            // otherwise just return whatever is available on current platform
+            else {
+                if (item.m_download.m_platforms.count(GEODE_PLATFORM_TARGET)) {
+                    items.push_back(item);
+                }
             }
         }
     }
diff --git a/loader/src/index/Index.hpp b/loader/src/index/Index.hpp
index e9021295..cb376748 100644
--- a/loader/src/index/Index.hpp
+++ b/loader/src/index/Index.hpp
@@ -2,6 +2,7 @@
 
 #include <Geode/Geode.hpp>
 #include <mutex>
+#include <optional>
 
 USE_GEODE_NAMESPACE();
 
@@ -132,7 +133,9 @@ public:
     static Index* get();
 
     std::vector<IndexItem> const& getItems() const;
-    std::vector<IndexItem> getUninstalledItems() const;
+    std::vector<IndexItem> getNoninstalledItems(
+        std::optional<std::unordered_set<PlatformID>> const& platforms
+    ) const;
     bool isKnownItem(std::string const& id) const;
     IndexItem getKnownItem(std::string const& id) const;
     Result<InstallTicket*> installItems(
diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp
index af40f5ae..c7d34ae9 100644
--- a/loader/src/ui/internal/list/ModListLayer.cpp
+++ b/loader/src/ui/internal/list/ModListLayer.cpp
@@ -233,8 +233,12 @@ void ModListLayer::reloadList() {
 	}
 
 	// create new list
-	const char* filter = m_searchInput ? m_searchInput->getString() : nullptr;
-	auto list = ModListView::create(g_tab, 358.f, 190.f, filter, m_searchFlags);
+	m_query.m_searchFilter =
+		m_searchInput &&
+		m_searchInput->getString() &&
+		strlen(m_searchInput->getString()) ?
+			std::optional<std::string>(m_searchInput->getString()) : std::nullopt;
+	auto list = ModListView::create(g_tab, 358.f, 190.f, m_query);
 	list->setLayer(this);
 
 	// set list status
@@ -299,7 +303,7 @@ void ModListLayer::reloadList() {
 
 	// check if the user has searched something, 
 	// and show visual indicator if so
-	auto hasQuery = filter && strlen(filter);
+	auto hasQuery = m_query.m_searchFilter.has_value();
 	m_searchBtn->setVisible(!hasQuery);
 	m_searchClearBtn->setVisible(hasQuery);
 
diff --git a/loader/src/ui/internal/list/ModListLayer.hpp b/loader/src/ui/internal/list/ModListLayer.hpp
index eb9d17bc..995fe505 100644
--- a/loader/src/ui/internal/list/ModListLayer.hpp
+++ b/loader/src/ui/internal/list/ModListLayer.hpp
@@ -24,7 +24,7 @@ protected:
 	CCNode* m_searchBG = nullptr;
 	CCTextInputNode* m_searchInput = nullptr;
 	LoadingCircle* m_loadingCircle = nullptr;
-	int m_searchFlags = ModListView::s_allFlags;
+	ModListQuery m_query;
 
 	virtual ~ModListLayer();
 
diff --git a/loader/src/ui/internal/list/ModListView.cpp b/loader/src/ui/internal/list/ModListView.cpp
index 30106b57..cec2b45a 100644
--- a/loader/src/ui/internal/list/ModListView.cpp
+++ b/loader/src/ui/internal/list/ModListView.cpp
@@ -367,20 +367,24 @@ void ModListView::loadCell(TableViewCell* cell, unsigned int index) {
     }
 }
 
-bool ModListView::filter(ModInfo const& info, const char* searchFilter, int searchFlags) {
-    if (!searchFilter || !strlen(searchFilter)) return true;
+bool ModListView::filter(
+    ModInfo const& info,
+    std::optional<std::string> const& searchFilter,
+    int searchFlags
+) {
+    if (!searchFilter) return true;
     auto check = [searchFlags, searchFilter](SearchFlags flag, std::string const& name) -> bool {
         if (!(searchFlags & flag)) return false;
         return string_utils::contains(
             string_utils::toLower(name),
-            string_utils::toLower(searchFilter)
+            string_utils::toLower(searchFilter.value())
         );
     };
-    if (check(SearchFlags::Name,        info.m_name)) return true;
-    if (check(SearchFlags::ID,          info.m_id)) return true;
-    if (check(SearchFlags::Developer,   info.m_developer)) return true;
-    if (check(SearchFlags::Description, info.m_description)) return true;
-    if (check(SearchFlags::Details,     info.m_details)) return true;
+    if (check(SearchFlag::Name,        info.m_name)) return true;
+    if (check(SearchFlag::ID,          info.m_id)) return true;
+    if (check(SearchFlag::Developer,   info.m_developer)) return true;
+    if (check(SearchFlag::Description, info.m_description)) return true;
+    if (check(SearchFlag::Details,     info.m_details)) return true;
     return false;
 }
 
@@ -416,8 +420,7 @@ bool ModListView::init(
     ModListType type,
     float width,
     float height,
-    const char* searchFilter,
-    int searchFlags
+    ModListQuery query
 ) {
     if (!mods) {
         switch (type) {
@@ -429,7 +432,7 @@ bool ModListView::init(
                 }
                 // internal geode representation always at the top
                 auto imod = Loader::getInternalMod();
-                if (this->filter(imod->getModInfo(), searchFilter, searchFlags)) {
+                if (this->filter(imod->getModInfo(), query.m_searchFilter, query.m_searchFlags)) {
                     mods->addObject(new ModObject(imod));
                 }
                 // then other mods
@@ -438,7 +441,7 @@ bool ModListView::init(
                     // loaded, it's as good as not existing
                     // (because it doesn't)
                     if (mod->isUninstalled() && !mod->isLoaded()) continue;
-                    if (this->filter(mod->getModInfo(), searchFilter, searchFlags)) {
+                    if (this->filter(mod->getModInfo(), query.m_searchFilter, query.m_searchFlags)) {
                         mods->addObject(new ModObject(mod));
                     }
                 }
@@ -449,8 +452,10 @@ bool ModListView::init(
 
             case ModListType::Download: {
                 mods = CCArray::create();
-                for (auto const& item : Index::get()->getUninstalledItems()) {
-                    mods->addObject(new ModObject(item));
+                for (auto const& item : Index::get()->getNoninstalledItems(query.m_platforms)) {
+                    if (this->filter(item.m_info, query.m_searchFilter, query.m_searchFlags)) {
+                        mods->addObject(new ModObject(item));
+                    }
                 }
                 if (!mods->count()) {
                     m_status = Status::NoModsFound;
@@ -473,12 +478,11 @@ ModListView* ModListView::create(
     ModListType type,
     float width,
     float height,
-    const char* searchFilter,
-    int searchFlags
+    ModListQuery query
 ) {
     auto pRet = new ModListView;
     if (pRet) {
-        if (pRet->init(mods, type, width, height, searchFilter, searchFlags)) {
+        if (pRet->init(mods, type, width, height, query)) {
             pRet->autorelease();
             return pRet;
         }
@@ -491,10 +495,9 @@ ModListView* ModListView::create(
     ModListType type,
     float width,
     float height,
-    const char* searchFilter,
-    int searchFlags
+    ModListQuery query
 ) {
-    return ModListView::create(nullptr, type, width, height, searchFilter, searchFlags);
+    return ModListView::create(nullptr, type, width, height, query);
 }
 
 ModListView::Status ModListView::getStatus() const {
diff --git a/loader/src/ui/internal/list/ModListView.hpp b/loader/src/ui/internal/list/ModListView.hpp
index c7fb6973..e3623444 100644
--- a/loader/src/ui/internal/list/ModListView.hpp
+++ b/loader/src/ui/internal/list/ModListView.hpp
@@ -2,9 +2,12 @@
 
 #include <Geode/Geode.hpp>
 #include <Index.hpp>
+#include <optional>
 
 USE_GEODE_NAMESPACE();
 
+struct ModListQuery;
+
 enum class ModListType {
 	Installed,
 	Download,
@@ -74,10 +77,8 @@ public:
     static ModCell* create(ModListView* list, const char* key, CCSize size);
 };
 
-class ModListView : public CustomListView {
-public:
-    // this is not enum class so | works
-    enum SearchFlags {
+struct SearchFlag {
+    enum : int {
         Name        = 0b1,
         ID          = 0b10,
         Developer   = 0b100,
@@ -85,14 +86,24 @@ public:
         Description = 0b10000,
         Details     = 0b100000,
     };
-    static constexpr int s_allFlags =
-        SearchFlags::Name |
-        SearchFlags::ID |
-        SearchFlags::Developer |
-        SearchFlags::Credits |
-        SearchFlags::Description |
-        SearchFlags::Details;
+};
+using SearchFlags = int;
 
+static constexpr SearchFlags ALL_FLAGS =
+    SearchFlag::Name |
+    SearchFlag::ID |
+    SearchFlag::Developer |
+    SearchFlag::Credits |
+    SearchFlag::Description |
+    SearchFlag::Details;
+
+struct ModListQuery {
+    std::optional<std::string> m_searchFilter = std::nullopt;
+    int m_searchFlags = ALL_FLAGS;
+    std::unordered_set<PlatformID> m_platforms { GEODE_PLATFORM_TARGET };
+};
+
+class ModListView : public CustomListView {
 protected:
     enum class Status {
         OK,
@@ -113,10 +124,13 @@ protected:
         ModListType type,
         float width,
         float height,
-        const char* searchFilter,
-        int searchFlags
+        ModListQuery query
+    );
+    bool filter(
+        ModInfo const& info,
+        std::optional<std::string> const& searchFilter,
+        SearchFlags searchFlags
     );
-    bool filter(ModInfo const& info, const char* searchFilter, int searchFlags);
 
 public:
     static ModListView* create(
@@ -124,15 +138,13 @@ public:
         ModListType type = ModListType::Installed,
         float width = 358.f,
         float height = 220.f,
-        const char* searchFilter = nullptr,
-        int searchFlags = 0
+        ModListQuery query = ModListQuery()
     );
     static ModListView* create(
         ModListType type,
         float width = 358.f,
         float height = 220.f,
-        const char* searchFilter = nullptr,
-        int searchFlags = 0
+        ModListQuery query = ModListQuery()
     );
 
     void updateAllStates(ModCell* toggled = nullptr);
diff --git a/loader/src/ui/internal/list/SearchFilterPopup.cpp b/loader/src/ui/internal/list/SearchFilterPopup.cpp
index e413150a..81d32af7 100644
--- a/loader/src/ui/internal/list/SearchFilterPopup.cpp
+++ b/loader/src/ui/internal/list/SearchFilterPopup.cpp
@@ -12,7 +12,7 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
     auto winSize = CCDirector::sharedDirector()->getWinSize();
     auto pos = CCPoint { winSize.width / 2 - 145.f, winSize.height / 2 + 35.f };
 
-    auto matchTitle = CCLabelBMFont::create("Search fields", "goldFont.fnt");
+    auto matchTitle = CCLabelBMFont::create("Match fields", "goldFont.fnt");
     matchTitle->setPosition(winSize.width / 2 - 90.f, winSize.height / 2 + 65.f);
     matchTitle->setScale(.5f);
     m_mainLayer->addChild(matchTitle);
@@ -27,13 +27,12 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
 	matchBG->setScale(.5f);
 	m_mainLayer->addChild(matchBG);
 
-    this->addSearchMatch("Name",        ModListView::SearchFlags::Name,        pos);
-    this->addSearchMatch("ID",          ModListView::SearchFlags::ID,          pos);
-    this->addSearchMatch("Credits",     ModListView::SearchFlags::Credits,     pos);
-    this->addSearchMatch("Description", ModListView::SearchFlags::Description, pos);
-    this->addSearchMatch("Details",     ModListView::SearchFlags::Details,     pos);
-    this->addSearchMatch("Developer",   ModListView::SearchFlags::Developer,   pos);
-
+    this->addSearchMatch("Name",        SearchFlag::Name,        pos);
+    this->addSearchMatch("ID",          SearchFlag::ID,          pos);
+    this->addSearchMatch("Credits",     SearchFlag::Credits,     pos);
+    this->addSearchMatch("Description", SearchFlag::Description, pos);
+    this->addSearchMatch("Details",     SearchFlag::Details,     pos);
+    this->addSearchMatch("Developer",   SearchFlag::Developer,   pos);
     
     auto line = CCSprite::createWithSpriteFrameName("edit_vLine_001.png");
     line->setPosition({ winSize.width / 2, winSize.height / 2 - 20.f });
@@ -41,13 +40,35 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
     line->setOpacity(100);
     m_mainLayer->addChild(line);
 
+    auto platformTitle = CCLabelBMFont::create("Platforms", "goldFont.fnt");
+    platformTitle->setPosition(winSize.width / 2 + 90.f, winSize.height / 2 + 65.f);
+    platformTitle->setScale(.5f);
+    m_mainLayer->addChild(platformTitle);
+
+	auto platformBG = CCScale9Sprite::create(
+        "square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
+    );
+    platformBG->setColor({ 0, 0, 0 });
+    platformBG->setOpacity(90);
+	platformBG->setContentSize({ 290.f, 300.f });
+	platformBG->setPosition(winSize.width / 2 + 90.f, winSize.height / 2 - 21.f);
+	platformBG->setScale(.5f);
+	m_mainLayer->addChild(platformBG);
+
+    pos = CCPoint { winSize.width / 2 + 45.f, winSize.height / 2 + 35.f };
+
+    this->addPlatformToggle("Windows", PlatformID::Windows, pos);
+    this->addPlatformToggle("MacOS",   PlatformID::MacOS,   pos);
+    this->addPlatformToggle("iOS",     PlatformID::iOS,     pos);
+    this->addPlatformToggle("Android", PlatformID::Android, pos);
+
     return true;
 }
 
 void SearchFilterPopup::addSearchMatch(const char* title, int flag, CCPoint& pos) {
     GameToolbox::createToggleButton(
-        title, menu_selector(SearchFilterPopup::onToggle),
-        m_modLayer->m_searchFlags & flag,
+        title, menu_selector(SearchFilterPopup::onSearchToggle),
+        m_modLayer->m_query.m_searchFlags & flag,
         m_buttonMenu, pos, this,
         m_buttonMenu, .5f, .5f, 100.f, 
         { 10.f, .0f }, nullptr, false, flag, nullptr
@@ -55,14 +76,46 @@ void SearchFilterPopup::addSearchMatch(const char* title, int flag, CCPoint& pos
     pos.y -= 22.5f;
 }
 
-void SearchFilterPopup::onToggle(cocos2d::CCObject* pSender) {
-    if (as<CCMenuItemToggler*>(pSender)->isToggled()) {
-        m_modLayer->m_searchFlags &= ~pSender->getTag();
+void SearchFilterPopup::addPlatformToggle(
+    const char* title,
+    PlatformID id,
+    CCPoint& pos
+) {
+    GameToolbox::createToggleButton(
+        title, menu_selector(SearchFilterPopup::onPlatformToggle),
+        m_modLayer->m_query.m_platforms.count(id),
+        m_buttonMenu, pos, this,
+        m_buttonMenu, .5f, .5f, 100.f, 
+        { 10.f, .0f }, nullptr, false, id.to<int>(), nullptr
+    )->setTag(id.to<int>());
+    pos.y -= 22.5f;
+}
+
+void SearchFilterPopup::onSearchToggle(CCObject* sender) {
+    if (as<CCMenuItemToggler*>(sender)->isToggled()) {
+        m_modLayer->m_query.m_searchFlags &= ~sender->getTag();
     } else {
-        m_modLayer->m_searchFlags |= pSender->getTag();
+        m_modLayer->m_query.m_searchFlags |= sender->getTag();
     }
 }
 
+void SearchFilterPopup::onPlatformToggle(CCObject* sender) {
+    if (as<CCMenuItemToggler*>(sender)->isToggled()) {
+        m_modLayer->m_query.m_platforms.erase(
+            PlatformID::from(sender->getTag())
+        );
+    } else {
+        m_modLayer->m_query.m_platforms.insert(
+            PlatformID::from(sender->getTag())
+        );
+    }
+}
+
+void SearchFilterPopup::onClose(CCObject* sender) {
+    Popup::onClose(sender);
+    m_modLayer->reloadList();
+}
+
 SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) {
     auto ret = new SearchFilterPopup();
     if (ret && ret->init(350.f, 220.f, layer, type)) {
diff --git a/loader/src/ui/internal/list/SearchFilterPopup.hpp b/loader/src/ui/internal/list/SearchFilterPopup.hpp
index 713394f1..773f7a7f 100644
--- a/loader/src/ui/internal/list/SearchFilterPopup.hpp
+++ b/loader/src/ui/internal/list/SearchFilterPopup.hpp
@@ -13,8 +13,12 @@ protected:
 
     bool setup(ModListLayer* layer, ModListType type) override;
     void addSearchMatch(const char* title, int flag, CCPoint& pos);
+    void addPlatformToggle(const char* title, PlatformID id, CCPoint& pos);
 
-    void onToggle(cocos2d::CCObject*);
+    void onSearchToggle(CCObject*);
+    void onPlatformToggle(CCObject*);
+
+    void onClose(CCObject*) override;
     
 public:
     static SearchFilterPopup* create(ModListLayer* layer, ModListType type);