Skip to content

XState v5 Migration

vue-state-machine is API-compatible with a useful subset of XState v5. Migrating a simple machine typically takes minutes.

API mapping

XState v5vue-state-machineNotes
createMachine(config)defineMachine(config)Config structure is identical
useMachine(machine) from @xstate/vueuseMachine(config)Same composable shape
send(event)send(event)Identical
matches(state)matches(state)Identical
context in configcontext in configIdentical
on handlerson handlersIdentical
entry / exitentry / exitIdentical
type: 'final'type: 'final'Identical
guard functionguard functionSame signature
assign(updater)Return Partial<context> from actionNo wrapper needed
snapshot / restoresnapshot / restoreIdentical concept
invoke / servicesNot supportedMove async work into actions
spawn / actor modelNot supportedIntentional scope limit
Hierarchical statesNot supportedFlat + parallel only

Step-by-step migration

1. Replace the import and factory:

ts
// Before (XState v5)
import { createMachine } from 'xstate'
const machine = createMachine({ ... })

// After
import { defineMachine } from 'vue-state-machine'
const machine = defineMachine({ ... })

2. Replace assign() with plain return values:

ts
// Before
import { assign } from 'xstate'
const increment = assign({ count: (ctx) => ctx.count + 1 })

// After — just return a partial context object
const increment = (ctx: { count: number }) => ({ count: ctx.count + 1 })

3. Replace the Vue composable import:

ts
// Before
import { useMachine } from '@xstate/vue'

// After
import { useMachine } from 'vue-state-machine'

4. Move async logic from invoke into actions:

ts
// Before (XState v5 invoke)
loading: {
  invoke: {
    src: (ctx, event) => fetch('/api/user'),
    onDone:  { target: 'success', actions: assign({ user: (_, e) => e.data }) },
    onError: { target: 'error' },
  }
}

// After — fire-and-forget inside the component or inside entry action
loading: {
  entry: [async (ctx, event) => {
    try {
      const user = await fetch('/api/user').then(r => r.json())
      return { user }       // merged into context; then send SUCCESS externally
    } catch {
      return { error: 'Failed' }
    }
  }]
}