Documentation Index Fetch the complete documentation index at: https://mintlify.com/statelyai/xstate/llms.txt
Use this file to discover all available pages before exploring further.
What are events?
Events are objects that trigger transitions in state machines. They represent things that happen - user actions, system notifications, timer expirations, or any other occurrence that the machine should respond to.
All events in XState must be objects with a type property.
Event structure
The simplest event:
Events with payload data:
{
type : 'UPDATE_USER' ,
name : 'Alice' ,
email : 'alice@example.com'
}
Sending events
Send events to actors using send():
import { createMachine , createActor } from 'xstate' ;
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
START: 'running'
}
},
running: {
on: {
STOP: 'idle'
}
}
}
});
const actor = createActor ( machine );
actor . start ();
// Send events
actor . send ({ type: 'START' });
actor . send ({ type: 'STOP' });
TypeScript event types
Define event types for type safety:
import { setup } from 'xstate' ;
const machine = setup ({
types: {
events: {} as
| { type: 'SUBMIT' ; data : string }
| { type: 'CANCEL' }
| { type: 'UPDATE' ; field : string ; value : string }
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
SUBMIT: 'processing' ,
CANCEL: 'cancelled'
}
},
processing: {},
cancelled: {}
}
});
Now TypeScript will enforce correct event structure:
// ✓ Valid
actor . send ({ type: 'SUBMIT' , data: 'hello' });
// ✗ Type error - missing 'data' property
actor . send ({ type: 'SUBMIT' });
// ✗ Type error - unknown event type
actor . send ({ type: 'UNKNOWN' });
Accessing event data
Event data is available in actions, guards, and other machine logic:
In actions
import { setup , assign } from 'xstate' ;
const machine = setup ({
types: {
context: {} as { message : string },
events: {} as { type : 'SET_MESSAGE' ; text : string }
}
}). createMachine ({
context: { message: '' },
initial: 'active' ,
states: {
active: {
on: {
SET_MESSAGE: {
actions: [
({ event }) => console . log ( 'Received:' , event . text ),
assign ({
message : ({ event }) => event . text
})
]
}
}
}
}
});
In guards
import { setup } from 'xstate' ;
const machine = setup ({
types: {
events: {} as { type : 'SUBMIT' ; age : number }
},
guards: {
isAdult : ({ event }) => {
return event . type === 'SUBMIT' && event . age >= 18 ;
}
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
SUBMIT: [
{
guard: 'isAdult' ,
target: 'adult'
},
{
target: 'minor'
}
]
}
},
adult: {},
minor: {}
}
});
In invoked actors
import { setup , fromPromise } from 'xstate' ;
const fetchData = fromPromise ( async ({ input } : { input : { id : string } }) => {
const response = await fetch ( `/api/data/ ${ input . id } ` );
return response . json ();
});
const machine = setup ({
types: {
events: {} as { type : 'FETCH' ; id : string }
},
actors: {
fetchData
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
invoke: {
src: 'fetchData' ,
input : ({ event }) => ({ id: event . id }),
onDone: 'success' ,
onError: 'error'
}
},
success: {},
error: {}
}
});
Event patterns
Multiple transitions for same event
states : {
idle : {
on : {
CLICK : [
{ guard: 'isDoubleClick' , target: 'selected' },
{ target: 'highlighted' }
]
}
}
}
Wildcard events
Handle any event:
states : {
active : {
on : {
SPECIFIC_EVENT : 'nextState' ,
'*' : {
actions : ({ event }) => console . log ( 'Unhandled event:' , event . type )
}
}
}
}
Event namespacing
Organize related events:
type Events =
| { type : 'user.login' ; credentials : { email : string ; password : string } }
| { type : 'user.logout' }
| { type : 'user.update' ; data : UserData }
| { type : 'data.fetch' ; endpoint : string }
| { type : 'data.save' ; payload : any };
Built-in events
Done events
Automatically sent when actors complete:
import { setup , fromPromise } from 'xstate' ;
const fetchUser = fromPromise ( async () => {
const response = await fetch ( '/api/user' );
return response . json ();
});
const machine = setup ({
actors: { fetchUser }
}). createMachine ({
initial: 'loading' ,
states: {
loading: {
invoke: {
src: 'fetchUser' ,
onDone: {
target: 'success' ,
actions : ({ event }) => {
console . log ( 'User data:' , event . output );
}
}
}
},
success: {}
}
});
Error events
Automatically sent when actors fail:
const machine = setup ({
actors: { fetchUser }
}). createMachine ({
initial: 'loading' ,
states: {
loading: {
invoke: {
src: 'fetchUser' ,
onError: {
target: 'error' ,
actions : ({ event }) => {
console . error ( 'Failed:' , event . error );
}
}
}
},
error: {}
}
});
Raising events
Raise events internally without external send:
import { setup , raise } from 'xstate' ;
const machine = setup ({
types: {
events: {} as
| { type: 'BUTTON_CLICK' }
| { type: 'INTERNAL_PROCESS' }
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
BUTTON_CLICK: {
actions: raise ({ type: 'INTERNAL_PROCESS' })
},
INTERNAL_PROCESS: 'processing'
}
},
processing: {}
}
});
Use raise for internal coordination between parts of your machine.
Emitting events
Emit events to parent machines or external observers:
import { setup , emit } from 'xstate' ;
const childMachine = setup ({
types: {
events: {} as { type : 'DO_WORK' },
emitted: {} as { type : 'WORK_DONE' ; result : string }
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
DO_WORK: {
actions: emit ({
type: 'WORK_DONE' ,
result: 'completed'
})
}
}
}
}
});
Delayed events
Schedule events to be sent after a delay:
import { setup , sendTo } from 'xstate' ;
const machine = setup ({}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
START: {
target: 'waiting' ,
actions: sendTo (
({ self }) => self ,
{ type: 'TIMEOUT' },
{ delay: 5000 }
)
}
}
},
waiting: {
on: {
TIMEOUT: 'done'
}
},
done: {}
}
});
Or use after for cleaner syntax:
const machine = createMachine ({
initial: 'waiting' ,
states: {
waiting: {
after: {
5000 : 'timeout'
}
},
timeout: {}
}
});
Event assertions
Assert event types for type narrowing:
import { assertEvent } from 'xstate' ;
const machine = setup ({
types: {
events: {} as
| { type: 'SUBMIT' ; data : string }
| { type: 'CANCEL' }
},
actions: {
handleSubmit : ({ event }) => {
assertEvent ( event , 'SUBMIT' );
// TypeScript now knows event has 'data' property
console . log ( event . data );
}
}
}). createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
SUBMIT: {
actions: 'handleSubmit'
}
}
}
}
});
Best practices
Use UPPER_CASE for event types to distinguish them from other strings.
Keep event names descriptive and action-oriented: FORM_SUBMITTED, DATA_LOADED, USER_LOGGED_OUT.
Namespace related events: user.login, user.logout, user.update.
Include all relevant data in the event payload rather than relying on external state.
Don’t mutate event objects. They should be treated as immutable.
Next steps
Transitions Learn how events trigger transitions
Actions Respond to events with actions
Raise action Raise internal events
Emit action Emit events to observers