Skip to content
msw-devtool logomsw-devtool
msw-devtool logo

msw-devtool

A TanStack DevTools plugin for managing MSW mocks.
Toggle, customize, and inspect your mock handlers in real time.

This project is a work in progress — the API has not been finalised yet, which is why we haven't reached 1.0.0. Expect breaking changes between minor versions.

Toggle Mocks
Switch Variants
Live Overrides
LIVE Tracking
Filter & Sort
Auto Refetch

Works with

TanStack QueryRTK QueryURQLApollo ClientSWRAxiosfetch

Why msw-devtool?

Tired of commenting out handlers, hard-coding error responses, and refreshing the page every time you need a different mock? msw-devtool lets you toggle, swap, and override any MSW mock on the fly — right from the browser, without touching your code.

Install the core library:

bash
npm install msw-devtools-plugin

This library renders inside TanStack DevTools, so you'll need that too:

bash
npm install @tanstack/react-devtools

1Register your handlers

Pass your MSW handlers directly to registerRestMocks or registerGraphqlMocks. The operation name, method, and path are auto-derived from the handler:

typescript
import { http, HttpResponse, graphql } from "msw";
import { registerRestMocks, registerGraphqlMocks } from "msw-devtools-plugin";

// REST — pass your HttpHandler directly
registerRestMocks(
  {
    handler: http.get("/api/users", () =>
      HttpResponse.json([{ id: 1, name: "Alice" }])
    ),
    group: "Users",
  },
);

// GraphQL — pass your GraphQLHandler directly
registerGraphqlMocks(
  {
    handler: graphql.query("GetUser", () =>
      HttpResponse.json({ data: { user: { id: 1, name: "Alice" } } })
    ),
    group: "Users",
  },
);
2Mount the DevTools plugin

Add the TanStack DevTools component to your app with the MSW plugin. The service worker starts automatically when the plugin mounts:

tsx
// App.tsx
import { TanStackDevtools } from "@tanstack/react-devtools";
import { createMswDevToolsPlugin } from "msw-devtools-plugin";
import "./mocks/setup"; // your registration calls

function App() {
  return (
    <>
      <YourApp />
      <TanStackDevtools plugins={[createMswDevToolsPlugin()]} />
    </>
  );
}
3Register adapters (optional)

Adapters connect your data-fetching library to the devtools. When you toggle a mock or switch variants, the adapter automatically refetches/revalidates so your UI updates immediately.

typescript
import { registerAdapter } from "msw-devtools-plugin";
import { createTanStackQueryAdapter } from "msw-devtools-plugin/adapters/tanstack-query";
import { createRtkQueryAdapter } from "msw-devtools-plugin/adapters/rtk-query";
import { createUrqlAdapter } from "msw-devtools-plugin/adapters/urql";
import { createApolloAdapter } from "msw-devtools-plugin/adapters/apollo";
import { createAxiosAdapter } from "msw-devtools-plugin/adapters/axios";

// Pick the adapters matching your stack:
registerAdapter(createTanStackQueryAdapter(queryClient));
registerAdapter(createRtkQueryAdapter(store, pokemonApi));
registerAdapter(createUrqlAdapter());
registerAdapter(createApolloAdapter(apolloClient));
registerAdapter(createAxiosAdapter());

Each adapter hooks into a specific library's cache invalidation mechanism. You only need to register the adapters for the libraries you actually use.

TanStack Query

Invalidates all queries via queryClient.invalidateQueries() when mock config changes. Only queries with active subscriptions (i.e., components currently mounted and observing them) are refetched.

typescript
import { QueryClient } from "@tanstack/react-query";
import { registerAdapter } from "msw-devtools-plugin";
import { createTanStackQueryAdapter } from "msw-devtools-plugin/adapters/tanstack-query";

const queryClient = new QueryClient();
registerAdapter(createTanStackQueryAdapter(queryClient));

URQL

Uses a custom exchange that re-executes active queries. Add mockRefetchExchange to your URQL client's exchange pipeline:

