drizzle-team/drizzle-orm · error · Error

crudPolicy requires a modify policy

Error message

crudPolicy requires a modify policy

What it means

An Error 'crudPolicy requires a modify policy' thrown by crudPolicy() in drizzle-orm/src/neon/rls.ts:25 when options.modify === undefined. crudPolicy needs an explicit modify decision (governing insert/update/delete policies). As with read, null is valid (suppresses the modify policies) — only undefined throws.

Source

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

 * 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`;
	} else if (options.modify === false) {
		modify = sql`false`;
	} else if (options.modify !== null) {
		modify = options.modify;

View on GitHub (pinned to b7862528fd)

Solutions

  1. Provide an explicit modify value: true, false, null, or a SQL expression.
  2. Default modify explicitly when assembling options dynamically.
  3. Strengthen the config type so TS catches the missing field.

Example fix

// before
crudPolicy({ role: authenticatedRole, read: true });
// throws: crudPolicy requires a modify policy

// after
crudPolicy({ role: authenticatedRole, read: true, modify: true });
// or, to suppress modify policies entirely:
crudPolicy({ role: authenticatedRole, read: true, modify: null });
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.modify === undefined) {
    throw new Error('crudPolicy: set `modify` (true | false | null | SQL). null suppresses modify policies.');
  }
}

assertCrudPolicyOptions(opts);

Type guard

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

Prevention

When it happens

Trigger: Calling crudPolicy({ role, read: ... }) and omitting modify. Reaches the guard after the read check passes, so you only see this when read is set but modify is undefined.

Common situations: A read-heavy table where the developer specifies read:true and forgets modify, or builds the options object from a partial. The runtime check enforces an explicit modify decision.

Related errors


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