Plugin Development
This guide will walk you through the complete process of creating a React Native DevTools plugin, from initial generation to building for production.
Tip: Before creating your own plugin, check out the Official Plugins to see if there’s already a plugin that meets your needs!
Quick Start
Section titled “Quick Start”npx rozenite generatecd my-awesome-pluginrozenite devStep 1: Generate Your Plugin
Section titled “Step 1: Generate Your Plugin”The rozenite generate command creates a complete plugin project structure:
# Generate in current directoryrozenite generate
# Generate in specific directoryrozenite generate my-plugin-nameThis creates:
- Complete TypeScript project setup
- Vite build configuration with Rozenite plugin
- Sample DevTools panel
- Git repository with initial commit
- All dependencies installed
Step 2: Understanding Plugin Structure
Section titled “Step 2: Understanding Plugin Structure”Your generated plugin has this structure:
my-plugin/├── src/│ └── hello-world.tsx # Your DevTools panels├── react-native.ts # App-side entry point├── rozenite.config.ts # Plugin configuration├── vite.config.ts # Build configuration├── package.json # Dependencies and scripts└── tsconfig.json # TypeScript configurationStep 3: Creating Panels
Section titled “Step 3: Creating Panels”Panels are React components that appear in the DevTools interface, defined in your rozenite.config.ts file. Your app side can use any API or library available in the app, so a panel can integrate as deeply with the runtime as you need.
Type-safe communication
Section titled “Type-safe communication”The RozeniteDevToolsClient uses an event-based API with full TypeScript support — compile-time checks and autocomplete for event names and payloads, and one client per plugin ID so events from different plugins never collide.
Client API
Section titled “Client API”// Hook usageconst client = useRozeniteDevToolsClient<EventMap>({ pluginId: 'your-plugin-id',});
// Client methodsclient.send('event-name', payload); // Send typed eventclient.onMessage('event-name', callback); // Listen for typed eventclient.close(); // Clean up connectionexport default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, { name: 'Another Panel', source: './src/another-panel.tsx', }, ],};Dev Host Configuration
Section titled “Dev Host Configuration”rozenite.config.ts can also define helpers for the in-browser dev host that rozenite dev launches.
export default { panels: [ { name: 'Storage', source: './src/storage-panel.tsx', }, ], dev: { presets: [ { name: 'Get snapshot', type: 'get-snapshot', payload: { target: 'all' }, }, ], flows: [ { name: 'Initialize', autoRun: true, async run({ send, waitForMessage }) { await waitForMessage({ type: 'get-snapshot', direction: 'in' }); send('snapshot', { items: [] }); }, }, ], },};dev.presetsadds ready-made command payloads to the Presets button in the Actions pane. Use presets when you want to quickly re-send common messages while iterating on your panel.dev.flowsadds runnable scripts to the Flows tab in the Actions pane. Use flows for small test routines like bootstrapping state, waiting for a request, or simulating a multi-step exchange.- Set
autoRun: trueon a flow when it should start automatically after the panel iframe loads. This is useful for initialization routines that should begin listening immediately. - These helpers are for the dev host workflow. They do not change the production plugin manifest.
Panel Configuration Options
Section titled “Panel Configuration Options”| Property | Type | Description |
|---|---|---|
name |
string |
Display name in DevTools sidebar |
source |
string |
Path to your React component |
Declaring Supported Integrations
Section titled “Declaring Supported Integrations”rozenite.config.ts can also declare which environments your plugin supports:
export default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, ], integrations: ['react-native'],};The valid ids are react-native, react-native-web, lynx, and lynx-web. Omitting integrations defaults to ['react-native'], the safe assumption for a plugin that predates this field. Declaring an id no integration reports yet (currently lynx-web) is harmless — it just means nothing refuses to load on it today.
An unknown id fails the build. To catch one while you type it instead, annotate the config:
import type { RozeniteConfig } from '@rozenite/vite-plugin';
export default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, ], integrations: ['react-native'],} satisfies RozeniteConfig;Declare an integration only if the plugin’s device-side code actually runs there. It is the imports that decide this, not the plugin’s own logic: a panel that never touches a native API still cannot declare react-native-web if the hook it ships imports TurboModuleRegistry or DevSettings, neither of which react-native-web provides.
Creating a Panel Component
Section titled “Creating a Panel Component”Create a new panel by adding a React component:
import React from 'react';
export default function MyPanel() { return ( <div style={{ padding: '16px' }}> <h2>My Custom Panel</h2> <p>This is my custom DevTools panel!</p> </div> );}Using the Plugin Bridge
Section titled “Using the Plugin Bridge”Connect your panel to React Native using the plugin bridge. The RozeniteDevToolsClient provides full TypeScript support for type-safe communication:
import React, { useEffect, useState } from 'react';import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
// Define type-safe event mapinterface PluginEvents { 'user-data': { id: string; name: string; email: string; }; 'request-user-data': { type: 'userInfo'; };}
export default function MyPanel() { const client = useRozeniteDevToolsClient<PluginEvents>({ pluginId: 'my-user-panel', }); const [userData, setUserData] = useState<PluginEvents['user-data'] | null>( null );
useEffect(() => { if (!client) return;
// Type-safe message listener const subscription = client.onMessage('user-data', (data) => { // TypeScript knows data is PluginEvents['user-data'] setUserData(data); });
// Type-safe message sending client.send('request-user-data', { type: 'userInfo' });
return () => subscription.remove(); }, [client]);
if (!client) { return <div>Connecting to React Native...</div>; }
return ( <div style={{ padding: '16px' }}> <h2>User Data Panel</h2> {userData ? ( <div> <p> <strong>Name:</strong> {userData.name} </p> <p> <strong>Email:</strong> {userData.email} </p> </div> ) : ( <p>Loading user data...</p> )} </div> );}Step 4: App-Side Integration
Section titled “Step 4: App-Side Integration”Add app-side functionality by creating a react-native.ts file. The filename is the same on every target Rozenite supports. On React Native you can reach for React Native APIs and libraries:
import { DevToolsPluginClient } from '@rozenite/plugin-bridge';import { Platform, Dimensions } from 'react-native';
// Use the same type-safe event mapinterface PluginEvents { 'user-data': { id: string; name: string; email: string; }; 'request-user-data': { type: 'userInfo'; };}
export default function setupPlugin( client: DevToolsPluginClient<PluginEvents>) { // Handle messages from DevTools panels with full type safety client.onMessage('request-user-data', (data) => { // Access React Native APIs const deviceInfo = { platform: Platform.OS, version: Platform.Version, dimensions: Dimensions.get('window'), };
// Send type-safe response client.send('user-data', { id: 'user-123', name: 'John Doe', }); });}Step 5: Local Development Workflow
Section titled “Step 5: Local Development Workflow”Complete Development Setup
Section titled “Complete Development Setup”For local plugin development, follow these steps:
Step 1: Create and Start Your Plugin
Section titled “Step 1: Create and Start Your Plugin”# Create a new pluginrozenite generatecd my-awesome-plugin
# Start the development serverrozenite devThis starts a development server that:
- Watches for file changes
- Hot reloads your panels automatically
- Opens the Rozenite dev host in your browser (see below)
- Provides real-time feedback during development
Step 2: Develop panels in the browser (no playground app)
Section titled “Step 2: Develop panels in the browser (no playground app)”rozenite dev uses @rozenite/vite-plugin to serve a dev host at the root of the dev server (by default http://localhost:8888/). You can iterate on DevTools panels without running a separate playground app:
- Panel preview — Every entry in
rozenite.config.tsappears as a tab. The selected panel loads inside an iframe, similar to how it is embedded in React Native DevTools. - Message log — Outbound messages from your panel (the same
rozenite-messageenvelope the plugin bridge uses when talking to the parent) are listed with timestamps so you can see what the panel emitted. - Dispatch message — Send a command
typeand JSONpayloadinto the iframe as if DevTools had sent it. The host fills inpluginIdfrom your packagenameinpackage.json. That value must match thepluginIdyou pass touseRozeniteDevToolsClient/getRozeniteDevToolsClient; otherwise your handlers will not run. - Presets — Any
dev.presetsentries fromrozenite.config.tsappear in the Actions pane so you can populate common command and payload combinations with one click. - Flows — Any
dev.flowsentries appear in a dedicated Flows tab so you can run repeatable dev routines against the panel iframe. Flows withautoRun: truestart automatically when the preview reloads.
The dev server port is aligned with Rozenite runtime dev mode: when you set ROZENITE_DEV_MODE to your plugin package name, the app loads the plugin from http://localhost:8888, so one rozenite dev process can serve both the in-browser host and the in-app plugin bundle.
Use this flow for rapid UI work and bridge message shapes. To exercise react-native.ts and native integration, continue with a real app (next steps).
Step 3: Link to a React Native app (optional, for native side)
Section titled “Step 3: Link to a React Native app (optional, for native side)”- Create or use a React Native project that has Rozenite configured (for example the repository playground app).
- Add your plugin to the app’s dependencies (you can use
npm link,yarn link, orpnpm linkfor local development).
Step 4: Run your React Native app
Section titled “Step 4: Run your React Native app”# In your playground project directory# Set ROZENITE_DEV_MODE to your plugin package name (from package.json) to load it in dev modeROZENITE_DEV_MODE=@scope/my-awesome-plugin npx react-native start# Or if using ExpoROZENITE_DEV_MODE=@scope/my-awesome-plugin npx expo startThen run the app on your device or simulator.
Step 5: Open DevTools
Section titled “Step 5: Open DevTools”- Open React Native DevTools in your browser
- Your plugin panels should appear in the sidebar automatically
Hot Reloading
Section titled “Hot Reloading”Your development workflow supports automatic hot reloading:
- Panel changes: Your DevTools panels will automatically update when you make changes to your plugin code
- React Native integration changes: Changes to your
react-native.tsfile will also hot reload - New panels: If you add a new panel to your
rozenite.config.ts, restart React Native DevTools by pressingCtrl+R(orCmd+Ron Mac) - Configuration changes: Most changes to
rozenite.config.tsrequire a DevTools restart
Checking Your Plugin By Hand
Section titled “Checking Your Plugin By Hand”- Make changes to your panel components - they should update instantly
- Modify your React Native integration code - changes should be reflected immediately
- Add new panels - remember to restart DevTools with
Ctrl+R - Test communication between your panels and React Native code
To cover the last one with automated tests instead - running your panel and your react-native.ts code against each other in Node, without Metro or a simulator - see Testing.
Step 6: Building for Production
Section titled “Step 6: Building for Production”Build your plugin for distribution:
rozenite buildThis creates optimized bundles:
- DevTools panels (minified and optimized)
- App-side entry point (if
react-native.tsexists) - Ready for distribution
Build Output
Section titled “Build Output”The build creates a dist/ directory with:
*.js- Individual DevTools panel files (one file per panel, names reflect your config)react-native.js- app-side integration (if applicable)rozenite.json- Plugin manifest with metadata and configuration- Source maps for debugging
Next Steps
Section titled “Next Steps”- Write tests for your plugin’s communication with Testing
- Check out Official Plugins to see available plugins
- Join the community to share your plugins and get help