npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rn-backstage

v1.5.3

Published

A zero-dependency developer/QA debug panel for React Native apps

Readme

rn-backstage

npm version npm downloads

A zero-dependency developer/QA debug panel for React Native apps. Inspect device info, view state trees, monitor console logs, inspect network requests, and trigger custom actions — all from a sleek in-app panel.

Features

  • 🎯 Draggable floating pill — always accessible, repositionable trigger with safe area bounds
  • 📱 Device & build info — OS version, app version, build number, and custom data
  • 🌳 State tree inspector — visualize Redux, Zustand, or any store state
  • 📋 Console log viewer — intercepts all console methods with search & filtering
  • 🌐 Network inspector — intercepts fetch & XMLHttpRequest with request/response details, headers, body, timing, and copy-as-cURL
  • 🎚 Feature flag toggle — render switches to toggle flags in real-time without restarting
  • 🗄 Storage viewer — inspect, edit, and delete AsyncStorage/MMKV entries via a pluggable adapter
  • Quick actions — add custom buttons (logout, clear cache, etc.)
  • 🔌 Extensible tabs — add custom tabs for app-specific debugging tools
  • 🎨 Light & dark theme — auto-follows device setting, or override manually
  • 📦 Zero dependencies — only peer deps are react and react-native

Installation

npm install rn-backstage
# or
yarn add rn-backstage

No additional native dependencies required!

Usage

import { Backstage } from 'rn-backstage'

export default function App() {
  return (
    <>
      {/* Your app content */}
      <Backstage
        appVersion="1.2.3"
        buildNumber="42"
        bundleId="com.example.app"
        state={store.getState()}
        quickActions={[
          { title: 'Logout', onPress: handleLogout, destructive: true },
          { title: 'Clear Cache', onPress: clearCache },
        ]}
        onCopyLogs={logs => Clipboard.setString(logs)}
      />
    </>
  )
}

Network Inspector

The Network tab automatically intercepts all fetch() and XMLHttpRequest traffic — including libraries built on top of them like Axios, Apisauce, ky, and Apollo Client.

Each request shows:

  • Method, URL, status code, and duration
  • Request & response headers
  • Request & response body (auto-parsed JSON with tree view)
  • Response size
  • Copy as cURL (long-press any request)
<Backstage
  // Network inspector is enabled by default
  enableNetworkInspector={true}
  // Exclude noisy URLs (analytics, Sentry, etc.)
  networkFilters={['sentry.io', 'analytics', 'hot-update']}
  // Limit body capture size (default: 64KB)
  maxNetworkBodySize={65536}
  // Auto-filters console.logs from Axios interceptors / fetch .then() chains
  // out of the Logs tab (they're already in the Network tab). Default: true
  autoFilterNetworkLogs={true}
/>

Feature Flag Toggle

Pass feature flags to render a dedicated Flags tab with toggle switches. The tab only appears when at least one flag is provided. Toggling calls your callback in real-time — no app restart needed.

const [flags, setFlags] = useState([
  { key: 'dark_mode', label: 'Dark Mode', value: true, description: 'Enable dark theme' },
  { key: 'beta', label: 'Beta Features', value: false, description: 'Experimental features' },
])

<Backstage
  featureFlags={flags}
  onToggleFeatureFlag={(key, value) => {
    setFlags(prev => prev.map(f => (f.key === key ? { ...f, value } : f)))
  }}
/>

Storage Viewer

Pass a storageAdapter to render a Storage tab that lets you browse, edit, add, and delete key-value entries. The tab only appears when an adapter is provided. Works with any storage backend — zero dependencies.

import AsyncStorage from '@react-native-async-storage/async-storage'
;<Backstage
  storageAdapter={{
    getAllKeys: () => AsyncStorage.getAllKeys(),
    getItem: key => AsyncStorage.getItem(key),
    setItem: (key, value) => AsyncStorage.setItem(key, value),
    removeItem: key => AsyncStorage.removeItem(key),
  }}
/>

MMKV example:

import { storage } from './mmkv'
;<Backstage
  storageAdapter={{
    getAllKeys: () => Promise.resolve(storage.getAllKeys()),
    getItem: key => Promise.resolve(storage.getString(key) ?? null),
    setItem: (key, value) => Promise.resolve(storage.set(key, value)),
    removeItem: key => Promise.resolve(storage.delete(key)),
  }}
/>

Bug Report

Add a bugReport config to enable one-tap bug reporting. Tapping the 🐛 button in the panel header opens a composer that auto-attaches device info, logs, network activity, and state. Reports can be shared via the system share sheet or submitted to a webhook.

<Backstage
  bugReport={{
    onSubmit: report => {
      // Full BugReport object with all context
      console.log(report.title, report.severity, report.logs.length)
    },
    // Optional: POST to a webhook
    webhookUrl: 'https://your-api.com/bugs',
    // Optional: capture screenshot (requires a library like react-native-view-shot)
    captureScreenshot: () => viewShotRef.current.capture(),
    maxLogsInReport: 50,
    maxNetworkEntriesInReport: 20,
  }}
/>

Environment Switcher

Add an environmentConfig prop to enable per-environment credential management and login. Each environment shows a card with radio selection. The active environment expands to show saved credentials — each with Login, Edit, and Delete actions. Add/edit opens a fullscreen modal with KeyboardAvoidingView.

