diff --git c/src/CMakeLists.txt i/src/CMakeLists.txt
index efeea0971a..e21b01f346 100644
--- c/src/CMakeLists.txt
+++ i/src/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(KParts_LIB_SRCS
     partbase.cpp
     part.cpp
+    partloader.cpp
     openurlarguments.cpp
     readonlypart.cpp
     readwritepart.cpp
diff --git c/src/partloader.cpp i/src/partloader.cpp
new file mode 100644
index 0000000000..f58ca0e9b7
--- /dev/null
+++ i/src/partloader.cpp
@@ -0,0 +1,84 @@
+/* This file is part of the KDE project
+   Copyright (C) 2020 David Faure <faure@kde.org>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#include "partloader.h"
+
+#include <KPluginLoader>
+#include <KPluginFactory>
+#include <KLocalizedString>
+#include <QDebug>
+
+QVector<KPluginMetaData> KParts::PartLoader::partsForMimeType(const QString &mimeType)
+{
+    auto supportsMime = [&](const KPluginMetaData &md) { return md.supportsMimeType(mimeType); };
+    QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/parts"), supportsMime);
+
+    auto byInitialPreference = [](const KPluginMetaData &left, const KPluginMetaData &right) { return left.initialPreference() > right.initialPreference(); };
+    std::sort(plugins.begin(), plugins.end(), byInitialPreference);
+
+    // TODO parse mimeapps.list
+
+    for (const KPluginMetaData &plugin : plugins) {
+        qDebug() << plugin.fileName() << plugin.initialPreference();
+    }
+    return plugins;
+}
+
+// KF6 TODO: make create(const char* iface...) public in KPluginFactory, remove this hack
+class KPluginFactoryHack : public KPluginFactory
+{
+public:
+     QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) override {
+         return KPluginFactory::create(iface, parentWidget, parent, args, keyword);
+     }
+};
+
+QObject *KParts::PartLoader::Private::createPartInstanceForMimeTypeHelper(const char *iface, const QString &mimeType, QWidget *parentWidget, QObject *parent, QString *error)
+{
+    const QVector<KPluginMetaData> plugins = KParts::PartLoader::partsForMimeType(mimeType);
+    for (const KPluginMetaData &plugin : plugins) {
+        KPluginLoader pluginLoader(plugin.fileName());
+        // ## How can the keyword feature work with JSON metadata?
+        // ## Several desktop files could point to the same .so file, but a .so file only has one metadata builtin.
+        const QString pluginKeyword;
+        KPluginFactory *factory = pluginLoader.factory();
+        if (factory) {
+            QObject *obj = static_cast<KPluginFactoryHack *>(factory)->create(iface, parentWidget, parent, QVariantList(), pluginKeyword);
+            if (error) {
+                if (!obj) {
+                    *error = i18n("The plugin '%1' does not provide an interface '%2' with keyword '%3'",
+                                  plugin.fileName(), QString::fromLatin1(iface), pluginKeyword);
+                } else {
+                    error->clear();
+                }
+            }
+            if (obj) {
+                return obj;
+            }
+        } else if (error) {
+            *error = pluginLoader.errorString();
+        }
+        pluginLoader.unload();
+    }
+    if (error) {
+        *error = i18n("No part was found for mimeType %1", mimeType);
+    }
+    return nullptr;
+}
+
diff --git c/src/partloader.h i/src/partloader.h
new file mode 100644
index 0000000000..eae04c16fc
--- /dev/null
+++ i/src/partloader.h
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+   Copyright (C) 2020 David Faure <faure@kde.org>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KPARTS_PARTLOADER_H
+#define KPARTS_PARTLOADER_H
+
+#include <kparts/kparts_export.h>
+#include <QVector>
+#include <QObject>
+#include <KPluginMetaData>
+
+namespace KParts
+{
+
+/**
+ * Helper methods for locating and loading parts.
+ * This is based upon KPluginLoader and KPluginFactory, but it takes
+ * care of querying by mimetype, sorting the available parts by builtin
+ * preference and by user preference.
+ */
+namespace PartLoader
+{
+    namespace Private
+    {
+        KPARTS_EXPORT QObject *createPartInstanceForMimeTypeHelper(const char *iface, const QString &mimeType, QWidget *parentWidget, QObject *parent, QString *error);
+    }
+
+    /**
+     * Locate all available KParts for a mimetype.
+     * @return a list of plugin metadata, sorted by preference.
+     * This takes care both of the builtin preference (set by developers)
+     * and of user preference (stored in mimeapps.list).
+     *
+     * This uses KPluginLoader::findPlugins, i.e. it requires the parts to
+     * provide the metadata as JSON embedded into the plugin.
+     */
+    KPARTS_EXPORT QVector<KPluginMetaData> partsForMimeType(const QString &mimeType);
+
+    template <class T>
+    static T *createPartInstanceForMimeType(const QString &mimeType, QWidget *parentWidget = nullptr, QObject *parent = nullptr,
+                                            QString *error = nullptr)
+    {
+        QObject *o = Private::createPartInstanceForMimeTypeHelper(T::staticMetaObject.className(), mimeType, parentWidget, parent, error);
+        T *part = qobject_cast<T *>(o);
+        if (!part) {
+            delete o;
+        }
+        return part;
+    }
+
+} // namespace
+} // namespace
+
+#endif
diff --git c/tests/partviewer.cpp i/tests/partviewer.cpp
index ba4e588e4d..6b1f7667ed 100644
--- c/tests/partviewer.cpp
+++ i/tests/partviewer.cpp
@@ -24,6 +24,7 @@
 #include <kactioncollection.h>
 #include <klocalizedstring.h>
 #include <kmimetypetrader.h>
+#include <partloader.h>
 
 #include <qmimedatabase.h>
 #include <QAction>
@@ -60,9 +61,8 @@ void PartViewer::openUrl(const QUrl &url)
     delete m_part;
     QMimeDatabase db;
     const QString mimeType = db.mimeTypeForUrl(url).name();
-    m_part = KMimeTypeTrader::self()->createPartInstanceFromQuery<KParts::ReadOnlyPart>(mimeType,
-             this,
-             this);
+    m_part = KParts::PartLoader::createPartInstanceForMimeType<KParts::ReadOnlyPart>(mimeType,
+             this, this);
 
     if (m_part) {
 
