diff --git a/src/conversationlistmodel.cpp b/src/conversationlistmodel.cpp
index fd0b16c0a83452abdaf7aa89c0e5ce2c745f9e97..355c79cdc5c7d442ff7de5acf997e77f4bdc26d6 100644
--- a/src/conversationlistmodel.cpp
+++ b/src/conversationlistmodel.cpp
@@ -105,19 +105,10 @@ ConversationListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
 
     using namespace ConversationList;
 
-    // name/id
-    auto uri = index.data(Role::URI).toString();
-    auto alias = index.data(Role::Alias).toString();
-    auto registeredName = index.data(Role::RegisteredName).toString();
-
-    // account type
-    auto itemProfileType = static_cast<profile::Type>(index.data(Role::ContactType).toInt());
-    // this is workaround for profile::Type including both account types and extended types
-    // - PENDING is implicitly also JAMI
-    // - TEMPORARY should never be in this list
-    if (itemProfileType == profile::Type::PENDING)
-        itemProfileType = profile::Type::JAMI;
-    auto typeFilter = itemProfileType == profileTypeFilter_;
+    QStringList toFilter;
+    toFilter += index.data(Role::Title).toString();
+    toFilter += index.data(Role::Uris).toStringList();
+    toFilter += index.data(Role::Monikers).toStringList();
 
     // requests
     auto isRequest = index.data(Role::IsRequest).toBool();
@@ -127,14 +118,22 @@ ConversationListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
 
     // banned contacts require exact match
     if (index.data(Role::IsBanned).toBool()) {
-        match = !rx.isEmpty()
-                && (rx.exactMatch(uri) || rx.exactMatch(alias) || rx.exactMatch(registeredName));
+        if (!rx.isEmpty()) {
+            Q_FOREACH (const auto& filter, toFilter)
+                if (rx.exactMatch(filter)) {
+                    match = true;
+                    break;
+                }
+        }
     } else {
-        match = (rx.indexIn(uri) != -1 || rx.indexIn(alias) != -1
-                 || rx.indexIn(registeredName) != -1);
+        Q_FOREACH (const auto& filter, toFilter)
+            if (rx.indexIn(filter) != -1) {
+                match = true;
+                break;
+            }
     }
 
-    return typeFilter && requestFilter && match;
+    return requestFilter && match;
 }
 
 bool
@@ -146,20 +145,6 @@ ConversationListProxyModel::lessThan(const QModelIndex& left, const QModelIndex&
     return leftData.toULongLong() < rightData.toULongLong();
 }
 
