diff --git a/next-docs/pages/guides/extending-world.mdx b/next-docs/pages/guides/extending-world.mdx
index 2c25c1c3de..3d4741873e 100644
--- a/next-docs/pages/guides/extending-world.mdx
+++ b/next-docs/pages/guides/extending-world.mdx
@@ -1 +1,486 @@
# Extending World
+
+On this page you learn how to modify a `World` that is already deployed to the blockchain.
+If you want to learn how to modify a `World` before it is deployed, [see the hello world page](./hello-world).
+
+## The sample program
+
+To learn how to extend a `World`, we will extend the `Counter` example to allow users to leave a message while incrementing the counter.
+The steps to create the example `World` that we'll extend [are here](/templates/typescript/getting-started).
+You can either do it now, or wait until after you've created the extension resources.
+
+To extend the `World` with the message functionality requires adding several resources:
+
+- **Namespace**.
+ A [namescape](/world/namespaces-access-control) can contain tables and systems.
+ In most cases, the only way you would be able to extend a `World` that somebody else owns is to create your own namespace within that world.
+
+- **Table**.
+ A table to store the messages that have been sent.
+
+- **System**.
+ A system that updates the messages table and then calls `increment` to update the counter.
+
+## Create the Solidity code
+
+The easiest way to create the Solidity code is to use the MUD template:
+
+1. Create a new MUD application.
+ It does not matter which user interface option you choose.
+
+ ```sh copy
+ pnpm create mud@next extension
+ cd extension/packages/contracts
+ ```
+
+1. Edit `mud.config.ts` to include the definitions we need.
+
+ ```ts filename="mud.config.ts" copy
+ import { mudConfig } from "@latticexyz/world/register";
+
+ export default mudConfig({
+ namespace: "messaging",
+ tables: {
+ Messages: {
+ keySchema: {
+ counterValue: "uint32",
+ },
+ valueSchema: {
+ message: "string",
+ },
+ },
+ },
+ systems: {
+ MessageSystem: {
+ name: "MessageSystem",
+ openAccess: true,
+ },
+ },
+ });
+ ```
+
+1. Create `src/systems/MessageSystem.sol`.
+
+ ```solidity filename="MessageSystem.sol" copy
+ // SPDX-License-Identifier: MIT
+ pragma solidity >=0.8.21;
+
+ import { System } from "@latticexyz/world/src/System.sol";
+ import { Messages } from "../codegen/index.sol";
+
+ interface WorldWithIncrement {
+ function increment() external returns (uint32);
+ }
+
+ contract MessageSystem is System {
+ function incrementMessage(string memory message) public returns (uint32) {
+ uint32 newVal = WorldWithIncrement(_world()).increment();
+ Messages.set(newVal, message);
+ return newVal;
+ }
+ }
+ ```
+
+
+
+ Explanation
+
+ ```solidity
+ import { System } from "@latticexyz/world/src/System.sol";
+ import { Messages } from "../codegen/index.sol";
+ ```
+
+ These are the two main things the `System` needs to know: how to be a `System` and how to access the `Messages` table.
+
+ ```solidity
+ interface WorldWithIncrement {
+ function increment() external returns (uint32);
+ }
+ ```
+
+ This `System` needs to call `increment` on the `World` where it is implemented.
+ However, as an extension author you might not have access to the source code of any `System` that isn't part of your extension.
+
+ If you define your own interface for `World` you can add whatever function signatures are supported.
+ Note that in Ethereum [a function signature](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector) is the function name and its parameter types, it does not include the return type.
+ So if you are unsure of the return type that is not a huge problem.
+
+ ```solidity
+ contract MessageSystem is System {
+ function incrementMessage(string memory message) public returns (uint32) {
+ uint32 newVal = WorldWithIncrement(_world()).increment();
+ ```
+
+ This is how we use the `WorldWithIncrement` interface we created.
+ The [`_world()`](https://github.com/latticexyz/mud/blob/main/packages/world/src/WorldContext.sol#L38-L44) call gives us the address of the `World` that called us.
+ When we specify `WorldWithIncrement()`, we are telling Solidity that there is already a `WorldWithIncrement` at that address, and therefore we can use functions that are supported by `WorldWithIncrement`, such as `increment()`.
+
+ ```solidity
+ Messages.set(newVal, message);
+ ```
+
+ This is one way to create a record with the key `newVal` and the value `message`.
+
+ ```solidity
+ return newVal;
+ }
+ }
+ ```
+
+ When we are called by a user, an externally owned account, the return value is meaningless.
+ However, just as we call `increment()` from a contract and use the return value (instead of having to read `Counter` ourselves), some future onchain code might call `incrementMessage` and use the returned value.
+
+
+
+1. Remove files that are part of the template but don't make sense when our extension doesn't have its own counter.
+
+ ```sh copy
+ rm src/systems/IncrementSystem.sol test/*.t.sol script/PostDeploy.s.sol
+ ```
+
+1. Build and compile the Solidity code.
+
+ ```sh copy
+ pnpm build
+ ```
+
+## Deploy to the blockchain
+
+1. To have a `Counter` example `World` to modify, go to a separate command line window and create a blockchain with a `World` using [the TypeScript template](/template/typescript/getting-started) and start the execution.
+ Choose either the **react** user interface or the **vanilla** one.
+
+ ```sh copy
+ pnpm create mud@next extendMe
+ cd extendMe
+ pnpm dev
+ ```
+
+1. Back in the extension's `packages/contracts` directory, create a `.env` file with:
+
+ - `PRIVATE_KEY` - the private key of an account that has ETH on the blockchain.
+ - `WORLD_ADDRESS` - the address of the `World` to which you add the namespace.
+
+ If you are using the template with a fresh `pnpm dev`, then you can use this `.env`:
+
+ ```sh filename=".env" copy
+ # Anvil default private key for the second account
+ # (NOT the account that deployed the World)
+ PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
+
+ # Address for the world we are extending
+ WORLD_ADDRESS=0x6e9474e9c83676b9a71133ff96db43e7aa0a4342
+ ```
+
+1. Create this script in `script/MessagingExtension.s.sol`.
+
+ ```solidity filename="MessagingExtension.s.sol" copy
+ // SPDX-License-Identifier: MIT
+ pragma solidity >=0.8.21;
+
+ import { Script } from "forge-std/Script.sol";
+ import { console } from "forge-std/console.sol";
+ import { IBaseWorld } from "@latticexyz/world-modules/src/interfaces/IBaseWorld.sol";
+
+ import { WorldRegistrationSystem } from "@latticexyz/world/src/modules/core/implementations/WorldRegistrationSystem.sol";
+
+ // Create resource identifiers (for the namespace and system)
+ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
+
+ // For registering the table
+ import { Messages, MessagesTableId } from "../src/codegen/index.sol";
+ import { IStore } from "@latticexyz/store/src/IStore.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
+
+ // For deploying MessageSystem
+ import { MessageSystem } from "../src/systems/MessageSystem.sol";
+
+ contract MessagingExtension is Script {
+ function run() external {
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+
+ WorldRegistrationSystem world = WorldRegistrationSystem(worldAddress);
+ ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("messaging"));
+ ResourceId systemResource = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "messaging", "message");
+
+ vm.startBroadcast(deployerPrivateKey);
+ world.registerNamespace(namespaceResource);
+
+ StoreSwitch.setStoreAddress(worldAddress);
+ Messages.register();
+
+ MessageSystem messageSystem = new MessageSystem();
+ console.log("MessageSystem address: ", address(messageSystem));
+
+ world.registerSystem(systemResource, messageSystem, true);
+ world.registerFunctionSelector(systemResource, "incrementMessage(string)");
+
+ vm.stopBroadcast();
+ }
+ }
+ ```
+
+
+
+ Explanation
+
+ ```solidity
+ // SPDX-License-Identifier: MIT
+ pragma solidity >=0.8.21;
+ ```
+
+ Standard Solidity boilerplate.
+
+ ```solidity
+ import { Script } from "forge-std/Script.sol";
+ import { console } from "forge-std/console.sol";
+ ```
+
+ The definitions for [forge scripts](https://book.getfoundry.sh/reference/forge/forge-script) and [the console](https://book.getfoundry.sh/reference/forge-std/console-log).
+
+ ```solidity
+ import { IBaseWorld } from "@latticexyz/world-modules/src/interfaces/IBaseWorld.sol";
+ ```
+
+ Use [IBaseWorld.sol](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/interfaces/IBaseWorld.sol) to get definitions that are common to all `World` contracts.
+
+ ```solidity
+ import { WorldRegistrationSystem } from "@latticexyz/world/src/modules/core/implementations/WorldRegistrationSystem.sol";
+ ```
+
+ [WorldRegistartionSystem.sol](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol) contains the function definitions necessary to register new namespaces and systems with an existing `World`.
+
+ ```solidity
+ // Create resource identifiers (for the namespace and system)
+ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
+ ```
+
+ These definitions make it easy to manage resource identifiers.
+ We need them for the resource IDs we need to create: the namespace and the system.
+
+ ```solidity
+ // For registering the table
+ import { Messages, MessagesTableId } from "../src/codegen/index.sol";
+ import { IStore } from "@latticexyz/store/src/IStore.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
+ ```
+
+ These are the definitions we need to register the `Messages` table.
+
+ ```solidity
+ // For deploying MessageSystem
+ import { MessageSystem } from "../src/systems/MessageSystem.sol";
+ ```
+
+ These are the definitions we need to deploy the `MessageSystem` contract so we'll then be able to register it as a `System` in the `World`.
+
+ ```solidity
+ contract MessagingExtension is Script {
+ function run() external {
+ ```
+
+ This is the function that implements the script.
+
+ ```solidity
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+ ```
+
+ Read the private key and the address of the `World` from the environment (which includes the content of the `.env` file).
+
+ ```solidity
+ WorldRegistrationSystem world = WorldRegistrationSystem(worldAddress);
+ ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("messaging"));
+ ResourceId systemResource = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "messaging", "message");
+ ```
+
+ Among other things, a MUD `World` is a `WorldRegistrationSystem`, so it has the appropriate functions.
+ A `ResourceId` is a 32 byte value that uniquely identifies a resource in a MUD `World`.
+ It is two bytes of resource type followed by 14 bytes of namespace and then 16 bytes of the name of the actual resource.
+
+ Here we create two `ResourceId` values:
+
+ | Name | Type | Namespace | Resource name |
+ | ----------------- | ---------------- | --------- | ------------- |
+ | namespaceResource | `ns` (namespace) | messaging | Empty |
+ | systemResource | `sy` (system) | messaging | message |
+
+ If you want to see these values, add these two lines to the script:
+
+ ```solidity
+ console.log("Namespace ID: %x", uint256(ResourceId.unwrap(namespaceResource)));
+ console.log("System ID: %x", uint256(ResourceId.unwrap(systemResource)));
+ ```
+
+ Note that `console.log` requires a `uint256` value, and we can't get that directly from a `ResourceId`.
+ Instead, we have to [unwrap](https://docs.soliditylang.org/en/v0.8.20/types.html#user-defined-value-types) our `ResourceId` to get the original type (`bytes32`) and then [cast](https://docs.soliditylang.org/en/v0.8.20/types.html#explicit-conversions) it to `uint256`.
+
+ The expected values are:
+
+ | Name | Expected value |
+ | ------------ | ------------------------------------------------------------------ |
+ | Namespace ID | 0x6e736d6573736167696e67000000000000000000000000000000000000000000 |
+ | System ID | 0x73796d6573736167696e6700000000006d657373616765000000000000000000 |
+
+ You can use [an online calculator](https://www.duplichecker.com/hex-to-text.php) to verify the values are correct.
+
+ | Hex value | ASCII |
+ | ---------------------: | ----------- |
+ | 6e736d6573736167696e67 | nsmessaging |
+ | 73796d6573736167696e67 | symessaging |
+ | 6d657373616765 | message |
+
+ ```solidity
+ vm.startBroadcast(deployerPrivateKey);
+ ```
+
+ Use the private key to submit transactions.
+
+ ```solidity
+ world.registerNamespace(namespaceResource);
+ ```
+
+ [Register the namespace](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol#L39-L63).
+
+ ```solidity
+ StoreSwitch.setStoreAddress(worldAddress);
+ Messages.register();
+ ```
+
+ Register the `Messages` table to `worldAddress`.
+
+ ```solidity
+ MessageSystem messageSystem = new MessageSystem();
+ world.registerSystem(systemResource, messageSystem, true);
+ ```
+
+ Deploy the new system and then register it with the `World` we are extending.
+ The last parameter is whether or not we allow everybody to access this `System`.
+
+ ```solidity
+ world.registerFunctionSelector(systemResource, "incrementMessage(string)");
+ ```
+
+ Register `MessageSystem.incrementMessage(string)`.
+ This step is necessary to make the function accessible through the `World`.
+ The function's name when accessed through the world is `__`, so this function will be available as `messaging_message_incrementMessage(string)`.
+
+ **Note:** This is the case for all namespaces except for the root namespace, where we just use the name of the function (such as `increment()`).
+
+ ```solidity
+ vm.stopBroadcast();
+ }
+ }
+ ```
+
+ Stop using the private key.
+ Here this call is not necessary because we immediately leave the script, but it is a good idea to include it in case `MessagingExtension.run()` ever becomes part of a larger script.
+
+
+
+1. Run the script.
+ Note that you need to provide the URL in the command line, you can't rely on the `ETH_RPC_URL` environment variable.
+
+ ```sh copy
+ forge script script/MessagingExtension.s.sol --rpc-url http://localhost:8545 --broadcast
+ ```
+
+1. Increment and write a message.
+
+ ```sh copy
+ source .env
+ cast send $WORLD_ADDRESS --private-key $PRIVATE_KEY "messaging_message_incrementMessage(string)" "hello"
+ ```
+
+ When a function is _not_ in the root namespace, it is accessible as `__` (as long as it is [registered](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol#L164-L201)).
+
+ Here we call our `incrementMessage(string)` with the parameter `hello`.
+
+1. You can see in the user interface of `extendMe` that the counter has been incremented.
+ To see the message we sent, use these commands:
+
+ ```sh copy
+ TABLE_ID=0x74626d6573736167696e6700000000004d657373616765730000000000000000
+ KEY=[`cast to-int256 2`]
+ RAW_RESULT=`cast call $WORLD_ADDRESS "getRecord(bytes32,bytes32[])" $TABLE_ID $KEY`
+ cast --to-ascii ${RAW_RESULT:322:-2}
+ ```
+
+
+
+ Explanation
+
+ ```sh
+ TABLE_ID=0x74626d6573736167696e6700000000004d657373616765730000000000000000
+ ```
+
+ `TABLE_ID` is the `ResourceId` for the table, taken from `src/codegen/tables/Messages.sol`
+ You can verify the interpretation [with the online calculator](https://www.duplichecker.com/hex-to-text.php).
+
+ | Type | Namespace | Resource name |
+ | ------------ | --------- | ------------- |
+ | `tb` (table) | messaging | Messages |
+
+ ```sh
+ KEY=[`cast to-int256 2`]
+ ```
+
+ The key to a MUD table is always an array of `byte32` values.
+ To create that value, we convert our key (`2`, because that's the first user call to `increment()`, the 0->1 increment is done by the post deploy script) to a 256 bit value using [`cast`](https://book.getfoundry.sh/reference/cast/cast-to-int256) and then envelop it in an array.
+
+ ```sh
+ RAW_RESULT=`cast call $WORLD_ADDRESS "getRecord(bytes32,bytes32[],bytes32)" $TABLE_ID $KEY $FIELD_LAYOUT`
+ ```
+
+ To call [`getRecord`](https://github.com/latticexyz/mud/blob/main/packages/store/src/StoreRead.sol#L44-L57) we need the tableID, the key array, and the field layout.
+ The call result is the entire value, which may have multiple static (fixed length) and dynamic (variable length) fields.
+
+ Here is the raw result divided into 32 byte words.
+
+ | Word | Value |
+ | ---: | ------------------------------------------------------------------ |
+ | 0 | `0000000000000000000000000000000000000000000000000000000000000060` |
+ | 1 | `0000000000000000000000000000000000000000000000000500000000000005` |
+ | 2 | `0000000000000000000000000000000000000000000000000000000000000080` |
+ | 3 | `0000000000000000000000000000000000000000000000000000000000000000` |
+ | 4 | `0000000000000000000000000000000000000000000000000000000000000005` |
+ | 5 | `68656c6c6f000000000000000000000000000000000000000000000000000000` |
+
+ When there is a variable-length field (a field whose length is not known at compile-time) in a Solidity function's return value, it is represented by a word that tells us at what offset into the return data that field starts.
+ This function is used for different tables with different lengths of static fields, so the static fields are a variable length field as far as Solidity is concerned.
+
+ Word 0 shows us that this field's value starts at `0x60`, which is word 3.
+ Because there are no static fields, word 3 is all zeros.
+
+ Word 1 is a [`PackedCounter`](https://github.com/latticexyz/mud/blob/main/packages/store/src/PackedCounter.sol) with the lengths of the dynamic fields.
+ Here is the interpretation.
+
+ | Bytes | Value | Meaning |
+ | ----: | ---------------: | --------------------------------------------------------------------------- |
+ | 6-0 | `00000000000005` | The total length of all dynamic fields is five bytes. |
+ | 11-7 | `0000000005` | The length of the first dynamic field is five bytes. |
+ | 16-12 | `0000000000` | The length of the (non existent in `Messages`) second dynamic field is zero |
+ | 21-17 | `0000000000` | The length of the (non existent in `Messages`) third dynamic field is zero |
+ | 26-22 | `0000000000` | The length of the (non existent in `Messages`) fourth dynamic field is zero |
+ | 31-27 | `0000000000` | The length of the (non existent in `Messages`) fifth dynamic field is zero |
+
+ MUD tables can only have up to five dynamic fields because `PackedCounter` needs to fit in a 32 byte word.
+
+ Word 2 shows us that the field with the dynamic lengths starts at byte 0x80, which is word 4.
+ Word 4 gives us the length of the string.
+
+ Finally, word 5 gives us the actual message, "hello".
+
+ ```sh
+ cast --to-ascii ${RAW_RESULT:322:-2}
+ ```
+
+ Based on the above we don't need the first 322 characters of `$RAW_RESULT`.
+ The first two characters are the `0x` that tells us this is a hexadecimal number.
+ The next 320 characters are words 0-4, which are not the actual message (each word is 32 bytes, which is 64 hexadecimal digits). We also don't need the trailing newline. `${RAW_RESULT:322:-2}` removes those characters so we can use [`cast`](https://book.getfoundry.sh/reference/cast/cast-to-ascii) to get the ASCII.
+
+