LLM Notice: This documentation site supports content negotiation for AI agents. Request any page with Accept: text/markdown or Accept: text/plain header to receive Markdown instead of HTML. Alternatively, append ?format=md to any URL. All markdown files are available at /md/ prefix paths. For all content in one file, visit /llms-full.txt
Skip to main content

Interactive Testing with Forked Emulator

This tutorial teaches you how to run your dapp, E2E tests, and manual explorations against a snapshot of Flow mainnet using flow emulator --fork. You'll learn how to connect your frontend to production-like state, test user flows with real contracts and data, and debug issues interactively—all without deploying to a live network.

The forked emulator creates a local Flow network that mirrors mainnet or testnet state. It's perfect for manual testing, running E2E test suites, and exploring contract interactions in a production-like environment with full control.

What You'll Learn

After you complete this tutorial, you'll be able to:

  • Start the emulator in fork mode with flow emulator --fork.
  • Connect your dapp frontend to the forked emulator.
  • Test against real mainnet contracts and production data interactively.
  • Run E2E tests (Cypress, Playwright) against forked state.
  • Use account impersonation to test as any mainnet account.
  • Pin to specific block heights for reproducible testing.
  • Debug and explore contract interactions manually.

What You'll Build

You'll create a complete forked emulator setup that demonstrates:

  • Starting the emulator with forked mainnet state.
  • A React dapp connected to the forked emulator reading real FlowToken data.
  • Manual testing flows using account impersonation.
  • Automating tests with E2E frameworks against forked state.
  • A reusable pattern for interactive testing and debugging.

Prerequisites

Flow CLI

This tutorial requires Flow CLI v1.8.0 or later installed. If you haven't installed it yet and have homebrew installed, run:


_10
brew install flow-cli

For other operating systems, refer to the installation guide.

Node.js and npm

You'll need Node.js (v16+) and npm to run the React frontend examples. Check your installation:


_10
node --version
_10
npm --version

Frontend development knowledge

Basic familiarity with React and JavaScript is helpful but not required. The examples use the Flow React SDK for Flow blockchain integration.

tip

This tutorial uses @onflow/react-sdk for all React examples. The React SDK provides hooks and components that make Flow development feel native to React. For non-React applications, you can use @onflow/fcl directly.

Network access

You'll need network access to Flow's public access nodes:

  • Mainnet: access.mainnet.nodes.onflow.org:9000
  • Testnet: access.devnet.nodes.onflow.org:9000
info

This tutorial covers flow emulator --fork (interactive testing with a forked emulator), which is different from flow test --fork (running Cadence test files against forked state). For testing Cadence contracts with test files, see Fork Testing with Cadence.

Understanding Emulator Fork Mode

What is flow emulator --fork?

The emulator's fork mode starts a local Flow blockchain that connects to a real network (mainnet or testnet) and fetches state on-demand. Your dapp, scripts, and transactions run locally but can read from and interact with real network data.

Key capabilities:

  • Full gRPC and REST API servers running locally
  • On-demand fetching of accounts, contracts, and state from the live network
  • Disabled signature validation. You can impersonate any mainnet account to execute transactions
  • All mutations stay local—never affect the real network
  • Perfect for E2E tests, manual exploration, and debugging

When to Use This

Use flow emulator --fork for:

  • E2E and frontend testing: Run Cypress/Playwright tests against production-like state
  • Manual exploration: Interact with your dapp connected to forked mainnet
  • Debugging user issues: Reproduce bugs at specific block heights
  • Migration testing: Test contract upgrades with real account state
  • Wallet integration: Test wallet connect flows and transactions
  • Bot and indexer testing: Run automated tools against forked data

Don't use this for:

Emulator Fork vs Test Framework Fork

Featureflow emulator --forkflow test --fork
Use forDapp E2E, manual testing, debuggingCadence unit/integration tests
Connects toFrontend, wallets, bots, E2E toolsCadence Testing Framework
Run withFCL, Cypress, Playwright, manual clicksflow test command
Best forUser flows, UI testing, explorationContract logic validation
ExamplesReact app, wallet flows, E2E suites*_test.cdc files

Both modes are valuable—use the right tool for the job.

Quick Start: Run in 60 Seconds

Want to see it work immediately? Here's the fastest path:


