diff --git a/.eslintignore b/.eslintignore
index e1b0ceb50ca..1cdde75cbc2 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -7,3 +7,4 @@ test/end-to-end-tests/lib/
 src/component-index.js
 # Auto-generated file
 src/modules.ts
+src/modules.js
diff --git a/.gitignore b/.gitignore
index 685a2cc3172..3e9dc5e135f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ electron/pub
 /coverage
 # Auto-generated file
 /src/modules.ts
+/src/modules.js
 /build_config.yaml
 /book
 /index.html
diff --git a/.prettierignore b/.prettierignore
index 418329cf287..46b1ac5b549 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -17,6 +17,7 @@ electron/pub
 /coverage
 # Auto-generated file
 /src/modules.ts
+/src/modules.js
 /src/i18n/strings
 /build_config.yaml
 # Raises an error because it contains a template var breaking the script tag
diff --git a/module_system/installer.ts b/module_system/installer.ts
index 4e677b7d674..48dad8d9089 100644
--- a/module_system/installer.ts
+++ b/module_system/installer.ts
@@ -23,10 +23,9 @@ const MODULES_TS_HEADER = `
  * You are not a salmon.
  */
 
-import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
 `;
 const MODULES_TS_DEFINITIONS = `
-export const INSTALLED_MODULES: RuntimeModule[] = [];
+export const INSTALLED_MODULES = [];
 `;
 
 export function installer(config: BuildConfig): void {
@@ -78,8 +77,8 @@ export function installer(config: BuildConfig): void {
             return; // hit the finally{} block before exiting
         }
 
-        // If we reach here, everything seems fine. Write modules.ts and log some output
-        // Note: we compile modules.ts in two parts for developer friendliness if they
+        // If we reach here, everything seems fine. Write modules.js and log some output
+        // Note: we compile modules.js in two parts for developer friendliness if they
         // happen to look at it.
         console.log("The following modules have been installed: ", installedModules);
         let modulesTsHeader = MODULES_TS_HEADER;
@@ -193,5 +192,5 @@ function isModuleVersionCompatible(ourApiVersion: string, moduleApiVersion: stri
 }
 
 function writeModulesTs(content: string): void {
-    fs.writeFileSync("./src/modules.ts", content, "utf-8");
+    fs.writeFileSync("./src/modules.js", content, "utf-8");
 }
diff --git a/src/modules.d.ts b/src/modules.d.ts
new file mode 100644
index 00000000000..0f804f17cd3
--- /dev/null
+++ b/src/modules.d.ts
@@ -0,0 +1,13 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { ModuleApi, RuntimeModule } from "@matrix-org/react-sdk-module-api";
+
+declare module "./modules.js" {
+    export type RuntimeModuleConstructor = { new (api: ModuleApi): RuntimeModule };
+    export const INSTALLED_MODULES: RuntimeModuleConstructor[];
+}
diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts
index 7f5affab0da..4eb5aaf654d 100644
--- a/src/stores/widgets/StopGapWidgetDriver.ts
+++ b/src/stores/widgets/StopGapWidgetDriver.ts
@@ -65,7 +65,7 @@ import { navigateToPermalink } from "../../utils/permalinks/navigator";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { ModuleRunner } from "../../modules/ModuleRunner";
 import SettingsStore from "../../settings/SettingsStore";
-import { Media } from "../../customisations/Media";
+import { mediaFromMxc } from "../../customisations/Media";
 
 // TODO: Purge this from the universe
 
@@ -684,7 +684,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
      */
     public async downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> {
         const client = MatrixClientPeg.safeGet();
-        const media = new Media({ mxc: contentUri }, client);
+        const media = mediaFromMxc(contentUri, client);
         const response = await media.downloadSource();
         const blob = await response.blob();
         return { file: blob };
diff --git a/src/vector/init.tsx b/src/vector/init.tsx
index 34f5b9fc08c..bb4a128d80e 100644
--- a/src/vector/init.tsx
+++ b/src/vector/init.tsx
@@ -125,12 +125,8 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
 }
 
 export async function loadModules(): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore - this path is created at runtime and therefore won't exist at typecheck time
-    const { INSTALLED_MODULES } = await import("../modules");
+    const { INSTALLED_MODULES } = await import("../modules.js");
     for (const InstalledModule of INSTALLED_MODULES) {
-        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-        // @ts-ignore - we know the constructor exists even if TypeScript can't be convinced of that
         ModuleRunner.instance.registerModule((api) => new InstalledModule(api));
     }
 }