Skip to content
Version: XState v5

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

  1. Install with npm or yarn:
npm install @xstate/inspect
# or yarn add @xstate/inspect
  1. Import @xstate/inspect at the beginning of your project before any other code is called:
import { inspect } from '@xstate/inspect';

inspect({
// options
// url: 'https://stately.ai/viz?inspect', // (default)
iframe: false, // open in new window
});
  1. Add { devTools: true } to any interpreted machines you want to visualize:
import { createMachine } from 'xstate';
const someMachine = createMachine({});

import { interpret } from 'xstate';
import { inspect } from '@xstate/inspect';

const actor = interpret(someMachine, {
devTools: true,
}).start();

Options

// defaults
inspect({
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.

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:

// eager
inspect({
iframe: document.querySelector('iframe.some-xstate-iframe'),
});
// lazy
inspect({
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.

import { inspect } from '@xstate/inspect';

const inspector = 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:

"actor.register"

{
type: 'actor.register';
machine: AnyStateMachine;
state: AnyState;
id: string;
sessionId: string;
parent?: string;
source?: string;
}

"actor.stop"

{
type: 'actor.stop';
sessionId: string;
}

"actor.state"

{
type: 'actor.state';
state: AnyState;
sessionId: string;
}

"actor.event"

{
type: 'actor.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:

import { createWindowReceiver } from '@xstate/inspect';

const windowReceiver = createWindowReceiver(/* options? */);

windowReceiver.subscribe((event) => {
// here, you will receive "actor.*" events
console.log(event);
});

You can also send events to the receiver:

// ...

// This will send the event to the inspected actor
windowReceiver.send({
type: 'xstate.event',
event: JSON.stringify({ type: 'someEvent' }),
actor: /* session ID of the actor this event is sent to */
});

The typical inspection workflow is as follows:

  1. The inspect(/* ... */) call on the client opens the inspector. For example, in a separate window or creates a WebSocket connection.
  2. The receiver sends an "xstate.inspecting" event to the client.
  3. The client sends "actor.register" events to the receiver.
  4. An inspector listening to the receiver via receiver.subscribe(...) registers the machine, event.machine, by its event.sessionId.
  5. The machine is rendered visually, and its current state, event.state, is highlighted
  6. As the actor at the source receives events and changes state, it will send the receiver "actor.event" and "actor.state" events, respectively.
  7. The inspector can use those events to highlight the current state and keep a log of events sent to that actor.
  8. When the actor stops, a "actor.stop" event is sent to the receiver with the event.sessionId to identify the stopped actor.

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:

if (typeof window !== 'undefined') {
inspect({
/* options */
});
}