Open-source project
statelyai/xstate avatar
statelyai/xstate

XState: state machines and actors for JavaScript logic that outgrows a store

State machines, statecharts, and actors for complex logic

30,154 stars1,391 forksTypeScriptMIT

At a glance

What is it?
XState models application and workflow logic as state machines, statecharts and actors in TypeScript. This review covers how the packages are split, how to install and run a first machine, and when a plain store is the better choice.
Who is it for?
Adopt XState when your logic has named states, guarded transitions and cancellation, and you want those modelled explicitly rather than inferred from booleans. Do not adopt it for a flat key-value store; the README points that case at @xstate/store instead.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem XState solves: logic that has states, not just values

Most state libraries store values. XState stores behaviour. The README describes it as "a state management and orchestration solution for JavaScript and TypeScript apps" built on event-driven programming, state machines, statecharts and the actor model, and it is aimed at frontend and backend logic alike. The distinction matters when a screen or a workflow has a fixed set of situations (idle, loading, retrying, done) and only some transitions between them are legal. With a store you encode that as several booleans and hope they never contradict each other. With a machine the illegal combinations simply do not exist.

The repository ships more than one answer. The README splits the decision in two: @xstate/store for "simple event-based state management" that is under 1kb and similar in spirit to Redux or Zustand, and the xstate package for "state machines, statecharts, actors, effects, and orchestration for complex app logic". The README is explicit that they work together but neither requires the other. That framing is honest about a real failure mode of statechart libraries: teams reach for the full formalism when a reducer would have done.

The audience is narrower than "everyone using React". It is engineers whose logic includes retries, cancellation, parallel regions, or long-running processes on a server. The examples directory reflects that range, from examples/toggle and examples/counter up to examples/express-workflow, examples/mongodb-persisted-state and examples/workflow-accumulate-room-readings.

How a machine becomes a running actor

The mechanism is a two-step split between definition and execution, and the README's quick start shows it plainly. createMachine returns a plain description of states, transitions and context. createActor turns that description into something live, which the README compares to a store. The actor is what you subscribe to and what you send events to; the machine is inert data you can test, reuse or export.

Context is the machine's extended state, and it only changes through assign. In the toggle example, the active state declares entry: assign({ count: ({ context }) => context.count + 1 }), so the counter increments as a side effect of entering a state rather than from a separate action call. That is the design point: transitions are the only way anything changes, and the transition table is inspectable before you run it.

The actor model is the other half. Actors are independent units that communicate by messages, and the package topics list actor-model, orchestration and background-jobs alongside scxml, the W3C specification the README cites as inspiration. For backend use this is the interesting part: an actor can own a workflow, receive events from a queue or an HTTP handler, and keep its own state without a shared mutable object. The README excerpt does not document persistence or snapshot rehydration, so check the docs at stately.ai/docs before designing around durable actors. The examples/mongodb-persisted-state and examples/persisted-donut-maker directories suggest the pattern exists, but the README itself is silent on the API.

Installing XState and running a first machine

The README's super quick start is a single npm install for the core package. The README states the library has zero dependencies, so nothing else is pulled in beside it.

bash
npm install xstate

The machine below is copied from the README. It declares an id, an initial state, a context object holding count, and two states that toggle into each other. The active state increments count on entry through assign.

ts
import { createMachine, createActor, assign } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  context: {
    count: 0
  },
  states: {
    inactive: {
      on: {
        TOGGLE: { target: 'active' }
      }
    },
    active: {
      entry: assign({ count: ({ context }) => context.count + 1 }),
      on: {
        TOGGLE: { target: 'inactive' }
      }
    }
  }
});

Then create an actor from the machine, subscribe to it, start it, and send events. The README shows the expected console output inline as comments: the first log prints 'inactive' with count 0, the first TOGGLE prints 'active' with count 1, and the second TOGGLE returns to 'inactive' while count stays at 1.

ts
const toggleActor = createActor(toggleMachine);
toggleActor.subscribe((state) => console.log(state.value, state.context));
toggleActor.start();
// => logs 'inactive', { count: 0 }

toggleActor.send({ type: 'TOGGLE' });
// => logs 'active', { count: 1 }

toggleActor.send({ type: 'TOGGLE' });
// => logs 'inactive', { count: 1 }

If you only need a store, the README gives a separate package with a different shape: a context object plus named event handlers that return the next context. The donut example below is from the README.

bash
npm install @xstate/store
ts
import { createStore } from '@xstate/store';

const donutStore = createStore({
  context: {
    donuts: 0,
    favoriteFlavor: 'chocolate'
  },
  on: {
    addDonut: (context) => ({ ...context, donuts: context.donuts + 1 }),
    changeFlavor: (context, event: { flavor: string }) => ({
      ...context,
      favoriteFlavor: event.flavor
    }),
    eatAllDonuts: (context) => ({ ...context, donuts: 0 })
  }
});

Subscribing and sending works the same way as the actor above: donutStore.subscribe receives a snapshot, and donutStore.send({ type: 'addDonut' }) produces { donuts: 1, favoriteFlavor: 'chocolate' }. The README notes that @xstate/store has React and Solid bindings and selectors documented in its own package README under packages/xstate-store. The repository also lists templates for vanilla TypeScript, React, Vue and Svelte, each pointing at a feedbackMachine.ts file, which is a faster starting point than assembling the pieces by hand.