typescript
import { createClient, cacheExchange, fetchExchange } from "@urql/core";
import { registerAdapter } from "msw-devtools-plugin";
import { createUrqlAdapter, mockRefetchExchange } from "msw-devtools-plugin/adapters/urql";

registerAdapter(createUrqlAdapter());

const client = createClient({
  url: "/graphql",
  exchanges: [cacheExchange, mockRefetchExchange, fetchExchange],
});

Apollo Client

Calls apolloClient.refetchQueries() to re-run all active queries.

typescript
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { registerAdapter } from "msw-devtools-plugin";
import { createApolloAdapter } from "msw-devtools-plugin/adapters/apollo";

const apolloClient = new ApolloClient({ uri: "/graphql", cache: new InMemoryCache() });
registerAdapter(createApolloAdapter(apolloClient));

RTK Query

Resets the API state via api.util.resetApiState() when mock config changes. Only endpoints with active subscriptions (i.e., components currently mounted and using the generated hooks) are refetched.

typescript
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { registerAdapter } from "msw-devtools-plugin";
import { createRtkQueryAdapter } from "msw-devtools-plugin/adapters/rtk-query";

const pokemonApi = createApi({
  baseQuery: fetchBaseQuery(),
  endpoints: (builder) => ({
    getPokemon: builder.query({ query: (id) => `/api/pokemon/${id}` }),
  }),
});

const store = configureStore({
  reducer: { [pokemonApi.reducerPath]: pokemonApi.reducer },
  middleware: (gDM) => gDM().concat(pokemonApi.middleware),
});

// Pass the store and the RTK Query API instance
registerAdapter(createRtkQueryAdapter(store, pokemonApi));

SWR

Calls the global mutate() to revalidate all SWR keys.

tsx
import { useSWRConfig } from "swr";
import { registerAdapter } from "msw-devtools-plugin";
import { createSwrAdapter } from "msw-devtools-plugin/adapters/swr";

function SetupAdapter() {
  const { mutate } = useSWRConfig();

  useEffect(() => {
    const unregister = registerAdapter(createSwrAdapter(mutate));
    return unregister;
  }, [mutate]);

  return null;
}

Axios & plain fetch

Consider using a server-state library instead. Libraries like TanStack Query, RTK Query, SWR, or Apollo provide built-in caching, deduplication, and automatic refetching — making the integration with msw-devtool seamless and zero-config. The Axios/fetch adapter requires manual useMockRefetch wiring in every component, which adds boilerplate and is harder to maintain.

Axios and fetch() have no query cache. Register the Axios adapter as a marker, then use the useMockRefetch hook in your components to re-run requests when mock config changes:

tsx
import { registerAdapter } from "msw-devtools-plugin";
import { createAxiosAdapter } from "msw-devtools-plugin/adapters/axios";
import { useMockRefetch } from "msw-devtools-plugin";

registerAdapter(createAxiosAdapter());

// In your component:
function UserCard() {
  const { data, refetch } = useMyFetch("/api/users/1");
  useMockRefetch("GET Users", refetch);

  return <div>{data?.name}</div>;
}

Most projects already have MSW handlers for unit tests, E2E, or Storybook. You can pass them directly to registerRestMocks or registerGraphqlMocks — no conversion needed.

Register handlers directly

Pass your existing MSW handlers directly. The devtools auto-derives the operation name, method, and path from each handler:

typescript
import { registerRestMocks, registerGraphqlMocks } from "msw-devtools-plugin";

// Your existing handlers — the same ones you use in tests
import { getUserHandler, getProductsHandler } from "./mocks/handlers";
import { getUserProfileQuery, updateSettingsMutation } from "./mocks/graphql-handlers";

// Register REST handlers
registerRestMocks(
  { handler: getUserHandler, group: "Users" },
  { handler: getProductsHandler, group: "Products" },
);