-void
-ConversationListProxyModel::setProfileTypeFilter(const profile::Type& profileTypeFilter)
-{
-    if (profileTypeFilter != lrc::api::profile::Type::JAMI
-        && profileTypeFilter != lrc::api::profile::Type::SIP) {
-        qWarning() << "Profile filter parameter must be an account type";
-        return;
-    }
-    beginResetModel();
-    profileTypeFilter_ = profileTypeFilter;
-    endResetModel();
-    updateSelection();
-};
-
 void
 ConversationListProxyModel::setFilterRequests(bool filterRequests)
 {
diff --git a/src/conversationlistmodel.h b/src/conversationlistmodel.h
index b41dad1fe898d04e01ff714e95bb8870dba93078..27027914de7d9604c605c151f0672458f61eb331 100644
--- a/src/conversationlistmodel.h
+++ b/src/conversationlistmodel.h
@@ -48,15 +48,9 @@ public:
     bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
     bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
 
-    Q_INVOKABLE void setProfileTypeFilter(const profile::Type& profileTypeFilter);
     Q_INVOKABLE void setFilterRequests(bool filterRequests);
 
 private:
-    // With swarm in place, this filter should only ever be set to profile::Type::JAMI
-    // and profile::Type::SIP as profile::Type::PENDING should no longer be used to
-    // filter for invites, and instead use the isRequest property of a conversation.
-    profile::Type profileTypeFilter_;
-
     // This flag can be toggled when switching tabs to show the current account's
     // conversation invites.
     bool filterRequests_ {false};
diff --git a/src/conversationlistmodelbase.cpp b/src/conversationlistmodelbase.cpp
index 6921dafbb66905919c981a04fba6fe8c7169b6b8..f65cff1aa706dfd0a13453ed7a7bf86be41d3903 100644
--- a/src/conversationlistmodelbase.cpp
+++ b/src/conversationlistmodelbase.cpp
@@ -47,70 +47,7 @@ ConversationListModelBase::roleNames() const
 QVariant
 ConversationListModelBase::dataForItem(item_t item, int role) const
 {
-    if (item.participants.isEmpty()) {
-        return QVariant();
-    }
-    // WARNING: not swarm ready
-    auto peerUri = item.participants[0];
-    ContactModel* contactModel {nullptr};
-    contact::Info contact {};
-    try {
-        const auto& accountInfo = lrcInstance_->getAccountInfo(item.accountId);
-        contactModel = accountInfo.contactModel.get();
-        contact = contactModel->getContact(peerUri);
-    } catch (...) {
-    }
-
-    // Since we are using image provider right now, image url representation should be unique to
-    // be able to use the image cache, account avatar will only be updated once PictureUid changed
     switch (role) {
-    case Role::Title:
-        return QVariant(model_->title(item.uid));
-    case Role::BestId:
-        return QVariant(contactModel->bestIdForContact(peerUri));
-    case Role::Presence:
-        return QVariant(contact.isPresent);
-    case Role::PictureUid:
-        return QVariant(contactAvatarUidMap_[peerUri]);
-    case Role::Alias:
-        return QVariant(contact.profileInfo.alias);
-    case Role::RegisteredName:
-        return QVariant(contact.registeredName);
-    case Role::URI:
-        return QVariant(peerUri);
-    case Role::UnreadMessagesCount:
-        return QVariant(item.unreadMessages);
-    case Role::LastInteractionTimeStamp: {
-        if (!item.interactions.empty()) {
-            auto ts = static_cast<qint32>(item.interactions.at(item.lastMessageUid).timestamp);
-            return QVariant(ts);
-        }
-        break;
-    }
-    case Role::LastInteractionDate: {
-        if (!item.interactions.empty()) {
-            return QVariant(
-                Utils::formatTimeString(item.interactions.at(item.lastMessageUid).timestamp));
-        }
-        break;
-    }
-    case Role::LastInteraction: {
-        if (!item.interactions.empty()) {
-            return QVariant(item.interactions.at(item.lastMessageUid).body);
-        }
-        break;
-    }
-    case Role::ContactType: {
-        return QVariant(static_cast<int>(contact.profileInfo.type));
-    }
-    case Role::IsSwarm: {
-        return QVariant(!item.isNotASwarm());
-    }
-    case Role::IsBanned: {
-        return QVariant(contact.isBanned);
-    }
-    case Role::UID:
-        return QVariant(item.uid);
     case Role::InCall: {
         const auto& convInfo = lrcInstance_->getConversationFromConvUid(item.uid);
         if (!convInfo.uid.isEmpty()) {
@@ -127,7 +64,7 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
                 return QVariant(call->isAudioOnly);
             }
         }
-        return QVariant();
+        return QVariant(false);
     }
     case Role::CallStackViewShouldShow: {
         const auto& convInfo = lrcInstance_->getConversationFromConvUid(item.uid);
@@ -150,7 +87,7 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
                 return QVariant(static_cast<int>(call->status));
             }
         }
-        return QVariant();
+        return {};
     }
     case Role::Draft: {
         if (!item.uid.isEmpty()) {
@@ -162,13 +99,94 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
                 return emojiString + draft;
             }
         }
-        return QVariant("");
+        return {};
     }