_19
# 1. Initialize a Flow project
_19
flow init --yes
_19
_19
# 2. Configure fork network (add to flow.json)
_19
# Add this under "networks":
_19
# "mainnet-fork": {
_19
# "host": "127.0.0.1:3569",
_19
# "fork": "mainnet"
_19
# }
_19
_19
# 3. Install FlowToken dependency
_19
flow dependencies install
_19
# Select FlowToken from the list
_19
_19
# 4. Start forked emulator (in a separate terminal)
_19
flow emulator --fork mainnet
_19
_19
# 5. In another terminal, check the forked state
_19
flow scripts execute cadence/scripts/get_flow_supply.cdc --network mainnet-fork

Create cadence/scripts/get_flow_supply.cdc:


_10
import "FlowToken"
_10
_10
access(all) fun main(): UFix64 {
_10
return FlowToken.totalSupply
_10
}

You'll see the real mainnet FlowToken supply! Now let's build a complete example with a frontend.

Create Your Project

Navigate to your development directory and create a new Flow project:


_10
mkdir emulator-fork-demo
_10
cd emulator-fork-demo
_10
flow init --yes

The --yes flag accepts defaults non-interactively.

Configure Fork Network in flow.json

Before starting the emulator, configure a fork network in your flow.json. This enables automatic contract alias inheritance from mainnet, so you don't need to manually duplicate aliases.

Open flow.json and add a mainnet-fork network:


_11
{
_11
"networks": {
_11
"emulator": "127.0.0.1:3569",
_11
"mainnet": "access.mainnet.nodes.onflow.org:9000",
_11
"testnet": "access.devnet.nodes.onflow.org:9000",
_11
"mainnet-fork": {
_11
"host": "127.0.0.1:3569",
_11
"fork": "mainnet"
_11
}
_11
}
_11
}

What this does:

  • host: Points to your local emulator
  • fork: Tells the CLI to automatically inherit contract aliases from mainnet

Now any contract with a mainnet alias will automatically work on mainnet-fork without manual configuration!

tip

Why forking is powerful:

The emulator fork mode gives you access to real production state:

  • ✅ Test against actual deployed contracts (FT, NFT, DEXs, marketplaces)
  • ✅ Read real account balances, storage, and capabilities
  • ✅ Query production data without setting up test fixtures
  • ✅ Catch integration issues with real-world contract implementations
  • ✅ Debug with historical state by pinning block heights

Plus, fork networks simplify configuration:

  • ✅ No need to duplicate 30+ contract aliases
  • ✅ Automatic inheritance from source network
  • ✅ Can override specific contracts if needed

Example of automatic inheritance:


_11
{
_11
"dependencies": {
_11
"FlowToken": {
_11
"aliases": {
_11
"mainnet": "0x1654653399040a61"
_11
// ✅ mainnet-fork automatically inherits this!
_11
// No need for: "mainnet-fork": "0x1654653399040a61"
_11
}
_11
}
_11
}
_11
}

When you run commands with --network mainnet-fork, the CLI automatically resolves contract imports to their mainnet addresses.

Start the Forked Emulator

Start the emulator in fork mode, connected to mainnet:


_10
flow emulator --fork mainnet

You'll see output like:


_10
INFO[0000] ⚙️ Using service account 0xf8d6e0586b0a20c7
_10
INFO[0000] 🌱 Starting Flow Emulator in fork mode (mainnet)
_10
INFO[0000] 🛠 GRPC server started on 127.0.0.1:3569
_10
INFO[0000] 📡 REST server started on 127.0.0.1:8888
_10
INFO[0000] 🌐 Forking from access.mainnet.nodes.onflow.org:9000

Leave this terminal running. The emulator is now serving:

  • REST API: http://localhost:8888 (for FCL/frontend)
  • gRPC API: localhost:3569 (for Flow CLI)
tip

Pin to a specific block height for reproducibility:


_10
flow emulator --fork mainnet --fork-height <BLOCK_HEIGHT>

This ensures the forked state is consistent across runs—essential for E2E tests in CI.

Mocking Mainnet Contracts

Just like mocking dependencies in unit tests, you can mock real mainnet contracts by deploying modified versions—perfect for testing upgrades, bug fixes, or alternative implementations against real production state.

Configure the mock in flow.json, then deploy to the forked emulator. Your mock takes precedence while other contracts use real mainnet versions.

Example

1. Configure in flow.json:


