diff --git a/pages/stack/interop/tutorials/_meta.json b/pages/stack/interop/tutorials/_meta.json
index 183081d61..e5ef429e1 100644
--- a/pages/stack/interop/tutorials/_meta.json
+++ b/pages/stack/interop/tutorials/_meta.json
@@ -2,6 +2,8 @@
"message-passing": "Interop message passing",
"deploy-superchain-erc20": "Issuing new assets with SuperchainERC20",
"transfer-superchainERC20": "Transferring a SuperchainERC20",
+ "deploy-superchain-erc20": "Issuing new assets with SuperchainERC20",
+ "custom-superchain-erc20": "Custom SuperchainERC20 tokens",
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
"relay-messages-cast": "Relaying interop messages using `cast`",
"relay-messages-viem": "Relaying interop messages using `viem`",
diff --git a/pages/stack/interop/tutorials/custom-superchain-erc20.mdx b/pages/stack/interop/tutorials/custom-superchain-erc20.mdx
new file mode 100644
index 000000000..9257b3877
--- /dev/null
+++ b/pages/stack/interop/tutorials/custom-superchain-erc20.mdx
@@ -0,0 +1,168 @@
+---
+title: Creating custom SuperchainERC20 tokens
+lang: en-US
+description: Create SuperchainERC20 tokens with custom behaviors
+---
+
+import { Callout } from 'nextra/components'
+import { Steps } from 'nextra/components'
+
+
+ The SuperchainERC20 standard is ready for production deployments.
+ Please note that the OP Stack interoperability upgrade, required for crosschain messaging, is currently still in active development.
+
+
+# Custom SuperchainERC20 tokens
+
+## Overview
+
+This guide explains how to modify the behavior of [`SuperchainERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol) contracts to create custom tokens that can then be bridged quickly and safely using the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract (once interop is operational).
+For more information on how it works, [see the explainer](/stack/interop/superchain-erc20).
+
+To ensure fungibility across chains, `SuperchainERC20` assets *must* have the same contract address on all chains.
+This requirement abstracts away the complexity of cross-chain validation.
+Achieving this requires deterministic deployment methods. There are [many ways to do this](https://github.com/Arachnid/deterministic-deployment-proxy).
+Here we will use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit).
+
+### What you'll do
+
+* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy tokens with your custom code.
+
+### What you'll learn
+
+* How to deploy custom ERC-20 tokens on different chains at the same address so that they can be bridged with the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract.
+
+## Prerequisites
+
+Before starting this tutorial, ensure your development environment meets the following requirements:
+
+### Technical knowledge
+
+* Understanding of smart contract development
+* Familiarity with blockchain concepts
+* Familiarity with [standard SuperchainERC20 deployments](./deploy-superchain-erc20).
+
+### Development environment
+
+* Unix-like operating system (Linux, macOS, or WSL for Windows)
+* Git for version control
+
+### Required tools
+
+The tutorial uses these primary tools:
+
+* Foundry: For sending transactions to blockchains.
+
+## Step by step explanation
+
+
+ ### Prepare for deployment
+
+ 1. Follow the setup steps in the [SuperchainERC20 Starter Kit](/app-developers/starter-kit#setup).
+ Don't start the development environment (step 5).
+
+ 2. Follow [the deployment preparations steps](./deploy-superchain-erc20#prepare-for-deployment) in the issuing new assets page.
+ Don't deploy the contracts yet.
+
+ **Note:** Make sure to specify a previously unused value for the salt, for example your address and a timestamp.
+ This is necessary because if the same constructor code is used with the same salt when using the deployment script, it gets the same address, which is a problem if you want a fresh deployment.
+
+ ### Create the custom contract
+
+ The easiest way to do this is to copy and modify the `L2NativeSuperchainERC20.sol` contract.
+ Use this code, for example, as `packages/contracts/src/CustomSuperchainToken.sol`.
+
+ ```solidity file=/public/tutorials/CustomSuperchainToken.sol hash=4ad95b9203ce523351eba0501f8b972d
+ ```
+
+
+ Explanation
+
+ ```solidity file=/public/tutorials/CustomSuperchainToken.sol#L36-L38 hash=4e402ea88c9cd796500425172a6de16d
+ ```
+
+ This function lets users get tokens for themselves.
+ This token is for testing purposes, so it is useful for users to get their own tokens to run tests.
+
+
+ ### Deploy the new token
+
+ 1. Edit `packages/contracts/scripts/SuperchainERC20Deployer.s.sol`:
+
+ * Change line 6 to import the new token.
+
+ ```solidity
+ import {CustomSuperchainToken} from "../src/CustomSuperchainToken.sol";
+ ```
+
+ * Update lines 52-54 to get the `CustomSuperchainToken` initialization code.
+
+ ```solidity
+ bytes memory initCode = abi.encodePacked(
+ type(CustomSuperchainToken).creationCode, abi.encode(ownerAddr_, name, symbol, uint8(decimals))
+ );
+ ```
+
+ * Modify line 62 to deploy a `CustomSuperchainToken` contract.
+
+ ```solidity
+ addr_ = address(new CustomSuperchainToken{salt: _implSalt()}(ownerAddr_, name, symbol, uint8(decimals)));
+ ```
+
+ 2. Deploy the token contract.
+
+ ```sh
+ pnpm contracts:deploy:token
+ ```
+
+
+ Sanity check
+
+ 1. Set `TOKEN_ADDRESS` to the address where the token is deployed.
+ You can also play with the token I created, which is at address [`0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8).
+
+ ```sh
+ TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8
+ ```
+
+ 2. Source the `.env` file to get the private key and the address to which it corresponds.
+
+ ```sh
+ . packages/contracts/.env
+ MY_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY`
+ ```
+
+ 3. Set variables for the RPC URLs.
+
+ ```sh
+ RPC_DEV0=https://interop-alpha-0.optimism.io
+ RPC_DEV1=https://interop-alpha-1.optimism.io
+ ```
+
+ 4. Get your current balance (it should be zero).
+
+ ```sh
+ cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
+ ```
+
+ 5. Call the faucet to get a token and check the balance again.
+
+ ```sh
+ cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "faucet()"
+ cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
+ ```
+
+
+
+## How does this work?
+
+To allow for superchain interoperability, an ERC-20 token has to implement [ERC-7802](https://specs.optimism.io/interop/token-bridging.html#ierc7802).
+You can either use [the `SuperchainERC20` implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol#L26-L46), or write your own.
+
+For more details [see the explainer](../superchain-erc20).
+
+## Next steps
+
+* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain.
+* Explore the [SuperchainERC20 specifications](https://specs.optimism.io/interop/token-bridging.html) for in-depth implementation details.
+* Review the [Superchain Interop Explainer](../explainer) for answers to common questions about interoperability.
diff --git a/public/tutorials/CustomSuperchainToken.sol b/public/tutorials/CustomSuperchainToken.sol
new file mode 100644
index 000000000..20bd72f62
--- /dev/null
+++ b/public/tutorials/CustomSuperchainToken.sol
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.25;
+
+import {SuperchainERC20} from "./SuperchainERC20.sol";
+import {Ownable} from "@solady/auth/Ownable.sol";
+
+contract CustomSuperchainToken is SuperchainERC20, Ownable {
+ string private _name;
+ string private _symbol;
+ uint8 private immutable _decimals;
+
+ constructor(address owner_, string memory name_, string memory symbol_, uint8 decimals_) {
+ _name = name_;
+ _symbol = symbol_;
+ _decimals = decimals_;
+
+ _initializeOwner(owner_);
+ }
+
+ function name() public view virtual override returns (string memory) {
+ return _name;
+ }
+
+ function symbol() public view virtual override returns (string memory) {
+ return _symbol;
+ }
+
+ function decimals() public view override returns (uint8) {
+ return _decimals;
+ }
+
+ function mintTo(address to_, uint256 amount_) external onlyOwner {
+ _mint(to_, amount_);
+ }
+
+ function faucet() external {
+ _mint(msg.sender, 10**_decimals);
+ }
+}