-    case Role::IsRequest: {
+    case Role::IsRequest:
         return QVariant(item.isRequest);
+    case Role::Title:
+        return QVariant(model_->title(item.uid));
+    case Role::UnreadMessagesCount:
+        return QVariant(item.unreadMessages);
+    case Role::LastInteractionTimeStamp: {
+        if (!item.interactions.empty()) {
+            auto ts = static_cast<qint32>(item.interactions.at(item.lastMessageUid).timestamp);
+            return QVariant(ts);
+        }
+        break;
+    }
+    case Role::LastInteractionDate: {
+        if (!item.interactions.empty()) {
+            return QVariant(
+                Utils::formatTimeString(item.interactions.at(item.lastMessageUid).timestamp));
+        }
+        break;
+    }
+    case Role::LastInteraction: {
+        if (!item.interactions.empty()) {
+            return QVariant(item.interactions.at(item.lastMessageUid).body);
+        }
+        break;
     }
+    case Role::IsSwarm:
+        return QVariant(item.isSwarm());
+    case Role::Mode:
+        return QVariant(static_cast<int>(item.mode));
+    case Role::UID:
+        return QVariant(item.uid);
+    case Role::Uris:
+        return QVariant(model_->peersForConversation(item.uid).toList());
+    case Role::Monikers: {
+        // we shouldn't ever need these individually, they are used for filtering only
+        QStringList ret;
+        Q_FOREACH (const auto& peerUri, model_->peersForConversation(item.uid))
+            try {
+                auto& accInfo = lrcInstance_->getCurrentAccountInfo();
+                auto contact = accInfo.contactModel->getContact(peerUri);
+                ret << contact.profileInfo.alias << contact.registeredName;
+            } catch (const std::exception&) {
+            }
+        return ret;
     }
-    return QVariant();
+    default:
+        break;
+    }
+
+    if (item.isCoreDialog()) {
+        auto peerUriList = model_->peersForConversation(item.uid);
+        if (peerUriList.isEmpty()) {
+            return {};
+        }
+        auto peerUri = peerUriList.at(0);
+        ContactModel* contactModel;
+        contact::Info contact {};
+        try {
+            contactModel = lrcInstance_->getCurrentAccountInfo().contactModel.get();
+            contact = contactModel->getContact(peerUri);
+        } catch (const std::exception&) {
+            return {};
+        }
+
+        switch (role) {
+        case Role::BestId:
+            return QVariant(contactModel->bestIdForContact(peerUri));
+        case Role::Presence:
+            return QVariant(contact.isPresent);
+        case Role::PictureUid:
+            return QVariant(contactAvatarUidMap_[peerUri]);
+        case Role::Alias:
+            return QVariant(contact.profileInfo.alias);
+        case Role::RegisteredName:
+            return QVariant(contact.registeredName);
+        case Role::URI:
+            return QVariant(peerUri);
+        case Role::IsBanned:
+            return QVariant(contact.isBanned);
+        case Role::ContactType:
+            return QVariant(static_cast<int>(contact.profileInfo.type));
+        }
+    }
+
+    return {};
 }
 
 void
diff --git a/src/conversationlistmodelbase.h b/src/conversationlistmodelbase.h
index 9a988bbdc5a71e871de850dc092aa88a88e3ba5f..40fc0fcd74ac45fe5fa421c47957e6f003303626 100644
--- a/src/conversationlistmodelbase.h
+++ b/src/conversationlistmodelbase.h
@@ -45,7 +45,10 @@
     X(AccountId) \
     X(PictureUid) \
     X(Draft) \
