jsguides

Error

Error is a built-in object that represents runtime errors. It is the base constructor for all JavaScript errors, including built-in error types like TypeError, RangeError, SyntaxError, and ReferenceError.

Creating error instances

Create an Error using the new keyword with the Error constructor:

const error = new Error("Something went wrong");
// Error: Something went wrong

// You can also call it without new (works the same way)
const error2 = Error("Another error");
// Error: Another error

The simplest form takes just a message string. For richer error reporting, the constructor also accepts an options object. The cause property preserves the original error when you wrap one failure in another, which is essential for debugging nested failures:

const error = new Error("Failed to load", { cause: "NETWORK_ERROR" });
// Error: Failed to load
// error.cause === "NETWORK_ERROR"

Creating an error is only the first step. Each instance carries a handful of standard properties that are useful for logging and debugging. The message and name fields are the most commonly accessed, while stack provides the call chain and cause chains together related errors:

Error Properties

Each Error instance has useful properties:

const error = new Error("Test error");

error.message   // "Test error"
error.name      // "Error"
error.stack     // Full stack trace (non-standard but widely supported)
error.cause     // The underlying cause (ES2022+)

These properties give you the basic shape of every error. JavaScript also ships with several built-in subclasses of Error that help distinguish different categories of failure at runtime. Each one inherits the same properties but signals a distinct kind of problem:

Built-in error subtypes

JavaScript provides several built-in error constructors that inherit from Error:

TypeError

Thrown when a value is not of the expected type:

const fn = "hello";
fn(); // TypeError: fn is not a function

TypeError is the most common subtype you will encounter — it fires whenever the runtime expects one type but gets another. A different category of problem is when a value is technically valid in type but outside the allowed range, which is where RangeError applies:

RangeError

Thrown when a value is outside an allowed range:

const arr = new Array(-1); // RangeError: Invalid array length

RangeError covers bounds violations. In contrast, SyntaxError fires before code even runs — it is raised during parsing, and there is no way to recover inside the same script. Catching it is only practical when parsing dynamic code:

SyntaxError

Thrown when parsing invalid JavaScript syntax:

eval("const x ="); // SyntaxError: Unexpected token

SyntaxError is about bad language syntax that prevents the parser from running your code at all. ReferenceError is different — it fires at runtime when you reference a name that has not been declared in the current scope. Catching this error is common during development when variable names are misspelled:

ReferenceError

Thrown when accessing an undefined variable:

console.log(undefinedVar); // ReferenceError: undefinedVar is not defined

ReferenceError is common during development. URIError is less frequent in application code but worth knowing about — it protects you from malformed URI operations:

URIError

Thrown when URI handling functions are used incorrectly:

decodeURIComponent("%"); // URIError: URI malformed

Throwing Errors

Use throw to raise errors:

function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}

try {
  divide(10, 0);
} catch (e) {
  console.log(e.message); // "Cannot divide by zero"
  console.log(e instanceof Error); // true
}

The basic Error constructor is fine for simple cases, but when you need domain-specific error handling, extending the base class lets you create typed errors with custom properties that catch blocks can distinguish:

Extending Error

Create custom error types by extending Error:

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

const err = new ValidationError("Invalid email", "email");
console.log(err instanceof Error);     // true
console.log(err instanceof ValidationError); // true

Custom error types make intent clear and allow catch blocks to respond to specific failures. The following patterns show how to handle errors in practice — catching only what you should, re-throwing with context, and checking error types without always catching:

Error handling best practices

Always catch specific errors

try {
  riskyOperation();
} catch (e) {
  if (e instanceof TypeError) {
    // Handle type error specifically
  } else if (e instanceof RangeError) {
    // Handle range error
  } else {
    // Handle unknown errors
    throw e;
  }
}

The instanceof check lets you handle specific errors while re-throwing unknowns. When you do re-throw, wrapping the original error with cause preserves the full context. This makes debugging much easier than a flat error message alone, especially in async code where stack traces are easy to lose:

Re-throw with Context

try {
  await fetchData();
} catch (e) {
  throw new Error("Failed to fetch data", { cause: e });
}

Re-throwing with cause is the modern approach, but you do not always need to catch an error to check its type. Sometimes you just want to inspect a value to see if it is error-shaped before deciding how to handle it:

Check error types without catching

function isErrorLike(value) {
  return value instanceof Error ||
         (value && typeof value.message === "string" && typeof value.name === "string");
}

Checking error types without catching is useful for validation at function boundaries. The cause property introduced in ES2022 takes error wrapping to the next level — it lets you preserve the entire failure chain, not just the message:

Error.cause for chaining errors

ES2022 added the cause option to the Error constructor. It lets you attach the original error when re-throwing, preserving the full chain of what went wrong:

async function loadUser(id) {
  try {
    return await db.query(`SELECT * FROM users WHERE id = ${id}`);
  } catch (dbError) {
    throw new Error(`Failed to load user ${id}`, { cause: dbError });
  }
}

try {
  await loadUser(42);
} catch (e) {
  console.log(e.message);       // "Failed to load user 42"
  console.log(e.cause.message); // original DB error message
}

The cause property makes error chaining explicit and machine-readable. This is a big improvement over string concatenation. The base Error constructor itself is the foundation all of this builds on — it provides the common shape that every JavaScript error type shares:

Why the base Error type matters

The base Error constructor is the common shape that all JavaScript error types build on. Using it directly is useful when the problem does not need a more specific subclass, or when you want to wrap lower-level failures in a simple message. It gives you a standard message, a familiar name, and a place to attach a cause if you need to preserve the original failure.

The stack property

The stack property is not part of the ECMAScript standard but is implemented by all major JavaScript engines. It contains a multiline string with the error message and a call stack trace from where the error was created:

function inner() { throw new Error("oops"); }
function outer() { inner(); }

try {
  outer();
} catch (e) {
  console.log(e.stack);
  // Error: oops
  //   at inner (file.js:1:24)
  //   at outer (file.js:2:16)
  //   at ...
}

Stack traces are invaluable for debugging but should not be parsed programmatically — the format varies by engine. Log them for human consumption; check message and name for programmatic branching.

See Also

  • Symbol — Create unique identifiers
  • console — Log errors and diagnostics
  • BigInt — Work with large integers