_21
{
_21
"accounts": {
_21
"flow-token-mainnet": {
_21
"address": "0x1654653399040a61",
_21
"key": "0000000000000000000000000000000000000000000000000000000000000000"
_21
}
_21
},
_21
"contracts": {
_21
"FlowToken": {
_21
"source": "./contracts/FlowTokenModified.cdc",
_21
"aliases": {
_21
"mainnet": "0x1654653399040a61"
_21
}
_21
}
_21
},
_21
"deployments": {
_21
"mainnet-fork": {
_21
"flow-token-mainnet": ["FlowToken"]
_21
}
_21
}
_21
}

2. Deploy the mock:


_10
flow emulator --fork mainnet
_10
flow project deploy --network mainnet-fork --update

Your dapp now uses the mocked FlowToken while FungibleToken, USDC, and all other contracts use real mainnet versions.

Install Dependencies

Use the Dependency Manager to install common Flow contracts. This adds them to your flow.json with mainnet aliases that will automatically work on the fork:


_10
flow dependencies install

Select FlowToken and FungibleToken from the list (use space to select, enter to confirm).

Your flow.json now includes:


_20
{
_20
"dependencies": {
_20
"FlowToken": {
_20
"source": "mainnet://1654653399040a61.FlowToken",
_20
"aliases": {
_20
"emulator": "0x0ae53cb6e3f42a79",
_20
"mainnet": "0x1654653399040a61",
_20
"testnet": "0x7e60df042a9c0868"
_20
}
_20
},
_20
"FungibleToken": {
_20
"source": "mainnet://f233dcee88fe0abe.FungibleToken",
_20
"aliases": {
_20
"emulator": "0xee82856bf20e2aa6",
_20
"mainnet": "0xf233dcee88fe0abe",
_20
"testnet": "0x9a0766d93b6608b7"
_20
}
_20
}
_20
}
_20
}

Key insight: Notice there's no mainnet-fork alias. That's the beauty—mainnet-fork automatically inherits the mainnet aliases thanks to the fork configuration!

Test with Flow CLI Scripts

Before connecting a frontend, verify the fork works with a simple script.

Create a directory for scripts:


_10
mkdir -p cadence/scripts

Create cadence/scripts/get_flow_supply.cdc:


_10
import "FlowToken"
_10
_10
access(all) fun main(): UFix64 {
_10
return FlowToken.totalSupply
_10
}

Notice we're using the import shorthand import "FlowToken" instead of an address. The CLI will automatically resolve this to the mainnet address on the fork.

In a new terminal (keep the emulator running), execute the script:


_10
flow scripts execute cadence/scripts/get_flow_supply.cdc --network mainnet-fork

You should see the real mainnet FlowToken supply (e.g., Result: 1523456789.00000000).

What happened:

  1. Your script ran on the local emulator
  2. The CLI resolved "FlowToken" to the mainnet address (0x1654653399040a61)
  3. The emulator fetched FlowToken contract state from mainnet on-demand
  4. The script returned real production data

Now let's connect a frontend.

Create a React Dapp

Create a React app with Flow integration:


_10
npx create-react-app flow-fork-app
_10
cd flow-fork-app
_10
npm install @onflow/react-sdk

Copy your project's flow.json into the React app's src directory:


_10
# From your flow-fork-app directory
_10
cp ../flow.json src/

This allows the FlowProvider to resolve contract imports.

Replace src/index.js with:


_22
import React from 'react';
_22
import ReactDOM from 'react-dom/client';
_22
import App from './App';
_22
import { FlowProvider } from '@onflow/react-sdk';
_22
import flowJSON from './flow.json';
_22
_22
const root = ReactDOM.createRoot(document.getElementById('root'));
_22
root.render(
_22
<React.StrictMode>
_22
<FlowProvider
_22
config={{
_22
accessNodeUrl: 'http://localhost:8888', // Point to forked emulator REST endpoint
_22
flowNetwork: 'mainnet-fork', // Use fork network (inherits mainnet aliases)
_22
appDetailTitle: 'Flow Fork Demo',
_22
discoveryWallet: 'http://localhost:8701/fcl/authn', // Dev wallet
_22
}}
_22
flowJson={flowJSON}
_22
>
_22
<App />
_22
</FlowProvider>
_22
</React.StrictMode>,
_22
);

Replace src/App.js with:


