Skip to main content
Prisma Next uses a structured error model across its packages and planes (CLI, migration planning, query planning, runtime execution). The goal is consistent, composable error handling: failures are actionable and machine-readable; bugs fail fast with full stack traces; boundaries convert structured failures into typed Result envelopes that callers can handle deterministically.

The three error categories

A failure is an expected, explainable outcome where a logical condition is not met. The caller can fix something deterministically.Examples:
  • Invalid user input or config shape
  • Plan-builder misuse — for example, calling update() without a .where() clause
  • Capability gating — a feature requires a contract capability that is absent
  • Policy or guardrail blocks — budgets or lints in strict mode
Failures should carry a stable code and structured metadata when surfaced. Within package internals you may throw a structured failure to abort quickly; at system boundaries, convert it to a Result or structured envelope.
An operational error is an expected error caused by an external system, not a bug in Prisma Next.Examples:
  • Database connection refused or timed out
  • Network interruption
  • Postgres driver errors (SQLSTATE codes)
  • Permission or auth failures
Operational errors are often transient. Drivers and adapters typically throw; catch at the boundary when you can translate to a stable, actionable envelope, and retain the original stack where available.
A bug is an invariant break or programming error where the system cannot reliably continue.Examples:
  • Unexpected undefined in a branch that should never be reached
  • Internal assertion failure
  • Serialization or type invariants broken after validation
Bugs should throw and fail fast. Catch only at the outermost boundary for crash reporting or last-resort formatting — do not disguise a bug as an expected failure.

The Result<T, F> type

For expected failures at system boundaries, Prisma Next uses a generic Result<T, F> type from @prisma-next/utils/result. It has two variants: Ok<T> (success with a value) and NotOk<F> (failure with structured data).
The discriminator property is ok: true | false. Use result.value for the success case and result.failure for the failure case — distinct names prevent confusion between success data and failure data. Both type parameters are required. F has no default because the whole point of Result is to make failure types explicit, not to propagate JavaScript’s untyped error handling.
Result is a boundary policy, not a universal implementation rule. Within package internals, prefer ergonomic throws and catch at the boundary. Use Result at system boundaries (CLI commands, migration runner entrypoints, public SDK entrypoints) where callers — including agents and CI pipelines — need to handle failures deterministically.

When to use Result and when to throw

Stable error codes

Expected failures use stable, structured codes so that agents and CI pipelines can match and branch on them without fragile string matching. Example error output with a stable code:

CLI error codes and exit codes

The CLI exits with one of three codes: PN-CLI-4xxx codes are the primary error codes for CLI command failures. A selection of common ones:

Runtime streaming errors

Runtime execution returns an AsyncIterable<Row> that throws on error. This shape allows early abort and preserves context and stack in async workflows:
Guardrails (budgets and lints) may block execution by throwing a structured runtime error in strict mode before the first row is yielded. The throw always propagates through the for await loop.

Migration runner error codes

The migration runner returns Result<MigrationRunnerSuccessValue, MigrationRunnerFailure> at its entrypoint. The MigrationRunnerFailure carries a stable MigrationRunnerErrorCode: These codes are defined in @prisma-next/migration-tools and can be matched deterministically in deploy pipelines: