Error Handling
Overview
Section titled “Overview”The ErrorController manages error handling throughout the flow lifecycle. It captures errors with metadata, maintains error history, and exposes errors to views via the protected errorState binding. The ErrorController is automatically instantiated by Player when a flow starts and is accessible through the error property in the controller state.
Key Features
Section titled “Key Features”- Error Capture: Capture errors with type, severity, and custom metadata
- Error History: Maintain a complete history of all captured errors in chronological order
- Protected State: Automatically manages
errorStatein the data model with middleware protection - Hook System: Allows plugins to observe errors and optionally prevent error state navigation
- Cross-Platform: Available on TypeScript/React, iOS, and JVM platforms
Error Types & Severity
Section titled “Error Types & Severity”Player provides standard error types: expression, binding, view, asset, navigation, validation, data, schema, network, plugin. Plugins can define custom types.
Severity Levels:
fatal- Cannot continue, flow must enderror- Standard error, may allow recoverywarning- Non-blocking, logged for telemetry
API Reference
Section titled “API Reference”Capturing Errors
Section titled “Capturing Errors”errorController.captureError( new Error("Failed to load view"), ErrorTypes.VIEW, ErrorSeverity.ERROR, { viewId: "my-view" });The same API is available on Kotlin (ErrorTypes.VIEW, ErrorSeverity.ERROR) and Swift (ErrorTypes.view, .error).
Retrieving Errors
Section titled “Retrieving Errors”// Get the most recent errorconst currentError = errorController.getCurrentError();
// Get complete error historyconst allErrors = errorController.getErrors();Clearing Errors
Section titled “Clearing Errors”// Clear all errors (history + current + data model)errorController.clearErrors();
// Clear only current error (preserve history)errorController.clearCurrentError();Accessing Error State in Views
Section titled “Accessing Error State in Views”When an error is captured, the ErrorController automatically sets errorState in the data model. This makes error information accessible to views using bindings:
{ "id": "error-view", "type": "text", "value": "Error: {{errorState.message}}"}Error State Structure
Section titled “Error State Structure”{ "errorState": { "message": "Failed to load view", "name": "Error", "errorType": "view", "severity": "error", "viewId": "my-view" }}Protected ErrorState
Section titled “Protected ErrorState”The errorState binding in the data model is protected by middleware:
- ✅ Views can read
errorStateusing bindings like{{errorState.message}} - ❌ Only the ErrorController can write to
errorState- views, expressions, and plugins cannot modify it directly
This protection ensures error state integrity and prevents accidental overwrites. Expressions like {{errorState}} = null will be blocked. To clear errors, use clearCurrentError() or clearErrors() methods.
onError Hook
Section titled “onError Hook”The onError hook fires whenever an error is captured, allowing plugins to observe errors and optionally prevent the error from being exposed to views.
Hook Behavior:
- Called in order for each tapped plugin
- Return
trueto bail and prevent the error from being set inerrorState(for custom error handling) - Return
undefinedorfalseto continue - error will be set inerrorStateand trigger navigation iferrorStateproperty is defined - Once
trueis returned, no further plugins are called
Example: Error Logging
Section titled “Example: Error Logging”export class ErrorLoggingPlugin implements Plugin { name = "error-logging";
apply(player) { player.hooks.errorController.tap(this.name, (errorController) => { errorController.hooks.onError.tap(this.name, (playerError) => { // Log to external service logToService({ message: playerError.error.message, type: playerError.errorType, severity: playerError.severity });
// Return undefined to allow error state navigation return undefined; }); }); }}Example: Custom Error Handler
Section titled “Example: Custom Error Handler”errorController.hooks.onError.tap("custom-handler", (error) => { // Handle specific error types with custom logic if (error.errorType === "network" && error.severity === "warning") { console.warn("Network warning:", error.error.message); return true; // Prevent errorState from being set }
// Allow other errors to proceed normally return undefined;});Cross-Platform Hook Examples
Section titled “Cross-Platform Hook Examples”// TypeScripterrorController.hooks.onError.tap("logger", (error) => { console.log(error.error.message); return undefined;});// Kotlinplayer.errorController?.hooks?.onError?.tap { errorInfo -> println("Error: ${errorInfo.message}") null // Return null to continue}// SwifterrorController.hooks.onError.tap(name: "logger") { errorInfo in print("Error: \(errorInfo.message)") return nil // Return nil to continue}Error Navigation Pattern
Section titled “Error Navigation Pattern”Errors can trigger automatic navigation using the errorTransitions map at node or flow level. This provides a dedicated error routing mechanism separate from regular transitions.
errorTransitions Map
Section titled “errorTransitions Map”The errorTransitions property maps error types directly to state names:
{ "errorTransitions": { "binding": "BINDING_ERROR_VIEW", "validation": "VALIDATION_ERROR_VIEW", "*": "GENERIC_ERROR_VIEW" }}The "*" wildcard matches any error type not explicitly defined.
Navigation Fallback Pattern
Section titled “Navigation Fallback Pattern”When an error is captured, the ErrorController uses errorTransition() to navigate following this hierarchical fallback:
- Node-level
errorTransitions- If defined on the current state, navigate to the mapped state for the error type - Flow-level
errorTransitions- If node-level not found or current state doesn’t have a match, use flow-level mapping - No Navigation - If neither level has a match, log a warning and stay on the current state (or reject flow if critical)
Node-Level Error Transitions
Section titled “Node-Level Error Transitions”Define errorTransitions on individual states to handle errors specific to that state:
{ "navigation": { "BEGIN": "FLOW_1", "FLOW_1": { "startState": "VIEW_1", "VIEW_1": { "state_type": "VIEW", "ref": "main-view", "errorTransitions": { "binding": "BINDING_ERROR_VIEW", "validation": "VALIDATION_ERROR_VIEW", "*": "GENERIC_ERROR_VIEW" }, "transitions": { "*": "END_Done" } }, "BINDING_ERROR_VIEW": { "state_type": "VIEW", "ref": "binding-error-view", "transitions": { "*": "END_Error" } }, "VALIDATION_ERROR_VIEW": { "state_type": "VIEW", "ref": "validation-error-view", "transitions": { "*": "END_Error" } }, "GENERIC_ERROR_VIEW": { "state_type": "VIEW", "ref": "generic-error-view", "transitions": { "*": "END_Error" } }, "END_Done": { "state_type": "END", "outcome": "done" }, "END_Error": { "state_type": "END", "outcome": "error" } } }}When a binding error is captured on VIEW_1, Player automatically navigates to BINDING_ERROR_VIEW.
Flow-Level Error Transitions
Section titled “Flow-Level Error Transitions”Define errorTransitions at the flow level as a fallback for states without their own error handling:
{ "navigation": { "BEGIN": "FLOW_1", "FLOW_1": { "startState": "VIEW_1", "errorTransitions": { "binding": "BINDING_ERROR_VIEW", "validation": "VALIDATION_ERROR_VIEW", "*": "GENERIC_ERROR_VIEW" }, "VIEW_1": { "state_type": "VIEW", "ref": "main-view", "transitions": { "*": "VIEW_2" } }, "VIEW_2": { "state_type": "VIEW", "ref": "second-view", "transitions": { "*": "END_Done" } }, "BINDING_ERROR_VIEW": { "state_type": "VIEW", "ref": "binding-error-view", "transitions": { "*": "END_Error" } }, "VALIDATION_ERROR_VIEW": { "state_type": "VIEW", "ref": "validation-error-view", "transitions": { "*": "END_Error" } }, "GENERIC_ERROR_VIEW": { "state_type": "VIEW", "ref": "generic-error-view", "transitions": { "*": "END_Error" } }, "END_Done": { "state_type": "END", "outcome": "done" }, "END_Error": { "state_type": "END", "outcome": "error" } } }}Any error captured on VIEW_1 or VIEW_2 will use the flow-level errorTransitions since they don’t define their own.
Wildcard Support
Section titled “Wildcard Support”The "*" wildcard matches any error type not explicitly mapped:
{ "errorTransitions": { "network": "NETWORK_ERROR_VIEW", "validation": "VALIDATION_ERROR_VIEW", "*": "GENERIC_ERROR_VIEW" }}A binding error would navigate to GENERIC_ERROR_VIEW via the wildcard.
Combining Node and Flow Level
Section titled “Combining Node and Flow Level”Node-level takes precedence, with flow-level as fallback:
{ "navigation": { "BEGIN": "FLOW_1", "FLOW_1": { "startState": "VIEW_1", "errorTransitions": { "*": "GENERIC_ERROR_VIEW" }, "VIEW_1": { "state_type": "VIEW", "ref": "main-view", "errorTransitions": { "validation": "CUSTOM_VALIDATION_ERROR" }, "transitions": { "*": "VIEW_2" } }, "VIEW_2": { "state_type": "VIEW", "ref": "second-view", "transitions": { "*": "END_Done" } }, "CUSTOM_VALIDATION_ERROR": { "state_type": "VIEW", "ref": "custom-validation-error", "transitions": { "*": "END_Error" } }, "GENERIC_ERROR_VIEW": { "state_type": "VIEW", "ref": "generic-error-view", "transitions": { "*": "END_Error" } }, "END_Done": { "state_type": "END", "outcome": "done" }, "END_Error": { "state_type": "END", "outcome": "error" } } }}Behavior:
- On
VIEW_1:validationerror →CUSTOM_VALIDATION_ERROR(node-level) - On
VIEW_1:bindingerror →GENERIC_ERROR_VIEW(flow-level fallback) - On
VIEW_2: any error →GENERIC_ERROR_VIEW(flow-level fallback)
Key Differences from Regular Transitions
Section titled “Key Differences from Regular Transitions”errorTransitions differs from regular transitions:
- Direct Navigation: Maps error types directly to state names (no intermediate transition values)
- Bypasses Hooks: Skips
skipTransitionandbeforeTransitionhooks for immediate error handling - No Expression Resolution: State names are used as-is without expression evaluation
- Two-Level Fallback: Supports both node-level and flow-level with automatic fallback
- Protected Writes: ErrorController writes to
errorStatein the data model using protected middleware