_60
import { useState } from 'react';
_60
import { useFlowCurrentUser, useFlowQuery, Connect } from '@onflow/react-sdk';
_60
_60
function App() {
_60
const { user } = useFlowCurrentUser();
_60
const [shouldFetch, setShouldFetch] = useState(false);
_60
_60
// Query FlowToken supply from forked mainnet
_60
const {
_60
data: flowSupply,
_60
isLoading,
_60
error,
_60
} = useFlowQuery({
_60
cadence: `
_60
import "FlowToken"
_60
_60
access(all) fun main(): UFix64 {
_60
return FlowToken.totalSupply
_60
}
_60
`,
_60
args: (arg, t) => [],
_60
query: {
_60
enabled: shouldFetch, // Only run when button is clicked
_60
},
_60
});
_60
_60
return (
_60
<div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
_60
<h1>🌊 Flow Emulator Fork Demo</h1>
_60
<p>
_60
Connected to: <strong>Forked Mainnet (localhost:8888)</strong>
_60
</p>
_60
_60
<div style={{ marginTop: '2rem' }}>
_60
<h2>FlowToken Supply (Real Mainnet Data)</h2>
_60
<button onClick={() => setShouldFetch(true)} disabled={isLoading}>
_60
{isLoading ? 'Loading...' : 'Get FlowToken Supply'}
_60
</button>
_60
{error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
_60
{flowSupply && (
_60
<p style={{ fontSize: '1.5rem', color: 'green' }}>
_60
Total Supply: {Number(flowSupply).toLocaleString()} FLOW
_60
</p>
_60
)}
_60
</div>
_60
_60
<div style={{ marginTop: '2rem' }}>
_60
<h2>Wallet Connection</h2>
_60
<Connect />
_60
{user?.loggedIn && (
_60
<p style={{ marginTop: '1rem' }}>
_60
Connected: <code>{user.addr}</code>
_60
</p>
_60
)}
_60
</div>
_60
</div>
_60
);
_60
}
_60
_60
export default App;

Start the dev wallet (optional)

For wallet authentication flows, start the FCL dev wallet in another terminal:


_10
flow dev-wallet

This starts the dev wallet at http://localhost:8701.

Run your dapp

Start the React app:


_10
npm start

Your browser will open to http://localhost:3000. Click "Get FlowToken Supply" to see real mainnet data!

What's happening:

  1. FlowProvider receives flow.json and configures import resolution
  2. The string import import "FlowToken" resolves to the mainnet address automatically
  3. useFlowQuery executes the Cadence script via the local emulator
  4. The emulator fetches FlowToken state from mainnet on-demand
  5. Your app displays real production data—all running locally!

Key React SDK features used:

  • FlowProvider – Wraps your app, configures the Flow connection, and resolves contract imports from flow.json
  • useFlowCurrentUser – Provides wallet authentication state
  • useFlowQuery – Executes Cadence scripts with automatic caching and loading states
  • Connect – Pre-built wallet connection UI component
Contract Import Resolution

By passing flowJson to the FlowProvider, string imports like import "FlowToken" automatically resolve to the correct network addresses.

How it works:

  1. SDK looks up contract aliases for the specified flowNetwork
  2. For fork networks, it checks if the network has a fork property and inherits aliases from the parent network
  3. Contract imports in your Cadence code are replaced with the resolved addresses

Example: With flowNetwork: 'mainnet-fork' (which has fork: 'mainnet'), import "FlowToken" resolves to 0x1654653399040a61 (the mainnet FlowToken address).

Account Impersonation

The forked emulator's superpower: you can execute transactions as any mainnet account because signature validation is disabled.

Read Account Balance

Create cadence/scripts/get_balance.cdc:


_11
import "FlowToken"
_11
import "FungibleToken"
_11
_11
access(all) fun main(address: Address): UFix64 {
_11
let account = getAccount(address)
_11
let vaultRef = account.capabilities
_11
.borrow<&{FungibleToken.Balance}>(/public/flowTokenBalance)
_11
?? panic("Could not borrow FlowToken Balance reference")
_11
_11
return vaultRef.balance
_11
}

Check the Flow service account balance (a real mainnet account):


_10
flow scripts execute cadence/scripts/get_balance.cdc 0x1654653399040a61 --network mainnet-fork

You'll see the service account's actual mainnet balance! The imports automatically resolved to mainnet addresses because you're using the mainnet-fork network.

Execute Transaction as Any Account

Create cadence/transactions/transfer_tokens.cdc:


_23
import "FungibleToken"
_23
import "FlowToken"
_23
_23
transaction(amount: UFix64, to: Address) {
_23
let sentVault: @{FungibleToken.Vault}
_23
_23
prepare(signer: auth(Storage) &Account) {
_23
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
_23
from: /storage/flowTokenVault
_23
) ?? panic("Could not borrow reference to the owner's Vault")
_23
_23
self.sentVault <- vaultRef.withdraw(amount: amount)
_23
}
_23
_23
execute {
_23
let recipient = getAccount(to)
_23
let receiverRef = recipient.capabilities
_23
.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
_23
?? panic("Could not borrow receiver reference")
_23
_23
receiverRef.deposit(from: <-self.sentVault)
_23
}
_23
}

