Skip to content
Version: XState v5

TypeScript

XState v5 and its related libraries are written in TypeScript, and utilize complex types to provide the best type safety and inference possible for you.

Follow these guidelines to ensure that your TypeScript project is ready to use XState v5:

Use the latest version of TypeScript

Use the latest version of TypeScript; version 5.0 or greater is required.

   npm install typescript@latest --save-dev

Set up your tsconfig.json file

Set strictNullChecks to true in your tsconfig.json file. This will ensure that our types work correctly and help catch errors in your code. (Strongly recommended).

// tsconfig.json
{
"compilerOptions": {
// ...
"strictNullChecks": true
// or set `strict` to true, which includes `strictNullChecks`
// "strict": true
}
}

Set skipLibCheck to true in your tsconfig.json file. (Recommended).

// tsconfig.json
{
"compilerOptions": {
// ...
"skipLibCheck": true
}
}

Specifying types

The recommended way to strongly type your machine is to use the setup(...) function:

import { setup } from 'xstate';

const feedbackMachine = setup({
types: {} as {
context: { feedback: string };
events: { type: 'feedback.good' } | { type: 'feedback.bad' };
},
actions: {
logTelemetry: () => {
// TODO: implement
}
}
}).createMachine({
// ...
});

You can also specify TypeScript types inside the machine config using the .types property:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
types: {} as {
context: { feedback: string };
events: { type: 'feedback.good' } | { type: 'feedback.bad' };
actions: { type: 'logTelemetry' };
},
});

These types will be inferred throughout the machine config and in the created machine and actor so that methods such as machine.transition(...) and actor.send(...) will be type-safe.

Dynamic parameters

It is recommended to use dynamic parameters in actions and guards as they allow you to make reusable functions that are not closely tied to the machine, and are strongly-typed.

import { setup } from 'xstate';

const feedbackMachine = setup({
types: {
context: {} as {
user: { name: string };
}
},
actions: {
greet: (_, params: { name: string }) => {
console.log(`Hello, ${params.name}!`);
}
}
}).createMachine({
context: {
user: {
name: 'David'
}
},
// ...
entry: {
type: 'greet',
params: ({ context }) => ({
name: context.user.name
})
}
});

Asserting events

If using dynamic parameters is infeasible and you must use the event in an action or guard implementation, you can assert the event type using the assertEvent(...) helper function:

import { createMachine, assertEvent } from 'xstate';

const machine = createMachine({
types: {
events: {} as
| { type: 'greet'; message: string }
| { type: 'log'; message: string }
| { type: 'doSomethingElse' }
},
// ...
states: {
someState: {
entry: ({ event }) => {
// In the entry action, it is currently not possible to know
// which event this action was called with.

// Calling `assertEvent` will throw if
// the event is not the expected type.
assertEvent(event, 'greet');

// Now we know the event is a `greet` event,
// and we can access its `message` property.
console.log(event.message.toUpperCase());
},
// ...
exit: ({ event }) => {
// You can also assert multiple possible event types.
assertEvent(event, ['greet', 'log']);

// Now we know the event is a `greet` or `log` event,
// and we can access its `message` property.
console.log(event.message.toUpperCase());
}
}
}
})

Typegen

Typegen does not yet support XState v5. However, with the setup(...) function and/or the .types property explained above, you can provide strong typing for most (if not all) of your machine.

If you were previously using typegen to narrow down events used in actions or guards, you can use the assertEvent(...) helper function to narrow down the event type.