diff --git a/package.json b/package.json
index 2b1a5e4cc9..945ea6299b 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
   "devDependencies": {
     "@ark/attest": "0.11.0",
     "@changesets/cli": "^2.27.7",
+    "@latticexyz/cli": "workspace:*",
     "@types/node": "^18.15.11",
     "@typescript-eslint/eslint-plugin": "7.1.1",
     "@typescript-eslint/parser": "7.1.1",
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 14f572d5c7..ef512d2758 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -49,6 +49,7 @@
     "@latticexyz/store": "workspace:*",
     "@latticexyz/utils": "workspace:*",
     "@latticexyz/world": "workspace:*",
+    "@latticexyz/world-modules": "workspace:*",
     "abitype": "1.0.0",
     "asn1.js": "^5.4.1",
     "chalk": "^5.0.1",
@@ -84,8 +85,5 @@
     "tsup": "^6.7.0",
     "tsx": "^3.12.6",
     "vitest": "0.34.6"
-  },
-  "peerDependencies": {
-    "@latticexyz/world-modules": "*"
   }
 }
diff --git a/packages/cli/src/deploy/configToModules.ts b/packages/cli/src/deploy/configToModules.ts
index a093c2c96a..a1fb9ebc02 100644
--- a/packages/cli/src/deploy/configToModules.ts
+++ b/packages/cli/src/deploy/configToModules.ts
@@ -6,26 +6,39 @@ import { bytesToHex } from "viem";
 import { createPrepareDeploy } from "./createPrepareDeploy";
 import { World } from "@latticexyz/world";
 import { getContractArtifact } from "../utils/getContractArtifact";
-import { knownModuleArtifacts } from "../utils/knownModuleArtifacts";
+import { importContractArtifact } from "../utils/importContractArtifact";
 import { resolveWithContext } from "@latticexyz/world/internal";
