drizzle-team/drizzle-orm · error · Error

crudPolicy requires a read policy

Error message

crudPolicy requires a read policy

What it means

An Error 'crudPolicy requires a read policy' thrown by crudPolicy() in drizzle-orm/src/neon/rls.ts:21 when options.read === undefined. crudPolicy generates the four RLS policies (select/insert/update/delete) for a role; it requires an explicit read decision (true, false, null, or a SQL expression) because omitting it is almost certainly a mistake. Note null is valid (suppresses the select policy) — only undefined throws.

Source

Thrown at drizzle-orm/src/neon/rls.ts:21

import { PgRole, pgRole } from '~/pg-core/roles.ts';
import { type SQL, sql } from '~/sql/sql.ts';

/**
 * Generates a set of PostgreSQL row-level security (RLS) policies for CRUD operations based on the provided options.
 *
 * @param options - An object containing the policy configuration.
 * @param options.role - The PostgreSQL role(s) to apply the policy to. Can be a single `PgRole` instance or an array of `PgRole` instances or role names.
 * @param options.read - The SQL expression or boolean value that defines the read policy. Set to `true` to allow all reads, `false` to deny all reads, or provide a custom SQL expression. Set to `null` to prevent the policy from being generated.
 * @param options.modify - The SQL expression or boolean value that defines the modify (insert, update, delete) policies. Set to `true` to allow all modifications, `false` to deny all modifications, or provide a custom SQL expression. Set to `null` to prevent policies from being generated.
 * @returns An array of PostgreSQL policy definitions, one for each CRUD operation.
 */
export const crudPolicy = (options: {
	role: PgPolicyToOption;
	read: SQL | boolean | null;
	modify: SQL | boolean | null;
}) => {
	if (options.read === undefined) {
		throw new Error('crudPolicy requires a read policy');
	}

	if (options.modify === undefined) {
		throw new Error('crudPolicy requires a modify policy');
	}

	let read: SQL | undefined;
	if (options.read === true) {
		read = sql`true`;
	} else if (options.read === false) {
		read = sql`false`;
	} else if (options.read !== null) {
		read = options.read;
	}

	let modify: SQL | undefined;
	if (options.modify === true) {
		modify = sql`true`;

View on GitHub (pinned to b7862528fd)

Solutions

  1. Provide an explicit read value: true (allow all), false (deny all), null (no select policy), or a SQL expression.
  2. If constructing options dynamically, default read explicitly instead of leaving it unset.
  3. Tighten the config object's type so TS flags the missing field at compile time.

Example fix

// before
crudPolicy({ role: authenticatedRole, modify: sql`(select auth.user_id() = ${posts.userId})` });
// throws: crudPolicy requires a read policy

// after
crudPolicy({
  role: authenticatedRole,
  read: sql`(select auth.user_id() = ${posts.userId})`,
  modify: sql`(select auth.user_id() = ${posts.userId})`,
});
Defensive patterns

Strategy: validation

Validate before calling

import type { crudPolicy } from 'drizzle-orm/neon';

type CrudOpts = Parameters<typeof crudPolicy>[0];

function assertCrudPolicyOptions(o: CrudOpts): void {
  if (o.read === undefined) {
    throw new Error('crudPolicy: set `read` (true | false | null | SQL). null suppresses the select policy.');
  }
}

assertCrudPolicyOptions(opts);

Type guard

function hasReadPolicy(o: { read?: unknown }): boolean {
  return o.read !== undefined;
}

Prevention

When it happens

Trigger: Calling crudPolicy({ role, modify: ... }) and forgetting the read field. TypeScript's declared type (read: SQL | boolean | null) does not include undefined, but undefined slips through when the object is built dynamically or cast through any.

Common situations: Building policy config from partial config objects, spreading defaults, or passing an any-typed payload where the read key is absent. The runtime guard catches what the type system should have prevented.

Related errors


AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03). Data as JSON: /data/errors/040118a4e6896f0a.json. Report an issue: GitHub.