diff --git a/loader/src/server/DownloadManager.cpp b/loader/src/server/DownloadManager.cpp
index 75e9b535..a2c9cf6d 100644
--- a/loader/src/server/DownloadManager.cpp
+++ b/loader/src/server/DownloadManager.cpp
@@ -1,4 +1,6 @@
 #include "DownloadManager.hpp"
+#include "Geode/loader/Mod.hpp"
+#include <optional>
 
 using namespace server;
 
@@ -18,6 +20,7 @@ public:
     std::string m_id;
     std::optional<VersionInfo> m_version;
     std::optional<DependencyFor> m_dependencyFor;
+    std::optional<std::string> m_replacesMod;
     DownloadStatus m_status;
     EventListener<ServerRequest<ServerModVersion>> m_infoListener;
     EventListener<web::WebTask> m_downloadListener;
@@ -25,11 +28,13 @@ public:
     Impl(
         std::string const& id,
         std::optional<VersionInfo> const& version,
-        std::optional<DependencyFor> const& dependencyFor
+        std::optional<DependencyFor> const& dependencyFor,
+        std::optional<std::string> const& replacesMod
     )
       : m_id(id),
         m_version(version),
         m_dependencyFor(dependencyFor),
+        m_replacesMod(replacesMod),
         m_status(DownloadStatusFetching {
             .percentage = 0,
         })
@@ -97,8 +102,9 @@ public:
             if (auto value = event->getValue()) {
                 if (value->ok()) {
                     bool removingInstalledWasError = false;
-                    // If this was an update, delete the old file first
-                    if (auto mod = Loader::get()->getInstalledMod(m_id)) {
+                    bool disablingReplacedWasError = false;
+                    std::string id = m_replacesMod.has_value() ? m_replacesMod.value() : m_id;
+                    if (auto mod = Loader::get()->getInstalledMod(id)) {
                         std::error_code ec;
                         std::filesystem::remove(mod->getPackagePath(), ec);
                         if (ec) {
@@ -108,6 +114,7 @@ public:
                             };
                         }
                     }
+                    // If this was an update, delete the old file first
                     if (!removingInstalledWasError) {
                         auto ok = file::writeBinary(dirs::getModsDir() / (m_id + ".geode"), value->data());
                         if (!ok) {
@@ -146,8 +153,9 @@ public:
 ModDownload::ModDownload(
     std::string const& id,
     std::optional<VersionInfo> const& version,
-    std::optional<DependencyFor> const& dependencyFor
-) : m_impl(std::make_shared<Impl>(id, version, dependencyFor)) {}
+    std::optional<DependencyFor> const& dependencyFor,
+    std::optional<std::string> const& replacesMod
+) : m_impl(std::make_shared<Impl>(id, version, dependencyFor, replacesMod)) {}
 
 void ModDownload::confirm() {
     m_impl->confirm();
@@ -156,6 +164,9 @@ void ModDownload::confirm() {
 std::optional<DependencyFor> ModDownload::getDependencyFor() const {
     return m_impl->m_dependencyFor;
 }
+std::optional<std::string> ModDownload::getReplacesMod() const {
+    return m_impl->m_replacesMod;
+}
 bool ModDownload::isDone() const {
     return std::holds_alternative<DownloadStatusDone>(m_impl->m_status);
 }
@@ -225,7 +236,8 @@ void ModDownload::cancel() {
 std::optional<ModDownload> ModDownloadManager::startDownload(
     std::string const& id,
     std::optional<VersionInfo> const& version,
-    std::optional<DependencyFor> const& dependencyFor
+    std::optional<DependencyFor> const& dependencyFor,
+    std::optional<std::string> const& replacesMod
 ) {
     // If this mod has already been succesfully downloaded or is currently
     // being downloaded, return as you can't download multiple versions of the
@@ -242,7 +254,7 @@ std::optional<ModDownload> ModDownloadManager::startDownload(
 
     // Start a new download by constructing a ModDownload (which starts the
     // download)
-    m_impl->m_downloads.emplace(id, ModDownload(id, version, dependencyFor));
+    m_impl->m_downloads.emplace(id, ModDownload(id, version, dependencyFor, replacesMod));
     return m_impl->m_downloads.at(id);
 }
 void ModDownloadManager::cancelAll() {
@@ -261,7 +273,16 @@ void ModDownloadManager::startUpdateAll() {
             if (result->isOk()) {
                 for (auto& mod : result->unwrap()) {
                     if (mod.hasUpdateForInstalledMod()) {
-                        this->startDownload(mod.id, std::nullopt);
+                        if (mod.replacement.has_value()) {
+                            this->startDownload(
+                                mod.replacement.value().id,
+                                mod.replacement.value().version,
+                                std::nullopt,
+                                mod.id
+                            );
+                        } else {
+                            this->startDownload(mod.id, std::nullopt);
+                        }
                     }
                 }
             }
diff --git a/loader/src/server/DownloadManager.hpp b/loader/src/server/DownloadManager.hpp
index 4b443267..569564b9 100644
--- a/loader/src/server/DownloadManager.hpp
+++ b/loader/src/server/DownloadManager.hpp
@@ -65,7 +65,8 @@ namespace server {
         ModDownload(
             std::string const& id,
             std::optional<VersionInfo> const& version,
-            std::optional<DependencyFor> const& dependencyFor
+            std::optional<DependencyFor> const& dependencyFor,
+            std::optional<std::string> const& replacesMod
         );
 
         friend class ModDownloadManager;
@@ -77,6 +78,7 @@ namespace server {
         bool isDone() const;
         bool isActive() const;
         bool canRetry() const;
+        std::optional<std::string> getReplacesMod() const;
         std::optional<DependencyFor> getDependencyFor() const;
         std::string getID() const;
         DownloadStatus getStatus() const;
@@ -100,7 +102,8 @@ namespace server {
         std::optional<ModDownload> startDownload(
             std::string const& id,
             std::optional<VersionInfo> const& version,
-            std::optional<DependencyFor> const& dependencyFor = std::nullopt
+            std::optional<DependencyFor> const& dependencyFor = std::nullopt,
+            std::optional<std::string> const& replacesMod = std::nullopt
         );
         void startUpdateAll();
         void confirmAll();
diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp
index 949b298c..9d2c72e2 100644
--- a/loader/src/server/Server.cpp
+++ b/loader/src/server/Server.cpp
@@ -348,6 +348,23 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
     return Ok(res);
 }
 
+Result<ServerModReplacement> ServerModReplacement::parse(matjson::Value const& raw) {
+    log::info("entered replacement parse");
+    auto json = raw;
+    log::info("{}", json);
+    JsonChecker checker(json);
+    auto root = checker.root("ServerModReplacement").obj();
+    auto res = ServerModReplacement();
+
+    root.needs("id").into(res.id);
+    root.needs("version").into(res.version);
+
+    if (root.isError()) {
+        return Err(root.getError());
+    }
+    return Ok(res);
+}
+
 Result<ServerModUpdate> ServerModUpdate::parse(matjson::Value const& raw) {
     auto json = raw;
     JsonChecker checker(json);
@@ -357,6 +374,9 @@ Result<ServerModUpdate> ServerModUpdate::parse(matjson::Value const& raw) {
 
     root.needs("id").into(res.id);
     root.needs("version").into(res.version);
+    if (root.has("replacement")) {
+        GEODE_UNWRAP_INTO(res.replacement, ServerModReplacement::parse(root.has("replacement").json()));
+    }
 
     // Check for errors and return result
     if (root.isError()) {
@@ -390,7 +410,7 @@ Result<std::vector<ServerModUpdate>> ServerModUpdate::parseList(matjson::Value c
 
 bool ServerModUpdate::hasUpdateForInstalledMod() const {
     if (auto mod = Loader::get()->getInstalledMod(this->id)) {
-        return mod->getVersion() < this->version;
+        return mod->getVersion() < this->version || this->replacement.has_value();
     }
     return false;
 }
@@ -739,7 +759,10 @@ ServerRequest<std::optional<ServerModUpdate>> server::checkUpdates(Mod* mod) {
         [mod](Result<std::vector<ServerModUpdate>, ServerError>* result) -> Result<std::optional<ServerModUpdate>, ServerError> {
             if (result->isOk()) {
                 for (auto& update : result->unwrap()) {
-                    if (update.id == mod->getID() && update.version > mod->getVersion()) {
+                    if (
+                        update.id == mod->getID() && 
+                        (update.version > mod->getVersion() || update.replacement.has_value())
+                    ) {
                         return Ok(update);
                     }
                 }
diff --git a/loader/src/server/Server.hpp b/loader/src/server/Server.hpp
index 45b5c3dd..4c0b111f 100644
--- a/loader/src/server/Server.hpp
+++ b/loader/src/server/Server.hpp
@@ -1,8 +1,11 @@
 #pragma once
 
+#include "Geode/utils/VersionInfo.hpp"
 #include <Geode/DefaultInclude.hpp>
 #include <Geode/utils/web2.hpp>
 #include <Geode/loader/SettingEvent.hpp>
+#include <matjson.hpp>
+#include <vector>
 
 using namespace geode::prelude;
 
@@ -34,10 +37,19 @@ namespace server {
 
         static Result<ServerModVersion> parse(matjson::Value const& json);
     };
+    
+    struct ServerModReplacement final {
+        std::string id;
+        VersionInfo version;
+        std::string download_link;
+
+        static Result<ServerModReplacement> parse(matjson::Value const& json);
+    };
 
     struct ServerModUpdate final {
         std::string id;
         VersionInfo version;
+        std::optional<ServerModReplacement> replacement;
 
         static Result<ServerModUpdate> parse(matjson::Value const& json);
         static Result<std::vector<ServerModUpdate>> parseList(matjson::Value const& json);
diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp
index bc177b29..790abf40 100644
--- a/loader/src/ui/mods/list/ModItem.cpp
+++ b/loader/src/ui/mods/list/ModItem.cpp
@@ -325,7 +325,12 @@ void ModItem::updateState() {
         update && !(download && (download->isActive() || download->isDone()))
     ) {
         m_updateBtn->setVisible(true);
-        auto updateString = m_source.getMetadata().getVersion().toString() + " -> " + update->version.toString();
+        std::string updateString = "";
+        if (update->replacement.has_value()) {
+            updateString += " -> " + update->replacement.value().id;
+        } else {
+            updateString += m_source.getMetadata().getVersion().toVString() + " -> " + update->version.toVString();
+        }
         m_versionLabel->setString(updateString.c_str());
         m_versionLabel->setColor(to3B(ColorProvider::get()->color("mod-list-version-label-updates-available"_spr)));
 
@@ -439,6 +444,17 @@ void ModItem::onEnable(CCObject*) {
     UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
 }
 void ModItem::onInstall(CCObject*) {
+    if (auto updates = m_source.hasUpdates()) {
+        if (updates->replacement.has_value()) {
+            server::ModDownloadManager::get()->startDownload(
+                updates->replacement->id,
+                updates->replacement->version,
+                std::nullopt,
+                m_source.getID()
+            );
+            return;
+        }
+    }
     server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
 }
 void ModItem::onDevelopers(CCObject*) {
diff --git a/loader/src/ui/mods/popups/ConfirmInstall.cpp b/loader/src/ui/mods/popups/ConfirmInstall.cpp
index 0bc105aa..fe52c2e7 100644
--- a/loader/src/ui/mods/popups/ConfirmInstall.cpp
+++ b/loader/src/ui/mods/popups/ConfirmInstall.cpp
@@ -7,6 +7,7 @@ using namespace server;
 void askConfirmModInstalls() {
     struct ToConfirm final {
         size_t modCount = 0;
+        size_t replacementCount = 0;
         size_t dependencyCount = 0;
         std::unordered_set<Mod*> toDisable;
         std::unordered_set<Mod*> toEnable;
@@ -23,6 +24,9 @@ void askConfirmModInstalls() {
             }
             else {
                 toConfirm.modCount += 1;
+                if (download.getReplacesMod()) {
+                    toConfirm.replacementCount += 1;
+                }
 
                 // Since the user has already explicitly chosen to download these mods, we 
                 // are going to assume they want these mods enabled over already installed 
@@ -68,10 +72,10 @@ void askConfirmModInstalls() {
     createQuickPopup(
         "Confirm Install",
         fmt::format(
-            "<cj>{}</c> mods will be installed, of which <cy>{}</c> are <cy>dependencies</c>.\n"
+            "<cj>{}</c> mods will be installed, of which <cy>{}</c> are <cy>dependencies</c> and <cy>{}</c> are <cy>replacements</c>.\n"
             "<cr>{} mods will be force-disabled, as they are incompatible</c>: {}\n"
             "<cg>{} mods will be force-enabled</c>: {}",
-            toConfirm.modCount, toConfirm.dependencyCount,
+            toConfirm.modCount, toConfirm.dependencyCount, toConfirm.replacementCount,
             toConfirm.toDisable.size(), joinModsToIDs(toConfirm.toDisable),
             toConfirm.toEnable.size(), joinModsToIDs(toConfirm.toEnable)
         ),