+import metadataModule from "@latticexyz/world-modules/out/MetadataModule.sol/MetadataModule.json" assert { type: "json" };
+
+/** Please don't add to this list! These are kept for backwards compatibility and assumes the downstream project has this module installed as a dependency. */
+const knownModuleArtifacts = {
+  KeysWithValueModule: "@latticexyz/world-modules/out/KeysWithValueModule.sol/KeysWithValueModule.json",
+  KeysInTableModule: "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json",
+  UniqueEntityModule: "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json",
+  Unstable_CallWithSignatureModule:
+    "@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json",
+};
+
+const metadataModuleArtifact = getContractArtifact(metadataModule);
 
 export async function configToModules<config extends World>(
   config: config,
   // TODO: remove/replace `forgeOutDir`
   forgeOutDir: string,
 ): Promise<readonly Module[]> {
-  const configModules = [
+  const defaultModules: Module[] = [
     {
-      name: "metadata",
-      artifactPath: "@latticexyz/world-modules/out/MetadataModule.sol/MetadataModule.json",
-      root: false,
-      args: [],
+      name: "MetadataModule",
+      installAsRoot: false,
+      installData: "0x",
+      prepareDeploy: createPrepareDeploy(metadataModuleArtifact.bytecode, metadataModuleArtifact.placeholders),
+      deployedBytecodeSize: metadataModuleArtifact.deployedBytecodeSize,
+      abi: metadataModuleArtifact.abi,
     },
-    ...config.modules,
   ];
 
   const modules = await Promise.all(
-    configModules.map(async (mod): Promise<Module> => {
+    config.modules.map(async (mod): Promise<Module> => {
       let artifactPath = mod.artifactPath;
 
       // Backwards compatibility
@@ -56,7 +69,7 @@ export async function configToModules<config extends World>(
       }
 
       const name = path.basename(artifactPath, ".json");
-      const artifact = await getContractArtifact({ artifactPath });
+      const artifact = await importContractArtifact({ artifactPath });
 
       // TODO: replace args with something more strongly typed
       const installArgs = mod.args
@@ -81,5 +94,5 @@ export async function configToModules<config extends World>(
     }),
   );
 
-  return modules;
+  return [...defaultModules, ...modules];
 }
diff --git a/packages/cli/src/utils/getContractArtifact.ts b/packages/cli/src/utils/getContractArtifact.ts
index 63d4abbd28..39d521baae 100644
--- a/packages/cli/src/utils/getContractArtifact.ts
+++ b/packages/cli/src/utils/getContractArtifact.ts
@@ -3,24 +3,6 @@ import { LibraryPlaceholder } from "../deploy/common";
 import { findPlaceholders } from "./findPlaceholders";
 import { z } from "zod";
 import { Abi as abiSchema } from "abitype/zod";
-import { createRequire } from "node:module";
-import { findUp } from "find-up";
-
-export type GetContractArtifactOptions = {
-  /**
-   * Path to `package.json` where `artifactPath`s are resolved relative to.
-   *
-   * Defaults to nearest `package.json` relative to `process.cwd()`.
-   */
-  packageJsonPath?: string;
-  /**
-   * Import path to contract's forge/solc JSON artifact with the contract's compiled bytecode.
-   *
-   * This path is resolved using node's module resolution relative to `configPath`, so this supports both
-   * relative file paths (`../path/to/MyModule.json`) as well as JS import paths (`@latticexyz/world-contracts/out/CallWithSignatureModule.sol/CallWithSignatureModule.json`).
-   */
-  artifactPath: string;
-};
 
 export type GetContractArtifactResult = {
   bytecode: Hex;
@@ -49,24 +31,7 @@ const artifactSchema = z.object({
   abi: abiSchema,
 });
 
-export async function getContractArtifact({
-  packageJsonPath,
-  artifactPath,
-}: GetContractArtifactOptions): Promise<GetContractArtifactResult> {
-  let importedArtifact;
-  try {
-    const requirePath = packageJsonPath ?? (await findUp("package.json", { cwd: process.cwd() }));
-    if (!requirePath) throw new Error("Could not find package.json to import relative to.");
-
-    const require = createRequire(requirePath);
-    importedArtifact = require(artifactPath);
-  } catch (error) {
-    console.error();
-    console.error("Could not import contract artifact at", artifactPath);
-    console.error();
-    throw error;
-  }
-
+export function getContractArtifact(importedArtifact: unknown): GetContractArtifactResult {
   // TODO: improve errors or replace with arktype?
   const artifact = artifactSchema.parse(importedArtifact);
   const placeholders = findPlaceholders(artifact.bytecode.linkReferences);
diff --git a/packages/cli/src/utils/importContractArtifact.ts b/packages/cli/src/utils/importContractArtifact.ts
new file mode 100644
index 0000000000..d44fa3791a
--- /dev/null
+++ b/packages/cli/src/utils/importContractArtifact.ts
@@ -0,0 +1,40 @@
+import { createRequire } from "node:module";
+import { findUp } from "find-up";
+import { GetContractArtifactResult } from "./getContractArtifact";
+
+export type ImportContractArtifactOptions = {
+  /**
+   * Path to `package.json` where `artifactPath`s are resolved relative to.
+   *
+   * Defaults to nearest `package.json` relative to `process.cwd()`.
+   */
+  packageJsonPath?: string;
+  /**
+   * Import path to contract's forge/solc JSON artifact with the contract's compiled bytecode.
+   *
+   * This path is resolved using node's module resolution relative to `configPath`, so this supports both
+   * relative file paths (`../path/to/MyModule.json`) as well as JS import paths (`@latticexyz/world-contracts/out/CallWithSignatureModule.sol/CallWithSignatureModule.json`).
+   */
+  artifactPath: string;
+};
+
+export async function importContractArtifact({
+  packageJsonPath,
+  artifactPath,
+}: ImportContractArtifactOptions): Promise<GetContractArtifactResult> {
+  let importedArtifact;
+  try {
+    const requirePath = packageJsonPath ?? (await findUp("package.json", { cwd: process.cwd() }));
+    if (!requirePath) throw new Error("Could not find package.json to import relative to.");
+
+    const require = createRequire(requirePath);
+    importedArtifact = require(artifactPath);
+  } catch (error) {
+    console.error();
+    console.error("Could not import contract artifact at", artifactPath);
+    console.error();
+    throw error;
+  }
+
+  return getContractArtifact(importedArtifact);
+}
diff --git a/packages/cli/src/utils/knownModuleArtifacts.ts b/packages/cli/src/utils/knownModuleArtifacts.ts
deleted file mode 100644
index a71c4fa2e4..0000000000
--- a/packages/cli/src/utils/knownModuleArtifacts.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/** @deprecated Please don't add to this list! These are kept for backwards compatibility and assumes the downstream project has this module installed as a dependency. */
-export const knownModuleArtifacts = {
-  KeysWithValueModule: "@latticexyz/world-modules/out/KeysWithValueModule.sol/KeysWithValueModule.json",
-  KeysInTableModule: "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json",
-  UniqueEntityModule: "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json",
-  Unstable_CallWithSignatureModule:
-    "@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json",
-};
diff --git a/packages/world-modules/package.json b/packages/world-modules/package.json
index 736cef22eb..d7c982bbd6 100644
--- a/packages/world-modules/package.json
+++ b/packages/world-modules/package.json
@@ -48,8 +48,6 @@
     "zod": "3.23.8"
   },
   "devDependencies": {
-    "@latticexyz/abi-ts": "workspace:*",
-    "@latticexyz/cli": "workspace:*",
     "@latticexyz/gas-report": "workspace:*",
     "@types/ejs": "^3.1.1",
     "@types/mocha": "^9.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 313cf236ba..5610b4e823 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
       '@changesets/cli':
         specifier: ^2.27.7
         version: 2.27.7
+      '@latticexyz/cli':
+        specifier: workspace:*
+        version: link:packages/cli
       '@types/node':
         specifier: ^18.15.11
         version: 18.15.11
@@ -164,8 +167,8 @@ importers:
         specifier: workspace:*
         version: link:../world
       '@latticexyz/world-modules':
-        specifier: '*'
-        version: 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)
+        specifier: workspace:*
+        version: link:../world-modules
       abitype:
         specifier: 1.0.0
         version: 1.0.0(typescript@5.4.2)(zod@3.23.8)
@@ -1103,12 +1106,6 @@ importers:
         specifier: 3.23.8
         version: 3.23.8
     devDependencies:
-      '@latticexyz/abi-ts':
-        specifier: workspace:*
-        version: link:../abi-ts
-      '@latticexyz/cli':
-        specifier: workspace:*
-        version: link:../cli
       '@latticexyz/gas-report':
         specifier: workspace:*
         version: link:../gas-report
@@ -2156,35 +2153,6 @@ packages:
     resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==}
     engines: {node: '>= 12'}
 
-  '@latticexyz/common@2.1.0':
-    resolution: {integrity: sha512-tpv4trVwDUqJpAGfK+j0Tav/U6x1oIeFCFxn6wbS86CRsh7uxWhe+c0tGn2why25dGzTXAlvk5qqdjAO1oHy8w==}
-    peerDependencies:
-      '@aws-sdk/client-kms': 3.x
-      asn1.js: 5.x
-    peerDependenciesMeta:
-      '@aws-sdk/client-kms':
-        optional: true
-      asn1.js:
-        optional: true
-
-  '@latticexyz/config@2.1.0':
-    resolution: {integrity: sha512-oMGQJrw2PQiHFtovyKYXqFPcuWdE63DiWxgc58CqwszVEOs+svqGmcgbpbs/U+XpRXlUj1fdr6UasQ0cpMSFnQ==}
-
-  '@latticexyz/protocol-parser@2.1.0':
-    resolution: {integrity: sha512-2RuIk3sLx3gQyAECS5/4BPP+xAd9ivw4wtF29UopukmOl2WxN7v30aDdRcCq4jlBphM/JhvWkLW9j/MOszCmqg==}
-
-  '@latticexyz/schema-type@2.1.0':
-    resolution: {integrity: sha512-hzOSGxeI6sBIQoEY2zF08A4ghAu2aLDeNKdGJuxvkNOpqhZdkjHJ8a0F0ZbfQFv6E29zCZlZJA+ypGscuMQDVA==}
-
-  '@latticexyz/store@2.1.0':
-    resolution: {integrity: sha512-ElpBm/FJHqzdbuYmoPVYHoqYYHyPmEvEle7kxnk+/7sNvJwHQIg+y7aTQ+/SzPrU5+miXqtMtjzXhfCOeJI1wg==}
-
-  '@latticexyz/world-modules@2.1.0':
-    resolution: {integrity: sha512-A/i1GNipFVFccuq8Uvna2AcPCEw5Qeql0uB7VsQ5fsqynuPAtHBz3WqJZz+ij7/xceLeHCyWJ87bL+xDlUd7eg==}
-
-  '@latticexyz/world@2.1.0':
-    resolution: {integrity: sha512-y7AIXPfXGHHoWO/G3VN/c5gA9BFFehFWPzchKEttOVfOw3sJK6b+wJyLHW+wjRs9Wcz+DA14mWU/WGy3oWYAVQ==}
-
   '@manypkg/find-root@1.1.0':
     resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
 
@@ -8079,125 +8047,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@latticexyz/common@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      '@solidity-parser/parser': 0.16.0
-      debug: 4.3.4
-      execa: 7.2.0
-      p-queue: 7.4.1
-      p-retry: 5.1.2
-      prettier: 3.2.5
-      prettier-plugin-solidity: 1.3.1(prettier@3.2.5)
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    optionalDependencies:
-      '@aws-sdk/client-kms': 3.556.0
-      asn1.js: 5.4.1
-    transitivePeerDependencies:
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-      - zod
-
-  '@latticexyz/config@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      '@ark/util': 0.1.2
-      '@latticexyz/common': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      esbuild: 0.17.17
-      find-up: 6.3.0
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    transitivePeerDependencies:
-      - '@aws-sdk/client-kms'
-      - asn1.js
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-      - zod
-
-  '@latticexyz/protocol-parser@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      '@latticexyz/common': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/config': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      abitype: 1.0.0(typescript@5.4.2)(zod@3.23.8)
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    transitivePeerDependencies:
-      - '@aws-sdk/client-kms'
-      - asn1.js
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-      - zod
-
-  '@latticexyz/schema-type@2.1.0(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      abitype: 1.0.0(typescript@5.4.2)(zod@3.23.8)
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    transitivePeerDependencies:
-      - bufferutil
-      - typescript
-      - utf-8-validate
-      - zod
-
-  '@latticexyz/store@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      '@ark/util': 0.1.2
-      '@latticexyz/common': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/config': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/protocol-parser': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      abitype: 1.0.0(typescript@5.4.2)(zod@3.23.8)
-      arktype: 1.0.29-alpha
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    transitivePeerDependencies:
-      - '@aws-sdk/client-kms'
-      - asn1.js
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-      - zod
-
-  '@latticexyz/world-modules@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)':
-    dependencies:
-      '@latticexyz/common': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/config': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/store': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/world': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      zod: 3.23.8
-    transitivePeerDependencies:
-      - '@aws-sdk/client-kms'
-      - asn1.js
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-
-  '@latticexyz/world@2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)':
-    dependencies:
-      '@ark/util': 0.1.2
-      '@latticexyz/common': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/config': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/protocol-parser': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/schema-type': 2.1.0(typescript@5.4.2)(zod@3.23.8)
-      '@latticexyz/store': 2.1.0(@aws-sdk/client-kms@3.556.0)(asn1.js@5.4.1)(typescript@5.4.2)(zod@3.23.8)
-      abitype: 1.0.0(typescript@5.4.2)(zod@3.23.8)
-      arktype: 1.0.29-alpha
-      viem: 2.9.20(typescript@5.4.2)(zod@3.23.8)
-    transitivePeerDependencies:
-      - '@aws-sdk/client-kms'
-      - asn1.js
-      - bufferutil
-      - supports-color
-      - typescript
-      - utf-8-validate
-      - zod
-
   '@manypkg/find-root@1.1.0':
     dependencies:
       '@babel/runtime': 7.21.0