The Expo DevTools system provides a plugin-based architecture for integrating development tools, debuggers, and monitoring systems into your Expo applications. It enables real-time communication between development tools and running applications through a standardized plugin interface.
Core client for connecting to and communicating with DevTools plugins during development.
/**
* DevTools plugin client for communicating with development tools
*/
class DevToolsPluginClient {
/**
* Add a listener for messages from the DevTools plugin
* @param listener - Function to handle incoming messages
* @returns Subscription object for removing the listener
*/
addMessageListener(listener: (message: any) => void): EventSubscription;
/**
* Add a listener for messages that only fires once
* @param listener - Function to handle incoming message
*/
addMessageListenerOnce(listener: (message: any) => void): void;
/**
* Send a message to the connected DevTools plugin
* @param message - Message to send to the plugin
*/
sendMessage(message: any): void;
/**
* Close the connection to the DevTools plugin
* @returns Promise that resolves when connection is closed
*/
closeAsync(): Promise<void>;
/**
* Check if the client is currently connected to a plugin
* @returns True if connected, false otherwise
*/
isConnected(): boolean;
}Factory function for creating DevTools plugin clients with configuration options.
/**
* Creates a DevTools plugin client for the specified plugin
* @param pluginName - Name of the DevTools plugin to connect to
* @param options - Optional configuration for the client connection
* @returns Promise resolving to the connected plugin client
*/
function getDevToolsPluginClientAsync(
pluginName: string,
options?: DevToolsPluginClientOptions
): Promise<DevToolsPluginClient>;
/**
* React hook to get the DevToolsPluginClient instance
* @param pluginName - Name of the plugin to connect to
* @param options - Optional configuration for the client
* @returns DevToolsPluginClient instance or null if not connected
*/
function useDevToolsPluginClient(
pluginName: string,
options?: DevToolsPluginClientOptions
): DevToolsPluginClient | null;
/**
* Enable or disable logging for DevTools
* @param enabled - Whether to enable logging
*/
function setEnableLogging(enabled: boolean): void;
/**
* Configuration options for DevTools plugin clients
*/
interface DevToolsPluginClientOptions {
/** The underlying WebSocket binaryType ('arraybuffer' or 'blob') */
websocketBinaryType?: 'arraybuffer' | 'blob';
}React hook for managing DevTools plugin connections within React components.
/**
* React hook for managing a DevTools plugin client connection
* @param pluginName - Name of the DevTools plugin to connect to
* @param options - Optional configuration for the client connection
* @returns The connected plugin client or null if not connected
*/
function useDevToolsPluginClient(
pluginName: string,
options?: DevToolsPluginClientOptions
): DevToolsPluginClient | null;Control logging behavior for the DevTools system.
/**
* Enable or disable logging for the DevTools system
* @param enable - Whether to enable detailed logging
*/
function setEnableLogging(enable: boolean): void;import { getDevToolsPluginClientAsync } from 'expo/devtools';
async function connectToDebugger() {
try {
const client = await getDevToolsPluginClientAsync('debugger', {
websocketBinaryType: 'arraybuffer',
});
// Listen for messages from the debugger plugin
const subscription = client.addMessageListener('breakpoint', (params) => {
console.log('Debugger breakpoint:', params);
handleBreakpoint(params);
});
// Send debugging information to the plugin
client.sendMessage('state_update', {
timestamp: Date.now(),
data: { userId: 123, screen: 'Profile' },
});
return { client, subscription };
} catch (error) {
console.error('Failed to connect to debugger plugin:', error);
return null;
}
}import React, { useEffect } from 'react';
import { useDevToolsPluginClient } from 'expo/devtools';
function DebuggingComponent() {
const debugClient = useDevToolsPluginClient('performance-monitor', {
autoReconnect: true,
});
useEffect(() => {
if (debugClient?.isConnected()) {
// Set up message listener
const subscription = debugClient.addMessageListener((message) => {
if (message.type === 'performance_request') {
// Respond with performance metrics
debugClient.sendMessage({
type: 'performance_data',
metrics: {
renderTime: performance.now(),
memoryUsage: performance.memory?.usedJSHeapSize,
},
});
}
});
return () => subscription.remove();
}
}, [debugClient]);
return null; // This is a debugging component
}import { getDevToolsPluginClientAsync, setEnableLogging } from 'expo/devtools';
class CustomDevToolsPlugin {
private client: DevToolsPluginClient | null = null;
private messageSubscription: EventSubscription | null = null;
async initialize(pluginName: string) {
// Enable detailed logging for development
setEnableLogging(__DEV__);
try {
this.client = await getDevToolsPluginClientAsync(pluginName, {
requestInterceptor: (request) => {
// Log all outgoing requests
console.log('DevTools request:', request);
return request;
},
responseInterceptor: (response) => {
// Log all incoming responses
console.log('DevTools response:', response);
return response;
},
});
this.messageSubscription = this.client.addMessageListener(
this.handleMessage.bind(this)
);
console.log('DevTools plugin initialized successfully');
} catch (error) {
console.error('Failed to initialize DevTools plugin:', error);
}
}
private handleMessage(message: any) {
switch (message.type) {
case 'inspect_component':
this.sendComponentInfo(message.componentId);
break;
case 'update_props':
this.updateComponentProps(message.componentId, message.props);
break;
case 'reload_request':
this.handleReloadRequest();
break;
default:
console.warn('Unknown DevTools message type:', message.type);
}
}
sendComponentInfo(componentId: string) {
if (this.client?.isConnected()) {
this.client.sendMessage({
type: 'component_info',
componentId,
data: {
props: getComponentProps(componentId),
state: getComponentState(componentId),
children: getComponentChildren(componentId),
},
});
}
}
async cleanup() {
if (this.messageSubscription) {
this.messageSubscription.remove();
this.messageSubscription = null;
}
if (this.client) {
await this.client.closeAsync();
this.client = null;
}
}
}import { useDevToolsPluginClient } from 'expo/devtools';
function NetworkDebugger() {
const networkClient = useDevToolsPluginClient('network-debugger');
// Intercept fetch requests for debugging
const originalFetch = global.fetch;
global.fetch = async (input, init) => {
const startTime = Date.now();
// Log request to DevTools
networkClient?.sendMessage({
type: 'network_request',
method: init?.method || 'GET',
url: input.toString(),
headers: init?.headers,
timestamp: startTime,
});
try {
const response = await originalFetch(input, init);
const endTime = Date.now();
// Log response to DevTools
networkClient?.sendMessage({
type: 'network_response',
url: input.toString(),
status: response.status,
statusText: response.statusText,
duration: endTime - startTime,
timestamp: endTime,
});
return response;
} catch (error) {
// Log error to DevTools
networkClient?.sendMessage({
type: 'network_error',
url: input.toString(),
error: error.message,
timestamp: Date.now(),
});
throw error;
}
};
return null;
}The DevTools system supports various message types for different debugging scenarios:
// Performance monitoring
interface PerformanceMessage {
type: 'performance_data';
metrics: {
renderTime: number;
memoryUsage?: number;
bundleSize?: number;
};
}
// Component inspection
interface ComponentMessage {
type: 'component_info';
componentId: string;
data: {
props: Record<string, any>;
state: Record<string, any>;
children: string[];
};
}
// Network debugging
interface NetworkMessage {
type: 'network_request' | 'network_response' | 'network_error';
url: string;
method?: string;
status?: number;
duration?: number;
error?: string;
}
// State management
interface StateMessage {
type: 'state_update';
store: string;
action: {
type: string;
payload: any;
};
newState: any;
}import { getDevToolsPluginClientAsync } from 'expo/devtools';
async function connectWithErrorHandling() {
try {
const client = await getDevToolsPluginClientAsync('my-plugin', {
connectionTimeout: 3000,
});
if (!client.isConnected()) {
throw new Error('Plugin client is not connected');
}
return client;
} catch (error) {
if (error.message.includes('timeout')) {
console.warn('DevTools plugin connection timed out');
} else if (error.message.includes('not found')) {
console.warn('DevTools plugin not available');
} else {
console.error('Unexpected DevTools error:', error);
}
return null;
}
}function setupRobustMessageHandling(client: DevToolsPluginClient) {
const subscription = client.addMessageListener((message) => {
try {
// Validate message structure
if (!message || typeof message !== 'object') {
console.warn('Invalid DevTools message received:', message);
return;
}
// Handle message based on type
handleDevToolsMessage(message);
} catch (error) {
console.error('Error handling DevTools message:', error);
// Send error back to plugin
client.sendMessage({
type: 'error',
originalMessage: message,
error: error.message,
});
}
});
return subscription;
}import { getDevToolsPluginClientAsync } from 'expo/devtools';
// Only enable DevTools in development
const client = __DEV__
? await getDevToolsPluginClientAsync('debugger')
: null;
if (client) {
// Development-only debugging code
setupDevelopmentDebugging(client);
}// Safe DevTools usage that won't break production
function safeDevToolsCall(callback: (client: DevToolsPluginClient) => void) {
if (__DEV__ && window.__EXPO_DEVTOOLS_CLIENT__) {
try {
callback(window.__EXPO_DEVTOOLS_CLIENT__);
} catch (error) {
console.warn('DevTools operation failed:', error);
}
}
}