Skip to content
Documentation GitHub
Integration Issues

Tauri 2 Structured Error Serialization Produces [object Object]

Tauri 2 Structured Error Serialization Produces [object Object]

Problem

Tauri 2 command errors appear as [object Object] in the UI instead of human-readable messages. Users see no useful error information when backend commands fail, making it impossible to diagnose issues.

Symptoms:

  • Error dialogs show [object Object] instead of meaningful messages
  • Backend commands appear to succeed silently (no error shown)
  • console.log of caught errors shows { type: "NoWorkspace" } or { type: "Internal", data: { message: "..." } }
  • Operations like Create Page appear to do nothing with no feedback

Investigation

Steps Tried

  1. Checked backend tests - All 218 Rust tests passed, ruling out backend logic bugs
  2. Inspected Tauri command registration - Commands correctly registered in invoke_handler
  3. Reviewed frontend catch blocks - Found the pattern err instanceof Error ? err.message : String(err) used in every component
  4. Tested error serialization - Confirmed Tauri 2 serializes Result::Err as plain JS objects, not Error instances

Root Cause

Tauri 2 uses serde to serialize Rust error types into JavaScript. When a command returns Err(CommandError), the error is serialized as a plain JS object matching the Rust enum’s serde configuration:

#[derive(Debug, Clone, Serialize, Type)]
#[serde(tag = "type", content = "data")]
pub enum CommandError {
NotFound { entity: String, id: String },
Validation { message: String },
Internal { message: String },
NoWorkspace,
}

This produces JS objects like:

  • { type: "NoWorkspace" } (no data field)
  • { type: "Internal", data: { message: "some error" } }
  • { type: "NotFound", data: { entity: "Page", id: "abc" } }

The frontend pattern err instanceof Error returns false for these objects because they are not Error instances. The fallback String(err) calls .toString() which produces "[object Object]" for plain objects.

Solution

1. Create a shared error parser

File: apps/desktop/src-react/lib/errors.ts

export function parseCommandError(err: unknown): string {
if (err instanceof Error) return err.message;
if (typeof err === "string") return err;
if (typeof err === "object" && err !== null) {
const obj = err as Record<string, unknown>;
// CommandError::NoWorkspace
if (obj.type === "NoWorkspace") return "No workspace is currently open";
// CommandError variants with data field
if (obj.data && typeof obj.data === "object") {
const data = obj.data as Record<string, unknown>;
if (typeof data.message === "string") return data.message;
if (typeof data.entity === "string" && typeof data.id === "string")
return `${data.entity} not found: ${data.id}`;
}
try { return JSON.stringify(err); } catch { /* ignore */ }
}
return "An unexpected error occurred";
}

2. Replace all catch blocks

Replace every instance of:

err instanceof Error ? err.message : String(err)

With:

parseCommandError(err)

Implementation Notes

  • The parser must match the Rust enum’s #[serde(tag = "type", content = "data")] layout
  • Import paths differ for components vs hooks vs nested directories
  • Add console.error with the raw error object for diagnostics in critical paths
  • The JSON.stringify fallback handles any unexpected error shapes

Prevention

Best Practices

  • Never use String(err) for Tauri command errors. Always use a structured parser.
  • Always log the raw error object with console.error before parsing, so the original shape is visible in dev tools.
  • Match your parser to your Rust error enum’s serde config. If the enum changes, update the parser.
  • When adding new CommandError variants in Rust, update parseCommandError to handle them.

Warning Signs

  • Any catch block using err instanceof Error ? err.message : String(err) with Tauri invoke calls
  • Error messages showing [object Object] in the UI
  • User-facing errors that say “undefined” or are blank

Search Pattern

Find broken error handling with:

Terminal window
grep -r "instanceof Error ? err.message : String(err)" apps/desktop/src-react/

Secondary Issue: Missing Backend Logs

tracing_subscriber::EnvFilter::from_default_env() defaults to off when RUST_LOG is not set. In dev mode, no backend logs appear without explicit configuration. Fix by falling back to info in debug builds:

let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| {
if cfg!(debug_assertions) {
tracing_subscriber::EnvFilter::new("info")
} else {
tracing_subscriber::EnvFilter::new("warn")
}
});

References

Was this page helpful?