Prisma ORM 8 is here.Read the docs

Built-in: budgets

The budgets middleware caps row counts and makes over-latency queries visible.

The budgets middleware puts a cost ceiling on every query: it rejects or warns on queries that would read more rows than you allow, counts rows while they stream, and reports queries that run longer than your latency budget.

Register it on the runtime with the row and latency limits you want to enforce:

src/prisma/db.ts
import postgres from '@prisma/orm-postgres/runtime';
import { budgets } from '@prisma/orm-postgres/family-runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL']!,
  middleware: [
    budgets({
      maxRows: 10_000,
      defaultTableRows: 10_000,
      tableRows: { user: 10_000, post: 10_000 },
      maxLatencyMs: 1_000,
    }),
  ],
});

budgets is SQL-family middleware (it declares familyId: 'sql'), so it registers on PostgreSQL runtimes. It ships with @prisma/orm-postgres/family-runtime, which comes in through @prisma/orm-postgres; you do not install anything extra.

Options

All options are optional.

OptionTypeDefaultWhat it controls
maxRowsnumber10_000The most rows a single query may produce, checked against both the pre-execution estimate and the observed row stream
defaultTableRowsnumber10_000Assumed row count for tables not listed in tableRows, used by the estimator
tableRowsRecord<string, number>{}Per-table row-count assumptions for the estimator, keyed by table name
maxLatencyMsnumber1_000Latency budget for one query, checked after execution
severities.rowCount'warn' | 'error''error'Whether pre-execution row-budget violations block the query or log a warning. Strict mode always blocks
severities.latency'warn' | 'error''warn'Whether an over-budget latency blocks the query or logs a warning. Strict mode always blocks

A latency violation blocks when severities.latency is 'error', or when the runtime is in strict mode. The postgres(...) client is always strict: it never forwards a mode, so BUDGET.TIME_EXCEEDED throws there whatever you set. Only mongo(...) accepts mode: 'permissive', and there the default 'warn' logs instead of throwing. Either way the check happens after execution, so it is a visibility tool rather than a cancellation mechanism.

How it enforces the budget

The middleware checks at three points in the query lifecycle:

  1. Before execution (in beforeQuery for row queries, beforeExecute for writes), it inspects the query before it runs. A SELECT with no LIMIT (and no aggregation that collapses the result) is treated as unbounded, and a bounded query whose estimated rows exceed maxRows is over budget. The estimate comes from tableRows and defaultTableRows, so tune those to your data. Violations raise BUDGET.ROWS_EXCEEDED before the driver runs, or warn when severities.rowCount is 'warn' and the runtime is not strict.
  2. As rows arrive (in onRow), it counts what the database actually returns. If the observed count passes maxRows, it throws BUDGET.ROWS_EXCEEDED and aborts the stream, which protects you when the estimate was wrong.
  3. After execution (in afterQuery and afterExecute), it compares the measured latencyMs against maxLatencyMs and reports BUDGET.TIME_EXCEEDED when the query ran too long. This check runs after the work is done, so it cannot save the slow query itself; it exists to make slow queries loud instead of invisible.

What you see when a budget trips

A blocked query rejects with a runtime error carrying the budget code and details:

Row budget error
BUDGET.ROWS_EXCEEDED: Unbounded SELECT query exceeds budget
  { source: 'ast', maxRows: 10000 }

The source field tells you whether the violation came from the pre-execution estimate ('ast') or the observed rows ('observed'). A blocking latency violation carries the measured and allowed values:

Latency budget error
BUDGET.TIME_EXCEEDED: Query latency exceeds budget
  { latencyMs: 2481, maxLatencyMs: 1000 }

When the violation only warns, the same information is emitted through the runtime logger. To fix an unbounded-select violation, add a limit to the query (limit(...) on the ORM API, .limit(...) on the SQL query builder) or raise the budget deliberately.

Common gotchas

See also

On this page