Overview
TaggedError is a factory function that creates custom error classes with a _tag discriminator property. This enables exhaustive pattern matching, type-safe error unions, and improved error handling with full type inference.
Tagged errors integrate seamlessly with Result types and support cause chaining, JSON serialization, and runtime type guards.
Factory Function
TaggedError < Tag extends string > (
tag : Tag
): < Props extends Record < string , unknown > = {}>() => TaggedErrorClass < Tag , Props >
Parameters
The discriminator tag for this error class. Used for pattern matching and type narrowing.
Returns
Returns a function that creates a class constructor. Call it immediately with type parameters to get your error class:
class NotFoundError extends TaggedError ( "NotFoundError" )<{
id : string ;
message : string ;
}>() {}
Instance Properties
The discriminator tag identifying this error type. Used for exhaustive pattern matching.
Optional error message. If included in Props, will be passed to Error constructor.
Optional error cause. If included in Props, will be chained in the stack trace.
Error name, set to the tag value.
Stack trace. When cause is an Error, its stack is appended with “Caused by:” prefix.
Static Methods
is()
Type guard for checking error instances.
static is ( value : unknown ): value is TaggedErrorInstance < Tag , Props >
Global Type Guard
TaggedError . is ( value : unknown ): value is AnyTaggedError
Checks if a value is ANY TaggedError instance:
if ( TaggedError . is ( error )) {
console . log ( error . _tag ); // string - any tag
}
Class-Specific Type Guard
Each error class has its own is() method:
if ( NotFoundError . is ( error )) {
console . log ( error . id ); // Narrowed to NotFoundError
}
Instance Methods
toJSON()
Serializes the error to a plain object.
Returns: Object containing all properties including _tag, name, message, cause, and stack.
Basic Usage
Creating Tagged Errors
class NotFoundError extends TaggedError ( "NotFoundError" )<{
id : string ;
message : string ;
}>() {}
class ValidationError extends TaggedError ( "ValidationError" )<{
field : string ;
message : string ;
}>() {}
type AppError = NotFoundError | ValidationError ;
Creating Instances
const notFound = new NotFoundError ({
id: "user-123" ,
message: "User not found"
});
console . log ( notFound . _tag ); // "NotFoundError"
console . log ( notFound . id ); // "user-123"
console . log ( notFound . message ); // "User not found"
With Result Types
function findUser ( id : string ) : Result < User , NotFoundError > {
const user = db . users . get ( id );
if ( ! user ) {
return Result . err ( new NotFoundError ({ id , message: `User ${ id } not found` }));
}
return Result . ok ( user );
}
Pattern Matching
Exhaustive Matching
Use matchError for type-safe exhaustive pattern matching:
import { matchError } from "better-result" ;
function handleError ( error : AppError ) : string {
return matchError ( error , {
NotFoundError : ( e ) => `Missing: ${ e . id } ` ,
ValidationError : ( e ) => `Invalid field: ${ e . field } `
});
}
Data-First
Data-Last (Pipeable)
matchError ( error , {
NotFoundError : ( e ) => `Missing: ${ e . id } ` ,
ValidationError : ( e ) => `Invalid: ${ e . field } `
});
Partial Matching with Fallback
Use matchErrorPartial when you only want to handle specific error types:
import { matchErrorPartial } from "better-result" ;
function handleError ( error : AppError ) : string {
return matchErrorPartial (
error ,
{
NotFoundError : ( e ) => `Missing: ${ e . id } `
// Only handle NotFoundError
},
( e ) => {
// Fallback receives ValidationError only
// Type is narrowed to exclude handled errors
return `Other error: ${ e . _tag } ` ;
}
);
}
The fallback parameter’s type is automatically narrowed to exclude handled error types.
Advanced Features
Error Cause Chaining
Automatically chains causes in stack traces:
class DatabaseError extends TaggedError ( "DatabaseError" )<{
message : string ;
cause : unknown ;
}>() {}
try {
await db . query ( sql );
} catch ( cause ) {
throw new DatabaseError ({
message: "Failed to fetch user" ,
cause
});
}
Stack trace will include:
DatabaseError: Failed to fetch user
at ...
Caused by:
Error: Connection timeout
at ...
No-Props Errors
Create errors without additional properties:
class TimeoutError extends TaggedError ( "TimeoutError" )<{}>() {}
const timeout = new TimeoutError (); // No args required
Custom Constructors
Override the constructor for custom initialization logic:
class ParseError extends TaggedError ( "ParseError" )<{
message : string ;
input : string ;
line : number ;
}>() {
constructor ( args : { input : string ; line : number }) {
const message = `Parse error at line ${ args . line } ` ;
super ({ ... args , message });
}
}
// Usage - message derived automatically
const error = new ParseError ({ input: "..." , line: 42 });
console . log ( error . message ); // "Parse error at line 42"
JSON Serialization
const error = new NotFoundError ({ id: "123" , message: "Not found" });
const json = error . toJSON ();
// {
// _tag: "NotFoundError",
// name: "NotFoundError",
// message: "Not found",
// id: "123",
// cause: undefined,
// stack: "..."
// }
console . log ( JSON . stringify ( error ));
Type Guards
Class-Specific Guards
function handleError ( error : unknown ) {
if ( NotFoundError . is ( error )) {
console . log ( error . id ); // Type is NotFoundError
console . log ( error . _tag ); // "NotFoundError"
}
}
Generic TaggedError Guard
import { isTaggedError } from "better-result" ;
function logError ( error : unknown ) {
if ( isTaggedError ( error )) {
console . log ( `Error: ${ error . _tag } ` );
}
}
Best Practices
Always include a message property in your error Props for better debugging.
Use descriptive tags : Make tags match the class name for clarity
class NotFoundError extends TaggedError ( "NotFoundError" ) // ✅ Good
class NotFoundError extends TaggedError ( "NFE" ) // ❌ Unclear
Type error unions : Create union types for domain-specific errors
type AuthError = InvalidCredentials | TokenExpired | UserNotFound ;
type ApiError = NetworkError | RateLimited | ServerError ;
Exhaustive matching : Use matchError instead of if/else chains
// ✅ Exhaustive - TypeScript enforces all cases
matchError ( error , {
NotFoundError: ... ,
ValidationError: ...
});
// ❌ Non-exhaustive - missing cases not caught
if ( error . _tag === "NotFoundError" ) { ... }
Include metadata : Add relevant context to error properties
class ValidationError extends TaggedError ( "ValidationError" )<{
field : string ;
value : unknown ;
constraint : string ;
message : string ;
}>() {}
See Also