The forked emulator disables transaction signature validation, allowing you to send transactions as any address without valid signatures.

Now let's test transferring tokens from a mainnet account using impersonation.

CLI-Based Impersonation

To use impersonation with the CLI, you need to add the mainnet account to your flow.json (signature validation is disabled, so the key value doesn't matter):


_10
{
_10
"accounts": {
_10
"mainnet-service": {
_10
"address": "0x1654653399040a61",
_10
"key": "0000000000000000000000000000000000000000000000000000000000000000"
_10
}
_10
}
_10
}

Transfer tokens from the mainnet service account to another mainnet account:


_10
# Transfer from mainnet service account to any mainnet address (impersonation!)
_10
flow transactions send cadence/transactions/transfer_tokens.cdc 100.0 0xRECIPIENT_ADDRESS \
_10
--signer mainnet-service \
_10
--network mainnet-fork
_10
_10
# Verify the transfer
_10
flow scripts execute cadence/scripts/get_balance.cdc 0xRECIPIENT_ADDRESS \
_10
--network mainnet-fork

Dev Wallet Authentication with Impersonation

The most powerful feature: when connecting your dapp to the forked emulator with the dev wallet, you can authenticate as ANY mainnet account directly in the UI.

Start the dev wallet:


_10
flow dev-wallet

In your dapp (running against the forked emulator), click the wallet connect button. In the dev wallet UI:

  1. Enter any mainnet address in the address field (e.g., a whale wallet, NFT collector, or protocol account)
  2. Click "Authenticate"
  3. Your dapp is now authenticated as that mainnet account with all its real balances, NFTs, and storage!

Additional dev wallet features in fork mode:

  • Fund accounts: The dev wallet can add FLOW tokens to any account, even real mainnet accounts
  • No configuration needed: The dev wallet handles impersonation automatically when connected to a forked emulator
  • Full account state: Access all assets, storage, and capabilities from the real mainnet account

This lets you:

  • Test your dapp as a user with specific assets or permissions
  • Debug issues reported by specific mainnet accounts
  • Verify flows work for accounts with large balances or many NFTs
  • Test edge cases with real account states
  • Add test funds to accounts that need more FLOW for testing
How "Impersonation" Works

The forked emulator simply skips signature verification. You can specify any mainnet address as the signer, and the emulator will execute the transaction as that account. Empty or invalid signatures are accepted. This lets you test with real account balances, storage, and capabilities without needing private keys. For frontend flows with the dev wallet, it works the same way—the wallet can "sign" as any address because the emulator doesn't validate signatures.

Automating with E2E Testing

The forked emulator works with any E2E testing framework (Cypress, Playwright, Puppeteer, etc.). This lets you automate your dapp tests against production-like state.

Quick Example with Cypress


_10
npm install --save-dev cypress

Create cypress/e2e/flow_fork.cy.js:


_10
describe('Flow Fork Test', () => {
_10
it('reads real mainnet data', () => {
_10
cy.visit('http://localhost:3000');
_10
cy.contains('Get FlowToken Supply').click();
_10
cy.contains('Total Supply:', { timeout: 10000 }).should('be.visible');
_10
});
_10
});

Running E2E Tests

Run three terminals:

  1. Terminal 1: flow emulator --fork mainnet --fork-height <BLOCK_HEIGHT>
  2. Terminal 2: npm start (your React app)
  3. Terminal 3: npx cypress run

Your tests now run against forked mainnet—perfect for CI/CD pipelines with pinned block heights ensuring deterministic results.

tip

Use the same approach with Playwright, Puppeteer, or any browser automation tool. The key is having your dapp connect to the forked emulator (http://localhost:8888) while your E2E framework tests the UI.

Common Use Cases

Testing Contract Upgrades

Test a contract upgrade against real mainnet state by mocking the contract with your upgraded version:

  1. Configure the mock in flow.json (see Mocking Mainnet Contracts)
  2. Start the forked emulator
  3. Deploy your upgraded contract: flow project deploy --network mainnet-fork --update
  4. Test your dapp against the upgraded contract with all real mainnet state intact
  5. Verify existing integrations and users aren't broken by the upgrade

Debugging User-Reported Issues

Reproduce a bug at the exact block height it occurred:


_10
flow emulator --fork mainnet --fork-height <BLOCK_HEIGHT>

Then manually interact with your dapp or run specific transactions to reproduce the issue.

Testing Wallet Integrations

Test wallet connect flows, transaction signing, and account creation against production-like state:

  1. Start forked emulator and dev wallet
  2. Use your dapp to authenticate
  3. Sign transactions as real mainnet accounts (via impersonation)
  4. Verify balance updates, event emissions, etc.

Running Bots and Indexers

Test automated tools against forked data by pointing your SDK to the local emulator:

Any Flow SDK works:

  • JavaScript/TypeScript: @onflow/fcl
  • Go: flow-go-sdk
  • Python: flow-py-sdk
  • Other languages: Configure to connect to http://localhost:8888

Example with JavaScript:


_11
// Node.js bot that monitors FlowToken transfers
_11
const fcl = require('@onflow/fcl');
_11
_11
fcl.config({
_11
'accessNode.api': 'http://localhost:8888', // Point to forked emulator
_11
});
_11
_11
async function monitorTransfers() {
_11
// Subscribe to blocks and process FlowToken events
_11
// Bot reads real mainnet data but runs locally
_11
}

Example with Go:


_10
import "github.com/onflow/flow-go-sdk/client"
_10
_10
// Connect to forked emulator
_10
flowClient, err := client.New("localhost:3569", grpc.WithInsecure())
_10
_10
// Your bot/indexer logic reads from forked mainnet state

Best Practices

1. Pin Block Heights for Reproducibility

Always pin heights in E2E tests and CI:


_10
flow emulator --fork mainnet --fork-height 85432100

Why: Ensures tests run against identical state every time.

2. Keep Emulator Running During Development

Start the forked emulator once and leave it running. Restart only when you need to change the fork height or network.

3. Use Testnet Before Mainnet

Test against testnet first to avoid mainnet access node rate limits:


_10
flow emulator --fork testnet --fork-height <BLOCK_HEIGHT>

4. Mock External Dependencies

The forked emulator only mirrors Flow blockchain state. External APIs, oracles, and cross-chain data won't work. Mock them in your E2E tests:


_10
// In Cypress: Mock external oracle response
_10
cy.intercept('GET', 'https://api.example.com/price', {
_10
statusCode: 200,
_10
body: { price: 123.45 },
_10
});

In your React app, you can mock API calls during testing while keeping real implementations for production.

5. Test Against Real User Accounts

The forked emulator disables signature validation, so you can transact as any mainnet account. Just reference the address—empty or invalid signatures are accepted:


_10
# Execute a transaction as any mainnet account
_10
flow transactions send my_transaction.cdc \
_10
--signer 0x1234567890abcdef \
_10
--network mainnet-fork

This lets you test with real NFT collector accounts, whale wallets, or any address that has interesting state on mainnet.

6. Document Your Fork Heights

Keep a log of which block heights you use for testing and why:


_10
# .env.test
_10
FORK_HEIGHT_STABLE=<BLOCK_HEIGHT_1> # Known stable state
_10
FORK_HEIGHT_LATEST=<BLOCK_HEIGHT_2> # Latest tested state

Limitations and Considerations

Network State Fetching

Fork mode fetches state from the access node on-demand. The first access to an account or contract fetches data over the network; subsequent accesses benefit from caching. With pinned block heights, caching is very effective.

Spork Boundaries

Historical data is only available within the current spork. You cannot fork to block heights from previous sporks via public access nodes.

See: Network Upgrade (Spork) Process.

Off-Chain Services

The fork only includes Flow blockchain state. External services don't work:

  • Oracles: Mock responses
  • IPFS/Arweave: Mock or run local nodes
  • Cross-chain bridges: Mock or test separately

Troubleshooting

Emulator Won't Start

Error: network "mainnet" not found in flow.json

Solution: Make sure your flow.json includes the mainnet network:


_10
{
_10
"networks": {
_10
"mainnet": "access.mainnet.nodes.onflow.org:9000"
_10
}
_10
}

Or use --fork-host directly:


_10
flow emulator --fork-host access.mainnet.nodes.onflow.org:9000

Contract Import Fails

Error: import "FlowToken" could not be resolved

Solution: Ensure your fork network is properly configured:


_10
{
_10
"networks": {
_10
"mainnet-fork": {
_10
"host": "127.0.0.1:3569",
_10
"fork": "mainnet"
_10
}
_10
}
_10
}

And that you've installed dependencies with the mainnet alias:


_10
flow dependencies install

Verify the contract has a mainnet alias that the fork can inherit.

Dapp Can't Connect

Error: Frontend can't reach the emulator

Solution: Verify FlowProvider is configured correctly:


_10
<FlowProvider
_10
config={{
_10
accessNodeUrl: 'http://localhost:8888', // Must match emulator REST port
_10
flowNetwork: 'mainnet-fork', // Use your fork network from flow.json
_10
}}
_10
flowJson={flowJSON}
_10
>
_10
<App />
_10
</FlowProvider>

Check the emulator is running and serving on port 8888.

Common mistakes:

  1. Wrong network: Using flowNetwork: 'emulator' when forking mainnet will use emulator contract addresses (0x0ae53cb6...) instead of mainnet addresses. Use your fork network name ('mainnet-fork').

  2. Missing fork network in flow.json: Make sure your flow.json has the fork network configured:


    _10
    "networks": {
    _10
    "mainnet-fork": {
    _10
    "host": "127.0.0.1:3569",
    _10
    "fork": "mainnet"
    _10
    }
    _10
    }

  3. Missing flowJson prop: The flowJson prop is required for contract import resolution. Make sure you're importing and passing your flow.json file.

Script Returns Stale Data

Issue: Script returns unexpected/old values

Solution: The fork fetches state at the pinned height or latest. Verify:


_10
# Check which block the emulator is at
_10
flow blocks get latest --network emulator

If you need fresher data, restart without --fork-height.

E2E Tests Flaky

Issue: Tests pass sometimes but fail randomly

Solution:

  1. Pin block height for consistency
  2. Add longer timeouts for network calls
  3. Check for race conditions in async code

When to Use Emulator Fork vs Test Framework Fork

Choose the right tool:

Use CaseTool
Cadence unit testsflow test (no fork)
Cadence integration tests with real contractsflow test --fork
Manual testing with dappflow emulator --fork
E2E testing (Cypress/Playwright)flow emulator --fork
Debugging frontend issuesflow emulator --fork
Testing wallets/bots/indexersflow emulator --fork

Both modes complement each other. See Testing Strategy for the full picture.

Conclusion

In this tutorial, you learned how to use the forked emulator for interactive testing, E2E test automation, and manual exploration. You created a React dapp using the Flow React SDK connected to forked mainnet, used account impersonation to test with real account states, and saw how to automate tests with E2E frameworks—all without deploying to a live network.

Now that you have completed this tutorial, you can:

  • Start the emulator in fork mode with flow emulator --fork.
  • Connect your dapp frontend to the forked emulator.
  • Test against real mainnet contracts and production data interactively.
  • Run E2E tests (Cypress, Playwright) against forked state.
  • Use account impersonation to test as any mainnet account.
  • Pin to specific block heights for reproducible testing.
  • Debug and explore contract interactions manually.

The forked emulator bridges the gap between local development and testnet/mainnet deployments. Use it to catch integration issues early, test against real-world conditions, and validate your dapp before going live.

Next Steps

  • Add E2E tests to your CI/CD pipeline using pinned fork heights
  • Test your dapp's upgrade flows against forked mainnet
  • Explore Flow React SDK hooks and components (events, mutations, Cross-VM features)
  • For Cadence contract testing, see Fork Testing with Cadence
  • Review the Testing Strategy for the full testing approach
  • Check Flow Emulator docs for advanced emulator flags