Inspector

@xstate/inspect enables you to inspect and manipulate machines while they’re running in your app. Check out the @xstate/inspect package on GitHub.
Currently, @xstate/inspect only works in frontend applications , but we’re working on a version that can inspect machines running in Node.
To see it running right now, here are our CodeSandbox templates:
Quickstart
- Install with npm or yarn:
bashnpm install @xstate/inspect# or yarn add @xstate/inspect
bashnpm install @xstate/inspect# or yarn add @xstate/inspect
- Import @xstate/inspect at the beginning of your project before any other code is called:
tsimport {inspect } from "@xstate/inspect";inspect ({// options// url: 'https://stately.ai/viz?inspect', // (default)iframe : false, // open in new window});
tsimport {inspect } from "@xstate/inspect";inspect ({// options// url: 'https://stately.ai/viz?inspect', // (default)iframe : false, // open in new window});
- Add
{ devTools: true }to any interpreted machines you want to visualize:
tsimport {interpret } from "xstate";import {inspect } from "@xstate/inspect";constservice =interpret (someMachine , {devTools : true,}).start ();
tsimport {interpret } from "xstate";import {inspect } from "@xstate/inspect";constservice =interpret (someMachine , {devTools : true,}).start ();
Options
js// defaultsinspect({iframe: () =>document.querySelector("iframe[data-xstate]"),url: "https://stately.ai/viz?inspect",});// the code above does the same as:inspect();
js// defaultsinspect({iframe: () =>document.querySelector("iframe[data-xstate]"),url: "https://stately.ai/viz?inspect",});// the code above does the same as:inspect();
You can pass several properties to the options object, all of which are optional.
iframe
iframe (function or iframe Element or false) resolves to the iframe element to display the inspector. If set to iframe: false, a popup window is used instead.
⚠️ You may need to “allow popups” in your browser to display the inspector in a popup window, as the browser might block them by default.
By default, the inspector will look for an <iframe data-xstate> element anywhere in the document. If you want to target a custom iframe, specify it eagerly or lazily:
js// eagerinspect({iframe: document.querySelector("iframe.some-xstate-iframe",),});
js// eagerinspect({iframe: document.querySelector("iframe.some-xstate-iframe",),});
js// lazyinspect({iframe: () =>document.querySelector("iframe.some-xstate-iframe"),});
js// lazyinspect({iframe: () =>document.querySelector("iframe.some-xstate-iframe"),});
url
Use the url property (string) to specify the URL of the inspector you want to connect to. By default, the inspector runs on https://stately.ai/viz?inspect.
Disconnecting
inspect returns a function, disconnect, you can use to disconnect the inspector.
tsimport {inspect } from "@xstate/inspect";constinspector =inspect ();inspector ?.disconnect ();
tsimport {inspect } from "@xstate/inspect";constinspector =inspect ();inspector ?.disconnect ();
Implementation
You can implement your own inspector by creating a receiver. A receiver is an actor that receives inspector events from a source, like a parent window or a WebSocket connection:
-
"service.register"ts{type: 'service.register';machine: AnyStateMachine;state: AnyState;id: string;sessionId: string;parent?: string;source?: string;}ts{type: 'service.register';machine: AnyStateMachine;state: AnyState;id: string;sessionId: string;parent?: string;source?: string;} -
"service.stop"ts{type: "service.stop";sessionId: string;}ts{type: "service.stop";sessionId: string;} -
"service.state"ts{type: "service.state";state: AnyState;sessionId: string;}ts{type: "service.state";state: AnyState;sessionId: string;} -
"service.event"ts{type: 'service.event';event: SCXML.Event<any>;sessionId: string};ts{type: 'service.event';event: SCXML.Event<any>;sessionId: string};
To listen to events from an inspected source, create a receiver with the appropriate create*Receiver(...) function; for example:
jsimport { createWindowReceiver } from "@xstate/inspect";const windowReceiver = createWindowReceiver(/* options? */);windowReceiver.subscribe((event) => {// here, you will receive "service.*" eventsconsole.log(event);});
jsimport { createWindowReceiver } from "@xstate/inspect";const windowReceiver = createWindowReceiver(/* options? */);windowReceiver.subscribe((event) => {// here, you will receive "service.*" eventsconsole.log(event);});
You can also send events to the receiver:
js// ...// This will send the event to the inspected servicewindowReceiver.send({type: 'xstate.event',event: JSON.stringify({ type: 'someEvent' }),service: /* session ID of the service this event is sent to */});
js// ...// This will send the event to the inspected servicewindowReceiver.send({type: 'xstate.event',event: JSON.stringify({ type: 'someEvent' }),service: /* session ID of the service this event is sent to */});
The typical inspection workflow is as follows:
- The
inspect(/* ... */)call on the client opens the inspector. For example, in a separate window or creates a WebSocket connection. - The receiver sends an
"xstate.inspecting"event to the client. - The client sends
"service.register"events to the receiver. - An inspector listening to the receiver via
receiver.subscribe(...)registers the machine,event.machine, by itsevent.sessionId. - The machine is rendered visually, and its current state,
event.state, is highlighted - As the service at the source receives events and changes state, it will send the receiver
"service.event"and"service.state"events, respectively. - The inspector can use those events to highlight the current state and keep a log of events sent to that service.
- When the service stops, a
"service.stop"event is sent to the receiver with theevent.sessionIdto identify the stopped service.
How do I run the inspector in a NextJS app?
If you want to run the inspector in a NextJS app, you must ensure that the inspector code only runs on the client rather than the server:
jsif (typeof window !== "undefined") {inspect({/* options */});}
jsif (typeof window !== "undefined") {inspect({/* options */});}