Where XState is the wrong tool

The clearest limitation is stated by the project itself: not every app needs statecharts. The README puts @xstate/store forward for simple event-based state management and describes it as similar to Redux or Zustand with less boilerplate. If your state is a handful of independent fields with no illegal combinations, a machine adds a vocabulary (states, events, guards, actors) that buys you nothing and costs review time for every new contributor.

The second limitation is version churn. The releases listed for this repository are [email protected] and @xstate/[email protected], both from 2026-09-03, while the README's templates and quick start examples are labelled XState v5. An alpha line means the API you read about in a blog post may not match the package you install. The repository carries a migration.md at the top level, which is the file to read before moving an existing codebase across a major version.

The third is that the README excerpt does not document persistence, snapshot restore or versioning of stored state. For a frontend toggle that is irrelevant. For a backend workflow that must survive a process restart, it is the first question, and the README does not answer it. Treat the documentation site as the source for that, not the README.

Finally, the visual tooling is a separate product. The README links Stately Studio for visually creating and editing machines, exporting to XState v5, path testing and documentation autogeneration, plus a VS Code extension. None of that is in the npm package. If visual editing is the reason you are interested, you are evaluating a hosted product, not this repository.

XState compared with Redux, Zustand and workflow engines

Against Redux and Zustand the difference is what the model can express. A Redux reducer is a function from state and action to state; nothing prevents an action from being dispatched in a situation where it is meaningless, and nothing tells a reader which situations exist. An XState machine declares the situations as first-class values and attaches transitions to specific states, so an event that has no handler in the current state is simply ignored rather than silently corrupting state. The README makes the same point from the other direction, calling @xstate/store similar in spirit to Redux or Zustand, which positions the full xstate package as the step up when the reducer stops being readable.

Against workflow engines such as Temporal the split is deployment and scope. XState is a library with zero dependencies that runs inside your JavaScript process, in a browser or on a server; the README frames it as useful for frontend and backend application logic. A workflow engine is infrastructure you run alongside your application, with its own service and persistence layer. If your workflow must survive a machine failure and be resumable by a different process, a library that lives in your process is the wrong shape unless you build the persistence yourself. If your workflow is a checkout flow, a form wizard or an in-process job orchestrator, adding a separate cluster is the larger cost.

Against hand-rolled state it is a matter of testing. A machine is data, so the transition table can be asserted directly without rendering anything, and the README's examples directory includes test-friendly shapes such as examples/7guis-flight-booker-react and examples/7guis-1-counter-vue. That is the practical argument for the formalism: the tests describe the statechart, not the UI that happens to render it today.

Maintenance, licensing and upgrade cost

The repository is not archived and the last push was on 2026-09-10, so it is being worked on. That is a fact about the repository, not a promise about any particular package's stability, and the presence of 6.0.0 alpha releases alongside v5-labelled documentation is the signal to weigh.

Licensing is MIT, stated in the repository package.json and in the repository metadata. That permits commercial use and modification. It is worth noting that the visual editor, Stately Studio, and the VS Code extension are separate offerings from the same organisation; the MIT grant covers the code in this repository, not those services. Nothing here is legal advice, and teams with unusual distribution requirements should read the LICENSE file at the repository root.

Upgrade cost is concentrated in the major version boundary. The repository keeps a migration.md file at the top level for that purpose, and the changeset tooling under .changeset/ plus the release script "changeset publish" indicate versioned, changelogged releases rather than untracked pushes. For a team on v5, the practical sequence is to read migration.md, check whether the packages you depend on have moved to the 6.x line, and only then touch code. The monorepo is a pnpm workspace with packages/ and scripts/ as the workspace globs, so a contributor working on the library itself needs pnpm, not npm, to run the build and test scripts.

Editorial conclusion

Adopt XState when your logic has named states, guarded transitions and cancellation, and you want those modelled explicitly rather than inferred from booleans. Do not adopt it for a flat key-value store; the README points that case at @xstate/store instead. Verify first which version line you are installing, since the releases listed are [email protected] and @xstate/[email protected] while the templates and quick start examples are labelled XState v5.

Frequently asked questions

What is XState used for?

The README describes it as a state management and orchestration solution for JavaScript and TypeScript apps, useful for frontend and backend application logic, built on event-driven programming, state machines, statecharts and the actor model.

What are actors in XState?

An actor is a running instance of machine logic. The README's quick start creates one with createActor(toggleMachine), subscribes to it, starts it, and sends events to it, comparing it to a store.

What are the key differences between React XState and Redux?

The README positions @xstate/store as similar in spirit to Redux or Zustand but with less boilerplate, and the xstate package as the option for state machines, statecharts, actors and orchestration. A machine declares which states exist and which transitions are legal; a Redux reducer does not.

What are the differences between XState and Stately?

XState is the MIT-licensed library in this repository. Stately Studio is a separate visual product linked from the README for creating, editing and collaborating on state machines, exporting to XState v5, path testing and documentation autogeneration.

Is XState open source?

Yes. The repository package.json and the repository metadata both state the license as MIT, and the code lives at github.com/statelyai/xstate.

What is XState?

XState is a zero-dependency state management and orchestration library for JavaScript and TypeScript that models logic as state machines, statecharts and actors, inspired by the SCXML specification.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. statelyai/xstate on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/statelyai-xstate.svg)](https://hysenlabs.com/projects/statelyai-xstate)