WebSocket support allows you to remotely control your on-device Storybook from a browser or external tools. This enables real-time synchronization between multiple Storybook instances.
Overview
The WebSocket server provides:
- Remote story selection: Control which story is displayed from external tools
- Event broadcasting: Send custom events to all connected clients
- Story index API: Fetch the list of all available stories
- MCP support: AI agent integration for component documentation (experimental)
Metro Configuration
Enable WebSockets in your Metro configuration:
const { getDefaultConfig } = require('expo/metro-config');
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config, {
websockets: 'auto',
});
WebSocket Options
interface WebsocketsOptions {
port?: number;
host?: string;
}
The port the WebSocket server will listen on.withStorybook(config, {
websockets: { port: 7007 },
});
host
string
default:"localhost"
The host the WebSocket server will bind to.Set to 'auto' to automatically use your local IP address for LAN connections.// Auto-detect host IP
withStorybook(config, {
websockets: 'auto',
});
// Specific host
withStorybook(config, {
websockets: { host: '0.0.0.0', port: 7007 },
});
When host is 'auto', the server binds to all network interfaces and automatically detects your local IP address for the client connection.
Client Configuration
Enable WebSocket connections in your app:
import { start } from '@storybook/react-native';
const view = start({
annotations,
storyEntries,
});
const StorybookUI = view.getStorybookUI({
enableWebsockets: true,
host: 'localhost',
port: 7007,
});
export default StorybookUI;
Client Options
Enable WebSocket connections to the server.view.getStorybookUI({
enableWebsockets: true,
});
host
string
default:"Platform dependent"
The WebSocket server host to connect to.
- Android emulator: Defaults to
10.0.2.2 (host machine)
- iOS simulator & physical devices: Defaults to
localhost
If WebSockets are configured in Metro config, this value is automatically injected.view.getStorybookUI({
enableWebsockets: true,
host: '192.168.1.100', // Your computer's IP
});
The WebSocket server port to connect to.If WebSockets are configured in Metro config, this value is automatically injected.view.getStorybookUI({
enableWebsockets: true,
port: 7007,
});
Use secure WebSocket connections (wss:// instead of ws://).view.getStorybookUI({
enableWebsockets: true,
secured: true,
});
query
string
default:"undefined"
Optional query string to append to the WebSocket URL.view.getStorybookUI({
enableWebsockets: true,
query: 'token=abc123',
});
Server API
The WebSocket server provides several HTTP endpoints:
GET /index.json
Returns the story index with all available stories.
curl http://localhost:7007/index.json
Response:
{
"v": 5,
"entries": {
"button--primary": {
"type": "story",
"id": "button--primary",
"name": "Primary",
"title": "Button",
"importPath": "./components/Button.stories.tsx",
"tags": ["story"]
}
}
}
POST /send-event
Sends an event to all connected WebSocket clients.
curl -X POST http://localhost:7007/send-event \
-H "Content-Type: application/json" \
-d '{"type":"setCurrentStory","args":[{"storyId":"button--primary"}]}'
This endpoint requires WebSockets to be enabled. It returns a 503 error if WebSockets are disabled.
WebSocket Connection
Connect to ws://localhost:7007 for real-time bidirectional communication.
const ws = new WebSocket('ws://localhost:7007');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
ws.send(JSON.stringify({
type: 'setCurrentStory',
args: [{ storyId: 'button--primary' }]
}));
The server broadcasts all received messages to all connected clients (except the sender).
Examples
Complete Configuration
const { getDefaultConfig } = require('expo/metro-config');
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config, {
websockets: {
port: 7007,
host: 'localhost',
},
});
import { start } from '@storybook/react-native';
const view = start({ annotations, storyEntries });
const StorybookUI = view.getStorybookUI({
enableWebsockets: true,
host: 'localhost',
port: 7007,
});
export default StorybookUI;
Auto Configuration (Recommended)
Use 'auto' to automatically configure WebSockets with sensible defaults:
module.exports = withStorybook(config, {
websockets: 'auto',
});
The server will:
- Listen on port 7007
- Auto-detect your local IP address
- Inject the configuration into your app automatically
LAN Testing
Test your Storybook on physical devices over your local network:
module.exports = withStorybook(config, {
websockets: {
port: 7007,
host: '0.0.0.0', // Bind to all interfaces
},
});
const StorybookUI = view.getStorybookUI({
enableWebsockets: true,
host: '192.168.1.100', // Your computer's LAN IP
port: 7007,
});
Troubleshooting
Port Already in Use
If you see the error:
[Storybook] Port 7007 is already in use. The channel server will not start.
Another instance is already running. Either:
- Stop the other instance
- Use a different port
withStorybook(config, {
websockets: { port: 7008 },
});
Connection Failed
If WebSocket connections fail:
- Check the host: Ensure the host is reachable from your device
- Firewall: Verify your firewall allows connections on the WebSocket port
- Network: Ensure both devices are on the same network (for LAN testing)
Android Emulator
The Android emulator uses a special IP address to reach the host machine:
view.getStorybookUI({
enableWebsockets: true,
host: '10.0.2.2', // Host machine from Android emulator
port: 7007,
});
Heartbeat
The WebSocket server sends a ping message every 10 seconds to keep connections alive:
{"type": "ping", "args": []}
Clients don’t need to respond to ping messages.