// Register GraphQL handlers
registerGraphqlMocks(
  { handler: getUserProfileQuery, group: "Users" },
  { handler: updateSettingsMutation, group: "Settings" },
);

Multiple variants for the same endpoint

If you have multiple success scenarios (e.g. full list vs empty list), use the variants array:

typescript
import { registerRestMocks } from "msw-devtools-plugin";
import { http, HttpResponse } from "msw";

registerRestMocks({
  variants: [
    http.get("/api/users", () => HttpResponse.json([{ id: 1, name: "Alice" }])),
    { handler: http.get("/api/users", () => HttpResponse.json([])), label: "Empty list" },
  ],
  group: "Users",
});

Tip: Error scenarios (401, 404, 429, 500, Network Error) are handled by the Error Override UI in the devtools panel — no need to create separate error variants.

The essentials you need to get started, plus advanced exports for custom integrations.

Core

The functions you'll use in every project.

registerRestMocks

void
registerRestMocks(...defs: RestMockDef[]): void

Register one or more REST mocks from MSW HttpHandlers. Operation metadata (method, path, operationName) is auto-derived from the handler.

ParameterTypeDefaultDescription
def.handlerHttpHandlerMSW HttpHandler (shorthand for single variant)
def.variantsHandlerVariantInput<HttpHandler>[]Multiple handler variants for the same endpoint
def.operationNamestringOverride the auto-derived operation name
def.groupstringOptional grouping label

registerGraphqlMocks

void
registerGraphqlMocks(...defs: GraphqlMockDef[]): void

Register one or more GraphQL mocks from MSW GraphQLHandlers. Operation metadata (operationName, operationType) is auto-derived from the handler.

ParameterTypeDefaultDescription
def.handlerGraphQLHandlerMSW GraphQLHandler (shorthand for single variant)
def.variantsHandlerVariantInput<GraphQLHandler>[]Multiple handler variants for the same operation
def.operationNamestringOverride the auto-derived operation name
def.operationType"query" | "mutation"Override the auto-derived operation type
def.groupstringOptional grouping label

createMswDevToolsPlugin

TanStackPlugin
createMswDevToolsPlugin(options?: MswDevToolsPluginOptions): TanStackPlugin

Creates a TanStack DevTools plugin that auto-starts the MSW service worker on mount. No manual worker setup required.

ParameterTypeDefaultDescription
options.defaultOpenbooleantrueWhether the devtools panel starts open
options.namestring'MSW Mocks'Plugin display name

registerAdapter

() => void
registerAdapter(adapter: MswDevToolAdapter): () => void

Register a data-fetching adapter. When mock config changes, the adapter automatically refetches queries. Returns an unregister function for cleanup.

React Hooks

React hooks for integrating with the mock system.

useMockRefetch

void
useMockRefetch(operationName: string, refetch: () => void): void

Listens for mock update events matching the given operation name and calls your refetch callback. Use with Axios or plain fetch.

tsx
function UserCard() {
  const { data, refetch } = useMyFetch("/api/users/1");
  useMockRefetch("GET /api/users/1", refetch);
  return <div>{data?.name}</div>;
}

Advanced

For custom integrations. Most apps won't need these.

startWorker

Promise<SetupWorker>
startWorker(options?: WorkerOptions): Promise<SetupWorker>

Manually start the MSW service worker with custom options. The plugin auto-starts the worker on mount — only use this if you need a custom serviceWorkerUrl or other non-default options. If the worker is already running, the plugin skips auto-start.

ParameterTypeDefaultDescription
options.onUnhandledRequest'bypass' | 'warn' | 'error''bypass'How to handle requests with no matching handler
options.quietbooleantrueSuppress MSW console logging
options.serviceWorkerUrlstring'/mockServiceWorker.js'Custom path to the service worker script
typescript
// Example: app deployed under a sub-path (e.g. GitHub Pages)
import { startWorker } from "msw-devtools-plugin";

void startWorker({
  serviceWorkerUrl: "/my-app/mockServiceWorker.js",
});