# How OpenSEO's Error Handling System Classifies and Reports Issues

> Discover how OpenSEO's error handling system classifies issues into validation, application-defined, and runtime errors, reporting them to PostHog with sanitized client messages.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-07-31

---

**OpenSEO uses a layered middleware approach that classifies errors into validation, application-defined, and unexpected runtime categories, then selectively reports them to PostHog analytics while sanitizing client-facing messages.**

The `every-app/open-seo` repository implements a robust error handling system that ensures type-safe error classification across server functions. This system distinguishes between schema validation failures, custom application errors, and unexpected runtime exceptions to determine which issues require analytics tracking and which details are safe to expose to clients.

## Error Classification Architecture

OpenSEO categorizes all server-side errors into three distinct buckets. Each category follows a specific detection path and reporting rule set defined in the core middleware and error library files.

### Validation Errors

Validation errors originate from TanStack's schema validators. When input validation fails, the middleware detects these through the `isValidatorError` helper in [`src/middleware/errorHandling.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts), which checks for JSON-encoded schema issues within the error object. These errors receive the code `VALIDATION_ERROR` and are converted into standardized `AppError` instances for consistent handling.

### Application-Defined Errors

Custom business logic errors use the `AppError` class defined in [`src/server/lib/errors.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/errors.ts). The `asAppError` function attempts to map caught errors to known error codes by matching error messages against the `ErrorCode` enum defined in [`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts). Common application codes include `UNAUTHENTICATED`, `PAYMENT_REQUIRED`, and `BACKLINKS_BILLING_ISSUE`.

### Unexpected Runtime Errors

Any error that fails validation detection and cannot be mapped to a known application code becomes a generic `INTERNAL_ERROR`. These represent unhandled exceptions and always trigger analytics capture since they indicate potential system defects requiring immediate attention.

## The Middleware Pipeline

The `errorHandlingMiddleware` in [`src/middleware/errorHandling.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) serves as the central entry point for all server functions. This TanStack-Start function-level middleware wraps every handler in a `try/catch` block to ensure consistent error processing across the application.

```typescript
// Registering the middleware in your route configuration
import { errorHandlingMiddleware } from "@/middleware/errorHandling";

export const route = {
  middleware: [errorHandlingMiddleware],
  // additional route configuration...
};

```

## Analytics and Reporting Logic

Not all errors warrant analytics tracking. The `shouldCaptureAppErrorCode` function in [`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts) maintains a whitelist of `NON_REPORTABLE_ERROR_CODES` including `UNAUTHENTICATED`, `VALIDATION_ERROR`, and audit-related limits.

When an error is capturable, the middleware invokes `captureServerError` from [`src/server/lib/posthog.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/posthog.ts) to forward structured error reports to PostHog. This enables team monitoring of system health without logging sensitive authentication failures or routine validation mistakes.

## Client-Safe Error Sanitization

The `toClientError` function in [`src/server/lib/errors.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/errors.ts) strips internal error details before sending responses to the browser. Unless an error code appears in the `CLIENT_DETAIL_ERROR_CODES` list (currently limited to `AUTH_CONFIG_MISSING`), clients receive only the error code or a generic `INTERNAL_ERROR` message. This prevents information leakage while maintaining debugging capabilities for specific configuration issues.

## Implementation Examples

The following patterns demonstrate how to work with OpenSEO's error handling system in server functions:

```typescript
// Automatic validation error handling
export const someServerFn = createServerFn()
  .input(z.object({ url: z.string().url() }))
  .handler(async ({ input }) => {
    // If input fails schema validation, TanStack throws a plain Error
    // that the middleware converts to an AppError with code VALIDATION_ERROR.
    // No manual error handling required.
  });

```

```typescript
// Raising custom application errors
import { AppError } from "@/server/lib/errors";

export const fetchBacklinks = createServerFn()
  .handler(async () => {
    const ok = await someExternalCall();
    if (!ok) {
      // Recognized by asAppError and reported based on its code whitelist status
      throw new AppError("BACKLINKS_BILLING_ISSUE", "Billing problem");
    }
  });

```

## Summary

- **Centralized Middleware**: [`src/middleware/errorHandling.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) wraps all server functions to provide consistent error handling across the application.
- **Validation Detection**: The `isValidatorError` helper identifies schema validation failures and assigns the `VALIDATION_ERROR` code.
- **Error Normalization**: `asAppError` maps unknown errors to known codes while preserving unmapped errors as unexpected runtime exceptions.
- **Selective Analytics**: `shouldCaptureAppErrorCode` filters out `NON_REPORTABLE_ERROR_CODES` before sending data to PostHog via `captureServerError`.
- **Security Sanitization**: `toClientError` ensures only safe error details reach the client, defaulting to `INTERNAL_ERROR` for non-whitelisted codes.

## Frequently Asked Questions

### How does OpenSEO differentiate between validation and application errors?

OpenSEO uses the `isValidatorError` helper in [`src/middleware/errorHandling.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/errorHandling.ts) to detect validation errors by checking for JSON-encoded schema issues in the error object. Application errors are identified when `asAppError` successfully matches the error message against known codes in [`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts). If neither check succeeds, the system treats the error as an unexpected runtime exception.

### Which error codes does OpenSEO exclude from PostHog analytics?

The `NON_REPORTABLE_ERROR_CODES` whitelist in [`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/error-codes.ts) excludes authentication failures like `UNAUTHENTICATED`, validation errors marked as `VALIDATION_ERROR`, and audit-related limit errors. The `shouldCaptureAppErrorCode` function returns `false` for these codes, preventing them from being forwarded to `captureServerError` in [`src/server/lib/posthog.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/posthog.ts).

### What happens when an error doesn't match any known error code?

When `asAppError` cannot map an error to a known `ErrorCode`, the system leaves the error unchanged. If the error also fails the `isValidatorError` check, the middleware treats it as an unexpected runtime error. These errors are always captured for analytics (since they lack a whitelist code) and are converted to `INTERNAL_ERROR` before reaching the client via `toClientError`.

### How can I throw a custom application error in OpenSEO?

Import the `AppError` class from `@/server/lib/errors` and instantiate it with a valid error code and message. When thrown within a server function wrapped by `errorHandlingMiddleware`, the error will be recognized by `asAppError`, evaluated by `shouldCaptureAppErrorCode` for analytics eligibility, and sanitized by `toClientError` before reaching the client.