-    X(IsRequest)
+    X(IsRequest) \
+    X(Mode) \
+    X(Uris) \
+    X(Monikers)
 
 namespace ConversationList {
 Q_NAMESPACE
diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp
index ba9ca91e008863638cbe9057a7abbdbc96362a23..2d7bf6b9494d89bad08a50a06f7c002a8b89ef5d 100644
--- a/src/conversationsadapter.cpp
+++ b/src/conversationsadapter.cpp
@@ -44,15 +44,8 @@ ConversationsAdapter::ConversationsAdapter(SystemTray* systemTray,
 
     new SelectableListProxyGroupModel({convModel_.data(), searchModel_.data()}, this);
 
-    // this will trigger when the conversations filter tab is selected
-    connect(this, &ConversationsAdapter::profileTypeFilterChanged, [this]() {
-        convModel_->setProfileTypeFilter(profileTypeFilter_);
-    });
-    set_profileTypeFilter(profile::Type::JAMI);
-
     // this will trigger when the invite filter tab is selected
     connect(this, &ConversationsAdapter::filterRequestsChanged, [this]() {
-        // it is assumed that profileTypeFilter is profile::Type::JAMI here
         convModel_->setFilterRequests(filterRequests_);
     });
 
@@ -73,9 +66,6 @@ ConversationsAdapter::ConversationsAdapter(SystemTray* systemTray,
             accInfo.conversationModel->selectConversation(convInfo.uid);
             accInfo.conversationModel->clearUnreadInteractions(convInfo.uid);
 
-            // set the account type filter corresponding to the conversation's account type
-            set_profileTypeFilter(accInfo.profileInfo.type);
-
             // this may be a request, so adjust that filter also
             set_filterRequests(convInfo.isRequest);
 
@@ -161,8 +151,6 @@ ConversationsAdapter::safeInit()
             Qt::UniqueConnection);
 
     connectConversationModel();
-
-    set_profileTypeFilter(lrcInstance_->getCurrentAccountInfo().profileInfo.type);
 }
 
 void
@@ -172,8 +160,6 @@ ConversationsAdapter::onCurrentAccountIdChanged()
 
     connectConversationModel();
 
-    set_profileTypeFilter(lrcInstance_->getCurrentAccountInfo().profileInfo.type);
-
     // Always turn the requests filter off when switching account.
     // Conversation selection will manage the filter state in the
     // case of programmatic selection(incoming call, notification
@@ -355,9 +341,6 @@ ConversationsAdapter::updateConversationFilterData()
     }
     set_totalUnreadMessageCount(totalUnreadMessages);
     set_pendingRequestCount(accountInfo.conversationModel->pendingRequestCount());
-    if (pendingRequestCount_ == 0 && profileTypeFilter_ == profile::Type::PENDING) {
-        set_profileTypeFilter(profile::Type::JAMI);
-    }
 }
 
 void
@@ -408,7 +391,7 @@ ConversationsAdapter::getConvInfoMap(const QString& convId)
             {"bestId", contactModel->bestIdForContact(peerUri)},
             {"title", lrcInstance_->getCurrentConversationModel()->title(convId)},
             {"uri", peerUri},
-            {"isSwarm", !convInfo.isNotASwarm()},
+            {"isSwarm", convInfo.isSwarm()},
             {"contactType", static_cast<int>(contact.profileInfo.type)},
             {"isAudioOnly", isAudioOnly},
             {"callState", static_cast<int>(callState)},
diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h
index 052a19129d1883a6d8f599132615e1370dbdc793..1f107c05d2d3e4a3403a711d17dccf26f4027ed4 100644
--- a/src/conversationsadapter.h
+++ b/src/conversationsadapter.h
@@ -33,7 +33,6 @@ class SystemTray;
 class ConversationsAdapter final : public QmlAdapterBase
 {
     Q_OBJECT
-    QML_PROPERTY(lrc::api::profile::Type, profileTypeFilter)
     QML_PROPERTY(bool, filterRequests)
     QML_PROPERTY(int, totalUnreadMessageCount)
     QML_PROPERTY(int, pendingRequestCount)
diff --git a/src/mainview/components/SidePanelTabBar.qml b/src/mainview/components/SidePanelTabBar.qml
index 1e5141c91179d31705c35346966e9eb5fcf3eae8..59babc805e3eaec762fd408b839a74676a50a384 100644
--- a/src/mainview/components/SidePanelTabBar.qml
+++ b/src/mainview/components/SidePanelTabBar.qml
@@ -39,7 +39,6 @@ TabBar {
     }
 
     function selectTab(idx) {
-        ConversationsAdapter.profileTypeFilter = LRCInstance.currentAccountType
         ConversationsAdapter.filterRequests = (idx === SidePanelTabBar.Requests)
     }
 
diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp
index e49d0d83cdb293fd3a22c9175108a66bc48b37fc..d3b295fff4ea0dcd5d3601f2daf7bee660e8d0b1 100644
--- a/src/messagesadapter.cpp
+++ b/src/messagesadapter.cpp
@@ -80,19 +80,19 @@ MessagesAdapter::setupChatView(const QString& convUid)
 
     QMetaObject::invokeMethod(qmlObj_,
                               "setSendContactRequestButtonVisible",
-                              Q_ARG(QVariant, convInfo.isNotASwarm() && convInfo.isRequest));
+                              Q_ARG(QVariant, convInfo.isLegacy() && convInfo.isRequest));
     QMetaObject::invokeMethod(qmlObj_,
                               "setMessagingHeaderButtonsVisible",
                               Q_ARG(QVariant,
-                                    !(convInfo.isNotASwarm()
+                                    !(convInfo.isLegacy()
                                       && (convInfo.isRequest || convInfo.needsSyncing))));
 
     setMessagesVisibility(false);
-    setIsSwarm(!convInfo.isNotASwarm());
+    setIsSwarm(convInfo.isSwarm());
     setInvitation(convInfo.isRequest or convInfo.needsSyncing,
                   convModel->title(convInfo.uid),
                   contactURI,
-                  !convInfo.isNotASwarm(),
+                  !convInfo.isSwarm(),
                   convInfo.needsSyncing);
 
     // Draft and message content set up.
@@ -257,7 +257,7 @@ MessagesAdapter::slotMessagesCleared()
     auto convOpt = convModel->getConversationForUid(lrcInstance_->get_selectedConvUid());
     if (!convOpt)
         return;
-    if (!convOpt->get().isNotASwarm() && !convOpt->get().allMessagesLoaded) {
+    if (convOpt->get().isSwarm() && !convOpt->get().allMessagesLoaded) {
         convModel->loadConversationMessages(convOpt->get().uid, 20);
     } else {
         printHistory(*convModel, convOpt->get().interactions);
@@ -504,15 +504,15 @@ MessagesAdapter::setConversationProfileData(const lrc::api::conversation::Info&
         QMetaObject::invokeMethod(qmlObj_,
                                   "setMessagingHeaderButtonsVisible",
                                   Q_ARG(QVariant,
-                                        !(!convInfo.isNotASwarm()
+                                        !(convInfo.isSwarm()
                                           && (convInfo.isRequest || convInfo.needsSyncing))));
 
         setInvitation(convInfo.isRequest or convInfo.needsSyncing,
                       title,
                       contactUri,
-                      !convInfo.isNotASwarm(),
+                      convInfo.isSwarm(),
                       convInfo.needsSyncing);
-        if (!convInfo.isNotASwarm())
+        if (convInfo.isSwarm())
             return;
         auto& contact = accInfo->contactModel->getContact(contactUri);
         bool isPending = contact.profileInfo.type == profile::Type::TEMPORARY;
@@ -811,6 +811,6 @@ MessagesAdapter::loadMessages(int n)
     auto convOpt = convModel->getConversationForUid(currentConvUid_);
     if (!convOpt)
         return;
-    if (!convOpt->get().isNotASwarm() && !convOpt->get().allMessagesLoaded)
+    if (convOpt->get().isSwarm() && !convOpt->get().allMessagesLoaded)
         convModel->loadConversationMessages(convOpt->get().uid, n);
 }
diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp
index d8e8e0f2c326c9d7d4b2187a2cf8441bc6aa59eb..ab968a6b6cf80f366a77406d36499cf234fb6921 100644
--- a/src/qmlregister.cpp
+++ b/src/qmlregister.cpp
@@ -45,6 +45,7 @@
 #include "api/newdevicemodel.h"
 #include "api/datatransfermodel.h"
 #include "api/pluginmodel.h"
+#include "api/conversation.h"
 
 #include <QMetaType>
 #include <QQmlEngine>
@@ -134,6 +135,7 @@ registerTypes()
     QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::interaction::staticMetaObject, "Interaction");
     QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::video::staticMetaObject, "Video");
     QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::profile::staticMetaObject, "Profile");
+    QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::conversation::staticMetaObject, "Conversation");
 
     // Same as QML_REGISTERUNCREATABLE but omit the namespace in Qml
     QML_REGISTERUNCREATABLE_IN_NAMESPACE(NewAccountModel, lrc::api);