Fixing TypeScript Error TS18046: 'Object is of type unknown' in Catch Blocks

beginner🔵 TypeScript2026-07-11| TypeScript 4.0+, Node.js, React, or any project with strict mode or useUnknownInCatchVariables enabled.

Error Message

Object is of type 'unknown'. (TS18046)
#typescript#web-development#error-handling#coding-tips

The Problem

You’re trying to log an error message in a standard try-catch block, but TypeScript stops you cold with a red squiggly line. It looks something like this:

try {
  // risky logic here
} catch (error) {
  console.error(error.message); // Error: Object is of type 'unknown'. (TS18046)
}

This happens because modern TypeScript (starting from version 4.0 and enforced more strictly in 4.4) defaults catch variables to unknown instead of any. It’s a protective measure. In JavaScript, a throw statement can return anything—a string, a number, or even a literal object like { code: 500 }. Since the compiler can't guarantee that the error is an actual Error object, it prevents you from accessing .message to stop your app from crashing at runtime.

How to Fix TS18046

1. The Gold Standard: 'instanceof' Type Guard

The cleanest way to satisfy the compiler is to verify the error's identity. By checking if the object is an instance of the built-in Error class, TypeScript automatically narrows the type, granting you safe access to message and stack.

try {
  // Perform an operation
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
  } else {
    console.error("An unexpected non-error object was thrown", error);
  }
}

This approach handles those rare cases where a library might throw a plain string instead of a proper Error object.

2. The Fast Track: Type Assertion

Sometimes you’re working on a quick prototype or an internal script where you know exactly what’s being thrown. In these cases, you can use a type assertion to skip the ceremony. Just be careful: if the error isn't what you expect, err.message will simply return undefined at runtime without warning.

try {
  // logic
} catch (error) {
  const err = error as Error;
  console.log(err.message);
}

3. Handling Axios and API Errors

API clients like Axios provide their own narrowing tools. If you're fetching data, the error might contain a response object with a status code (like 404 or 500). Use the library’s built-in guard for the best results.

import axios from 'axios';

try {
  await axios.get('/api/users/1');
} catch (error) {
  if (axios.isAxiosError(error)) {
    // Access specific Axios properties safely
    console.error(`Status: ${error.response?.status} - ${error.message}`);
  } else {
    console.error("Native error:", error);
  }
}

4. The Cleanest Way: A Reusable Helper

Writing if (error instanceof Error) in every single catch block gets repetitive fast. I recommend moving this logic into a utility function. This keeps your catch blocks lean and ensures consistent error logging across your entire codebase.

function ensureErrorMessage(error: unknown): string {
  if (error instanceof Error) return error.message;
  if (typeof error === "string") return error;
  return "An unknown error occurred";
}

// Usage:
try {
  throw new Error("Database timeout after 5000ms");
} catch (error) {
  console.error(ensureErrorMessage(error));
}

Why did TypeScript change this?

In the past, catch (error) defaulted to any. This was dangerous. You could call error.something(), and the compiler would let it slide, only for your app to explode with a TypeError in production.

The useUnknownInCatchVariables flag forces you to acknowledge that the catch block is a wild west where anything can happen. It might feel like extra work now, but it prevents the "Cannot read property of undefined" bugs that plague large JavaScript apps.

How to Verify the Fix

  • Check your Editor: The red underline on error.message should vanish as soon as you wrap it in an instanceof check.
  • Run a Build: Execute npx tsc --noEmit in your terminal. If the fix is correct, the TS18046 error will no longer appear in the output.
  • Test Edge Cases: Try manually throwing a string (throw "API Key Missing") to make sure your else logic handles it without crashing.

Pro Tips for Error Management

  • Resist the 'any' temptation: Don't use catch (error: any). It’s a shortcut that bypasses the very safety features you’re using TypeScript for.
  • Keep the context: When narrowing errors, log the original unknown object to services like Sentry. It often contains hidden metadata that helps with debugging.
  • Configuration: If this behavior is breaking a massive legacy project, you can temporarily disable it by setting "useUnknownInCatchVariables": false in your tsconfig.json, though fixing the types is always the better long-term move.

Related Error Notes