const [activeEnv, setActiveEnv] = useState('dev')

<Backstage
  environmentConfig={{
    environments: [
      { key: 'dev', label: 'Development', baseUrl: 'https://api.dev.example.com', color: '#10B981' },
      { key: 'staging', label: 'Staging', baseUrl: 'https://api.staging.example.com', color: '#F59E0B' },
      { key: 'prod', label: 'Production', baseUrl: 'https://api.example.com', color: '#EF4444' },
    ],
    activeEnvironment: activeEnv,
    onEnvironmentChange: key => setActiveEnv(key),
    credentialFields: [
      { key: 'email', label: 'Email', keyboardType: 'email-address' },
      { key: 'password', label: 'Password', secureTextEntry: true },
    ],
    onLogin: (envKey, credentials) => {
      // credentials = { email: '...', password: '...' }
      authService.login(envKey, credentials)
    },
    // Pre-fill multiple credentials per environment
    initialCredentials: {
      dev: [
        { name: 'Admin', values: { email: '[email protected]', password: 'admin123' } },
        { name: 'Test User', values: { email: '[email protected]', password: 'user123' } },
      ],
    },
    // Optional: persist credentials to storage (reuses StorageAdapter)
    storageAdapter: myStorageAdapter,
  }}
/>

When storageAdapter is provided, credentials auto-save on change and auto-load on mount. Without it, credentials are in-memory only.

Props

| Prop | Type | Default | Description | | ------------------------ | ----------------------------- | ----------- | ----------------------------------------------- | | visible | boolean | true | Show/hide the floating pill | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Theme preference; auto follows device setting | | appVersion | string | undefined | App version to display | | buildNumber | string | undefined | Build number | | bundleId | string | undefined | Bundle identifier | | deviceInfo | AppInfoItem[] | [] | Additional device/app info rows | | state | object | undefined | State tree to inspect | | quickActions | QuickAction[] | [] | Custom action buttons | | featureFlags | FeatureFlag[] | [] | Feature flags with toggle switches | | onToggleFeatureFlag | (key, val: boolean) => void | undefined | Callback when a flag is toggled | | storageAdapter | StorageAdapter | undefined | Storage adapter for the Storage Viewer tab | | maxLogs | number | 500 | Maximum logs to retain | | logFilters | string[] | [] | Messages to exclude from logs | | onCopyLogs | (logs: string) => void | undefined | Callback when copying logs | | enableNetworkInspector | boolean | true | Enable/disable network request interception | | maxNetworkEntries | number | 500 | Maximum network entries to retain | | maxNetworkBodySize | number | 65536 | Max body size (bytes) to capture per request | | networkFilters | string[] | [] | URL substrings to exclude from capture | | autoFilterNetworkLogs | boolean | true | Auto-filter network callback logs from Logs tab | | jsonMaxDepth | number | 10 | Max nesting depth for all JSON tree views | | initialX | number | undefined | Initial X position for the floating pill | | initialY | number | undefined | Initial Y position for the floating pill | | pillText | string | undefined | Text on the pill (defaults to version or "DEV") | | pillWidth | number | 60 | Width of the floating pill | | pillHeight | number | 32 | Height of the floating pill | | extraTabs | BackstageTab[] | [] | Additional custom tabs | | bugReport | BugReportConfig | undefined | Bug report config (shows 🐛 button in header) | | environmentConfig | EnvironmentConfig | undefined | Environment switcher config (shows 🔐 Env tab) | | styles | BackstageStyleOverrides | undefined | Custom style overrides | | children | ReactNode | undefined | Extra content in InfoTab |

Ref Methods

const ref = useRef<BackstageRef>(null)

ref.current?.open() // Open the panel
ref.current?.close() // Close the panel
ref.current?.clearLogs() // Clear all captured logs
ref.current?.clearNetworkEntries() // Clear all captured network entries
ref.current?.submitBugReport() // Open bug report composer

Individual Components

All internal components are exported for advanced use cases — compose your own custom debug UI:

import {
  BackstagePanel,
  FloatingPill,
  TabBar,
  InfoTab,
  LogsTab,
  LogItem,
  NetworkTab,
  NetworkItem,
  FlagsTab,
  StorageTab,
  BugReportComposer,
  EnvironmentTab,
  JsonTreeView, // useful standalone for any JSON data
} from 'rn-backstage'

TestIDs

All interactive elements have consistent testID attributes for E2E testing. Import the TestIDs object to reference them:

import { TestIDs } from 'rn-backstage'

// Static IDs
TestIDs.floatingPill // 'backstage.floating-pill'
TestIDs.panel // 'backstage.panel'
TestIDs.header.closeButton // 'backstage.header.close'

// Dynamic IDs (for items in lists)
TestIDs.logItem.container(id) // 'backstage.log-item.{id}'
TestIDs.flagsTab.flagSwitch(key) // 'backstage.flag.{key}'
TestIDs.storageTab.entryRow(key) // 'backstage.storage.entry.{key}'
TestIDs.environmentTab.environmentCard(key) // 'backstage.environment.card.{key}'
TestIDs.environmentTab.loginButton(key) // 'backstage.environment.{key}.login'

Try It

Scan with Expo Go to try the example app on your device:

License

MIT